How to Ship Apex Domain Support for Your SaaS Customers (Without the CNAME-at-Root Mess)
Almost every SaaS that ships custom-domain support eventually hits the same support ticket. "Hi, I want to use acme.com, not www.acme.com. How?"
You explain CNAME flattening. They forward your email to their IT person. Their IT person says "we use GoDaddy, that doesn't work here." Now you have a problem. This post is how to ship apex support so that ticket never opens.
Why this is a thing
DNS has a rule. You can't put a CNAME on the apex (root) of a domain. www.acme.com → CNAME → yoursaas.com is fine. acme.com → CNAME → yoursaas.com is not. The reason is technical — CNAMEs replace all other records at the name, and the apex needs to keep SOA and NS records — but for your customer it's just "the thing that doesn't work."
Their options:
- Use a subdomain (
www.acme.com) and skip the apex entirely - Use
Arecords at the apex, pointing at your edge IPs - Use the registrar's "ALIAS" or "ANAME" record type (CNAME-flavored, works at the apex)
- Use Cloudflare DNS with "CNAME flattening"
Each has trade-offs. Let me walk through them.
Option 1: Subdomain only
The easiest path. Your UI just doesn't accept apex domains. Customers add www.acme.com (or app.acme.com, shop.acme.com).
You lose the customers who really want acme.com. There are more of those than you'd think — at any reasonable scale, 20-30% of inbound asks want the apex.
Validate the hostname in your form and reject anything without a subdomain:
function validateHostname(h: string) {
const labels = h.split(".");
if (labels.length < 3) {
throw new Error(
"Apex domains aren't supported yet. Use a subdomain like www." + h,
);
}
}
This is fine for v1. Don't ship a custom-domain feature with apex support before non-apex even works. Most teams over-engineer the apex story on day one.
Option 2: A records (the brutally simple answer)
Tell the customer to add A records pointing at your edge IPs:
acme.com A 192.0.2.10
acme.com A 192.0.2.11
This works at every registrar in the world. The cost: you need static edge IPs that don't change, and you want at least two for redundancy.
Use this when:
- Your edge has stable IPs (your own infra or a provider that gives you BYO IPs)
- You want the simplest possible answer for the customer
Don't use this when:
- Your edge is on a multi-tenant CDN with rotating IPs (most managed providers)
- You want to add edge regions later without forcing every customer to update DNS
Option 3: ALIAS / ANAME records
Some registrars (DNSimple, DNS Made Easy, Hover, name.com, Cloudflare-as-registrar) support a record type called ALIAS or ANAME. It looks like an A record to the world, but it tracks a CNAME-style hostname server-side:
acme.com ALIAS edge.yoursaas.com
Right answer if the customer is on a registrar that supports it. Their resolver translates the hostname to current IPs every query, so your edge IPs can change without breaking anything.
The catch: GoDaddy, Namecheap (default DNS), Google Domains, and IONOS — none of them support ALIAS or ANAME. So half your customer base can't use this option.
Option 4: Cloudflare CNAME flattening
If the customer uses Cloudflare DNS (free tier is enough), they can put a regular CNAME at the apex:
acme.com CNAME edge.yoursaas.com
Cloudflare's DNS resolves the CNAME server-side and serves A/AAAA records to the world. Best of both worlds: works like a CNAME, looks like an A record, no static IP requirement on your side.
This is the answer for customers sophisticated enough to be on Cloudflare DNS. Surprisingly many are.
What to actually tell your customer
The UI matters. Don't make them pick blind. Surface BOTH options and let them choose based on their registrar:
To point
acme.comat our platform, add ONE of the following at your DNS provider:Option A — CNAME / ALIAS / ANAME (preferred):
acme.com CNAME edge.yoursaas.comOption B — A records (works everywhere):
acme.com A 192.0.2.10acme.com A 192.0.2.11If your registrar supports CNAME / ALIAS / ANAME at the apex (Cloudflare DNS, DNSimple, DNS Made Easy, Hover, name.com), use Option A.
If you're on GoDaddy, Namecheap (default DNS), Google Domains, or IONOS, use Option B.
A registrar matrix is more useful than a flowchart. The customer scans the list, finds their registrar, follows the matching option.
A few practical traps
CAA records. If the customer has a CAA record at the apex restricting cert issuance (acme.com CAA 0 issue "digicert.com"), Let's Encrypt can't issue. Your cert provisioning silently fails. Detect this on hostname registration:
import { promises as dns } from "node:dns";
const caaRecords = await dns.resolveCaa("acme.com").catch(() => []);
const lecAllowed =
caaRecords.length === 0 ||
caaRecords.some(
(r) => r.issue === "letsencrypt.org" || r.issue === ";",
);
if (!lecAllowed) {
throw new Error(
"CAA record blocks Let's Encrypt. Customer must add 'letsencrypt.org' to CAA",
);
}
MX records survive a CNAME (when done right). Customers panic about losing email when they add records at the apex. A records and ALIAS records leave MX alone. Only a true CNAME at the actual root would clobber MX — which is exactly why DNS forbids it. Reassure them up front.
Cloudflare orange-cloud. If the customer is using Cloudflare DNS and clicks the orange-cloud (proxy), Cloudflare puts itself in front of your edge. Your TLS handshake fails or cert validation breaks. In your docs say it loudly: "If you're using Cloudflare DNS, the proxy must be OFF (gray cloud) for this hostname."
How Domainee handles all of this
POST /v1/domains returns both CNAME and A record options in dnsRecords, with hints about which to use:
{
"domain": {
"hostname": "acme.com",
"dnsRecords": [
{
"type": "CNAME",
"name": "acme.com",
"value": "edge.domainee.dev",
"appliesIf": "registrar supports ALIAS/ANAME or you use Cloudflare DNS",
"purpose": "Traffic Routing"
},
{
"type": "A",
"name": "acme.com",
"value": "192.0.2.10",
"appliesIf": "fallback for registrars without ALIAS/ANAME support",
"purpose": "Traffic Routing"
},
{
"type": "A",
"name": "acme.com",
"value": "192.0.2.11",
"purpose": "Traffic Routing"
}
]
}
}
Your customer-facing UI renders both options with the registrar hint. They pick whichever fits. Apex works on every Domainee plan, free included.
Cloudflare for SaaS requires Enterprise tier for apex support (it's called BYO IP). Vercel for Platforms supports apex but via Vercel-controlled IPs that can change without notice. If apex is important to you, that's the practical comparison.
Ship it
50 hostnames free at domainee.dev/sign-up, apex included on every plan. Mint a key, register your first apex hostname, surface both record options in your UI, done.
For more on the Connect API see /docs/api/domains. For the broader Cloudflare for SaaS comparison see /blog/how-to-set-up-custom-domains-cloudflare-ssl-for-saas.