ccnames.dev

cnames.dev / blog

Guide22 min read

Custom Domains for SaaS: The Complete Guide (2026)

CNAME vs A records, the apex problem, SSL issuance (HTTP-01 vs DNS-01), SNI, proxy architecture, tenant routing, and how to avoid domain takeover. Written by an operator.

S
Saeed
July 6, 2026

If you run a SaaS, your customers eventually want their product to live on their domain — app.acme.com instead of acme.yourapp.com. It looks like a small feature. It is not. Behind “let customers use their own domain” sits DNS, certificate automation, SNI, edge routing, tenant isolation, and a surprising number of ways to get taken over.

This is the guide I wish existed when we started. It is written from running this in production for more than 5,000 customer domains at Lindo.ai. The whole flow, end to end, is really just:

# 1. Customer points DNS at you
app.acme.com.  CNAME  proxy.yourapp.com.

# 2. You detect the hostname, verify ownership, and issue a cert
curl -X POST https://api.cnames.dev/v1/domains \
  -H "Authorization: Bearer sk_live_..." \
  -d '{"hostname":"app.acme.com","origin":"acme.internal"}'

# 3. First HTTPS request arrives; edge presents the right cert and routes to the tenant
GET https://app.acme.com  ->  TLS (SNI: app.acme.com)  ->  origin for tenant "acme"

Everything below is the detail hiding inside those three steps. If you only take one thing away: the hard parts are DNS ergonomics and certificate lifecycle, not the proxy.

The two connection models: CNAME vs A record

There are exactly two ways a customer can send traffic for their domain to you, and the choice has long-term consequences.

CNAME to a hostname you own (preferred)

You give the customer a target like proxy.yourapp.com and they create:

app.acme.com.  CNAME  proxy.yourapp.com.

The win is indirection. proxy.yourapp.com is a name you control, so you can change the IPs behind it — add anycast points, migrate providers, fail over a region — without touching a single customer's DNS. This is the model to push customers toward whenever the record is a subdomain.

A/AAAA record to your IPs

The customer points their domain straight at your IP addresses:

acme.com.  A  203.0.113.10

You need this at the apex (see below), but it hard-codes your IPs into thousands of zones you do not control. Only publish apex IPs that are stable anycast addresses you are prepared to keep forever. Renumbering them later is a multi-month migration where you are at the mercy of customers updating records.

The apex problem (and why www is easier)

Customers usually want the bare domain, acme.com, not www.acme.com. But DNS forbids a CNAME on a zone apex: the apex must carrySOA and NS records, and RFC 1034 says a CNAME cannot coexist with any other record on the same name. So acme.com CNAME proxy.yourapp.com is simply invalid.

Your realistic options, in rough order of preference:

This deserves its own treatment — see the CNAME-at-apex problem, and every way to solve it. When you do let customers self-serve DNS, tools like Domain Connect can set the right records for them in one click on supported providers.

How TLS certificates actually get issued

Once DNS points at you, HTTPS needs a certificate for each hostname. In 2026 this is a solved problem via ACME and free CAs like Let's Encrypt or ZeroSSL — but you must understand the validation methods, because they dictate your architecture.

HTTP-01

The CA gives you a token; you serve it at http://app.acme.com/.well-known/acme-challenge/<token>; the CA fetches it over port 80. This is the natural fit for custom domains: the moment a customer's CNAME resolves to your edge, your edge can answer the challenge. No access to the customer's DNS required.

TLS-ALPN-01

Validation happens inside a special TLS handshake on port 443 using the acme-tls/1 ALPN protocol. Useful when port 80 is blocked. Also requires the domain to already resolve to you.

DNS-01

You prove control by creating a _acme-challenge TXT record. This is the only method that issues wildcard certificates — but it needs write access to the zone. For customer-owned domains you rarely have that, so DNS-01 is for your own domains (issuing *.yourapp.com), not for customer domains.

On-demand TLS

The pattern that makes custom domains scale: do not pre-issue anything.When the first TLS handshake for an unknown hostname arrives, check an allow-list (is this a domain a tenant registered with us?), and if yes, issue the certificate inline and cache it. Caddy calls this on-demand TLS; you can build the same with any ACME client plus a fast “is this host allowed” lookup. The allow-list check is non-negotiable — without it, anyone pointing a domain at you can burn your CA rate limits.

Rate limits are real. Let's Encrypt's well-known limits include roughly 50 certificates per registered domain per week and a cap on duplicate certificates; the new-orders limits are per-account. For custom domains, each customer domain is its own registered domain, so the per-domain cap rarely bites — but a retry storm against the account-level limits absolutely will. Always verify the current numbers in the Let's Encrypt rate-limit docs before you design your retry logic.

SNI: how the edge picks the right certificate

