DEV Community

BrantLockwood468
BrantLockwood468

Posted on

Custom Domains as a SaaS Product Feature: The DNS Work You Are Taking On

Short answer: custom domains are a control-plane product, not a text field. A SaaS team that adds them owns domain verification, DNS state, TLS lifecycle, routing, abuse handling, and a migration plan. For a gaming platform moving zones away from a registrar-specific API, the durable design is an adapter around standard DNS plus explicit ownership boundaries.

Before, a registrar API call might have created a zone, written records, and returned a status your application trusted. After, your product has to represent several independently changing facts: the customer owns play.example.com; a DNS record points at the right edge; a certificate is valid; and the game service is ready to answer that hostname. Those facts do not converge at the same time.

The useful mental model is a small state machine. requested becomes awaiting_verification, then verified, then routed, and finally active. Each transition needs evidence. A successful API response is not evidence that recursive resolvers have the new record, and a DNS lookup is not evidence that the customer controls the name.

That distinction matters during a zone migration. Keep the old registrar integration as a read-only inventory source, write desired state to your own record model, and make reconciliation idempotent. A retry should produce the same desired record set, not a second zone or a duplicate TXT token. In a gaming system, I would also persist the edge target and tenant mapping together, because changing one without the other can send a login request to the right hostname but the wrong environment. The migration job should emit a correlation ID, record the authoritative nameserver answer it observed, and leave an audit trail that can be replayed after a deploy. That is more data than a registrar response gives you, but it is the data your support and on-call teams need when a tournament starts in ten minutes.

DNS is slow.

What are custom domains as a SaaS feature really asking your team to own?

Start with ownership. A customer-owned zone means the customer keeps the authoritative nameservers and adds records you specify, often a CNAME or a TXT verification record. A platform-owned zone means your service operates the authoritative zone and can create records directly. The UI can look identical while the operational contract is completely different.

Verification should be narrow and replayable. Generate a high-entropy token, publish it under a documented label, and query authoritative DNS before accepting it. Cache the result, but provide a manual recheck because TTLs and negative caching make “not found” a time-dependent answer. Never treat an HTTP request that happens to contain the hostname as proof of control.

Routing is the next boundary. Resolve the hostname at the edge, attach a tenant identifier only after a verified mapping exists, and reject unknown hostnames. This prevents a stale DNS record from selecting the wrong game environment. Log the hostname, tenant, mapping version, and decision; those fields turn a vague “the lobby is down” report into a searchable event.

TLS is part of the feature. Certificate issuance, renewal, and revocation need their own state and alerts. A domain can be verified while its certificate is still pending, so expose that distinction instead of showing one optimistic green check.

A copyable reconciliation shape

The exact DNS provider is an implementation detail. Your application code should depend on a small interface and make every operation safe to retry:

type DomainState = "requested" | "awaiting_verification" | "verified" | "routed" | "active";

type Domain = {
  hostname: string;
  tenantId: string;
  state: DomainState;
  verificationName: string;
  verificationValue: string;
  desiredTarget: string;
};

async function reconcile(domain: Domain, dns: {
  read(name: string, type: "TXT" | "CNAME"): Promise<string[]>;
  ensure(name: string, type: "TXT" | "CNAME", value: string): Promise<void>;
}) {
  const tokens = await dns.read(domain.verificationName, "TXT");
  if (!tokens.includes(domain.verificationValue)) {
    await dns.ensure(domain.verificationName, "TXT", domain.verificationValue);
    return "awaiting_verification" as const;
  }

  await dns.ensure(domain.hostname, "CNAME", domain.desiredTarget);
  return "routed" as const;
}
Enter fullscreen mode Exit fullscreen mode

In production, put a queue and a bounded retry policy around reconciliation. Record the last observation and next retry time, and make an alert when a domain stays in one state longer than your product promises. A short before/after dashboard is enough: requested domains, verified domains, certificate-pending domains, and routing failures.

I once assumed a registrar migration was mostly a mapping exercise. The surprise was the tail: a handful of old TXT records, a forgotten staging hostname, and a resolver cache made the happy path look finished while players still reached the old edge. The fix was boring and effective—inventory first, reconcile second, and keep the old path observable until traffic is zero. I've learned to treat every “complete” status as a prompt for another observation, not as a celebration. If a record has a 300-second TTL, that is a planning input, not a promise that all players will switch in exactly five minutes; recursive caches, negative answers, and client behavior can stretch the tail. Your mileage may vary, so choose the observation window from measured traffic and the risk of the event you are protecting.

Standards and failure modes that shape the design

DNS TTL is a cache instruction, not a deployment lock. Lowering it shortly before a cutover cannot force every recursive resolver to forget an older answer. Plan overlap, measure traffic at both edges, and remove the old record only after the observation window closes.

Use DNSSEC validation where your resolver and threat model support it, and keep verification records scoped to one tenant. For email on a customer domain, document SPF, DKIM, and DMARC responsibilities instead of silently editing mail policy. DMARC is defined in RFC 7489, and a SaaS feature that changes customer DNS can affect mail even when the game traffic is healthy.

The common failures are predictable: accepting a non-authoritative answer as proof, allowing a hostname collision, issuing a certificate before ownership is verified, and deleting the old route before caches expire. Each one deserves a metric and a runbook entry. Short alerts win.

Choosing an ownership model without surprising customers

Platform-owned zones make automation straightforward, but they increase your blast radius: your team holds authoritative control and must provide export, recovery, and incident procedures. Customer-owned zones preserve customer control and simplify offboarding, yet every onboarding depends on instructions, DNS permissions, and propagation time.

The catch is that neither model fits every tenant. A regulated studio or an enterprise with its own DNS team may reject platform ownership; a small indie team may not have access to a DNS console at all. Offer a clear decision rule, document the records each model requires, and support a staged migration so the registrar-specific API can be retired without a flag-day cutover.

Further reading (References)

Top comments (0)