One IP serves thousands of domains. When a browser opens a TLS connection it sends the target hostname in the SNI field of the ClientHello, before any certificate is chosen. Your edge reads SNI, loads (or issues) the certificate for exactly that hostname, and completes the handshake. This is why on-demand TLS works: SNI tells you which cert you need at the exact moment you need it.

Practical implication: your certificate store is keyed by hostname, and your hot path is a lookup by SNI. Miss the cache and you either issue (first ever request for that host) or serve from a shared store. Keep that lookup in memory or a fast KV; it is on every cold connection.

What happens between the CNAME and the first byte

A request to a custom domain travels through more stages than people expect:

  1. DNS: app.acme.com resolves via the CNAME chain to your anycast edge.
  2. TCP + TLS: connection lands on the nearest edge node; SNI selects the cert.
  3. Host lookup: map the hostname to a tenant and an origin.
  4. Header shaping: set Host, forward the real client IP, inject per-tenant headers.
  5. Origin fetch: proxy to the tenant's backend (which may itself be multi-tenant).
  6. Response: stream back, optionally rewriting redirects and cookies.

Two details cause most production pain. First, the real client IP: once you proxy, the origin sees your IP unless you forward the client's address (via X-Forwarded-For / X-Real-IP, or PROXY protocol). Rate limiters and geo logic downstream depend on getting this right. Second, the Host header: your origin needs to know which tenant this is — either preserve the original Host or map it to an internal identifier the backend understands.

Tenant routing and isolation

The core data structure is a mapping from hostname → tenant → origin + policy. Everything else is a cache of it.

{
  "app.acme.com": {
    "tenant": "acme",
    "origin": "acme-cluster.internal:8080",
    "headers": { "X-Tenant-Id": "acme" },
    "verified": true
  }
}

Isolation matters more than it looks. A bug that routes app.acme.com to tenant globex is a cross-tenant data breach, not a 404. Design so that an unverified or unknown host fails closed — it should never fall through to a default tenant. Per-tenant header profiles, redirect rules, and origin overrides let you give each customer correct behavior without forking your proxy.

Wildcards vs per-domain certificates

Two different jobs that people conflate:

Security: domain takeover and dangling CNAMEs

This is where custom-domain features quietly become vulnerabilities. Three rules:

You can sanity-check any customer's setup with our free CNAME checker and SSL checker — no signup.

Build vs buy

You can absolutely build all of this. Caddy with on-demand TLS plus an allow-list endpoint gets you a working single node in a weekend. The cost shows up later: anycast for latency, multi-node certificate storage and sharing, rate-limit-aware retries, DDoS posture, observability per tenant, and the on-call that comes with being in the TLS path for someone else's production traffic. We wrote an honest breakdown in cnames.dev vs self-hosting Caddy on-demand TLS, and how the managed CDN-native option compares in Cloudflare for SaaS alternative.

Buying it means one API call per domain and none of the lifecycle burden. That is exactly what cnames.dev does, and it is the same infrastructure we run for website builders, AI app builders, and agencies today.

Summary

Building a SaaS that needs custom domains? cnames.dev gives every customer their own domain — SSL, edge routing, and per-tenant isolation in one API call. Free for 25 domains, no demo call. Start free · Read the docs

Frequently asked questions

Should customers point their domain with a CNAME or an A record?

Prefer a CNAME to a hostname you control (for example proxy.yourapp.com). A CNAME lets you change the underlying IPs without asking every customer to update DNS. Use A/AAAA records only at the apex, where CNAME is not allowed, or offer an ALIAS/ANAME record if the customer's DNS provider supports it.

How long does SSL certificate issuance take for a new custom domain?

With an automated ACME flow (Let's Encrypt HTTP-01 or TLS-ALPN-01) issuance typically completes in a few seconds once DNS resolves to your edge. The slow part is almost never the CA — it is waiting for the customer's DNS change to propagate, which can take seconds to a few hours depending on their provider's TTL.

What is the CNAME-at-apex problem?

DNS does not allow a CNAME on a zone apex (example.com) because the apex must carry SOA and NS records, and a CNAME cannot coexist with other records on the same name. The workarounds are ALIAS/ANAME records, apex A records pointing at stable anycast IPs, or redirecting the apex to a www subdomain.

Do I need a wildcard certificate for customer domains?

No. Each customer domain (app.customer.com) gets its own certificate issued on demand. Wildcards (*.yourapp.com) are useful for the subdomains you hand out under your own domain, not for customer-owned domains, and they require DNS-01 validation.

How do I prevent domain takeover with custom domains?

Verify ownership before routing (a TXT record or a unique CNAME target), stop serving and de-provision certificates promptly when a customer removes their domain, and never leave a dangling CNAME pointing at a target you no longer control. Monitor for domains that resolve to you but are no longer active in your system.

Keep reading

S

SaeedFounder, cnames.dev — runs custom-domain infra for 5,000+ production sites at Lindo.ai.