Direct answer
The bare domain (the zone apex, example.com itself) must carry an SOA (start of authority) record and NS (name server) records, and DNS says that a name holding a CNAME (canonical name alias) record may hold no other data. RFC 1034 section 3.6.2 states it, and RFC 2181 section 10.1 allows only DNSSEC-related records beside a CNAME. So an apex CNAME is impossible by design: it would hide the SOA and NS records, and any MX (mail exchanger) or TXT (text) records at the apex too. DNS providers get around it with a provider-side feature that looks like a CNAME in their control panel but answers with ordinary A and AAAA records. It goes by different names: Route 53 calls it an alias record, Cloudflare calls it CNAME flattening. It is a vendor feature, not a standard record type.
What the rule looks like in practice (executed with BIND's checker)
A zone file that puts a CNAME at the apex next to the required NS record:
$TTL 300
@ IN SOA ns1.example.com. hostmaster.example.com. ( 1 3600 600 86400 300 )
IN NS ns1.example.com.
IN CNAME my-lb-123.us-east-1.elb.example-cloud.net.
ns1 IN A 192.0.2.53
www IN CNAME my-lb-123.us-east-1.elb.example-cloud.net.
named-checkzone example.com /z/db.example.com (BIND 9.20) refused it:
dns_master_load: /z/db.example.com:4: example.com: CNAME and other data
dns_master_load: /z/db.example.com:4: example.com: CNAME and other data
zone example.com/IN: loading from master file /z/db.example.com failed: CNAME and other data
zone example.com/IN: not loaded due to errors.
Adding an MX record at the apex next to the CNAME fails the same way. Removing the apex CNAME and keeping www as a CNAME loads with OK: a CNAME is fine at any non-apex name that has nothing else on it.
What the providers do instead
- Route 53 alias record. You create an A (and AAAA) record at the apex flagged as an alias to the load balancer's hostname. When a query arrives, Route 53 answers with the load balancer's current IP addresses, so the client sees plain A records. Alias queries to AWS resources are not charged; for an alias to an AWS resource you cannot set the TTL (time-to-live), because Route 53 uses the resource's default. An alias is limited to certain AWS resources or another record in the same hosted zone. A change batch looks like this (the zone id is a placeholder):
json
{ "Changes": [ { "Action": "UPSERT",
"ResourceRecordSet": { "Name": "example.com.", "Type": "A",
"AliasTarget": { "HostedZoneId": "Z0000000EXAMPLE",
"DNSName": "my-lb-123.us-east-1.elb.amazonaws.com.",
"EvaluateTargetHealth": true } } } ] }
applied with aws route53 change-resource-record-sets --hosted-zone-id ID --change-batch file://alias.json. Reading the batch: Changes is a list of edits applied together; "Action": "UPSERT" means create the record or replace it if it exists; Name and Type say this is the apex A record; AliasTarget replaces the usual list of IP addresses; inside it DNSName is the load balancer's hostname, HostedZoneId is the ID of the zone that the load balancer's name lives in (the AWS documentation says to take it from the load balancer, not from your own zone, which is why the value shown is a placeholder), and EvaluateTargetHealth: true makes an alias inherit the health of the referenced resource. The AWS documentation says it has no effect when the record uses simple routing, which is what this single record does (the batch has no routing policy), so it is harmless here. It only matters when several records share the name under a failover, weighted, latency or geolocation policy, where Route 53 can then route queries to other healthy resources. (Fields checked against the Route 53 API reference for AliasTarget.)
- CNAME flattening (Cloudflare). The provider resolves the CNAME chain itself and returns the final A/AAAA records instead of a CNAME. If the target has no A or AAAA records (a dangling CNAME), the answer is empty, which can look like a change that has not propagated.
Either way the provider synthesises the answer on every query and the zone file you export may not be able to represent it, which leads to the trade-offs.
What a client receives differs between a true CNAME and a provider-side apex answer. This lab (illustrative: BIND serving the zones on 127.0.0.1 port 5300, Unbound as the resolver) has www as a true CNAME and the apex holding plain A records, the shape a flattening provider returns. The zone data, with SOA and NS lines left out:
; zone example.com, default TTL 300
@ IN A 198.51.100.11
@ IN A 198.51.100.10
@ IN MX 10 mail.example.com.
www IN CNAME my-lb-123.lb.example-cloud.test.
; zone example-cloud.test, default TTL 60
my-lb-123.lb IN A 198.51.100.11
my-lb-123.lb IN A 198.51.100.10
and Unbound had a stub-zone: for each of the two zones with stub-addr: 127.0.0.1@5300 (plus do-not-query-localhost: no). The order of the address lines rotates between queries, so it can differ from run to run:
text
$ dig @127.0.0.1 +noall +answer www.example.com A
www.example.com. 300 IN CNAME my-lb-123.lb.example-cloud.test.
my-lb-123.lb.example-cloud.test. 60 IN A 198.51.100.11
my-lb-123.lb.example-cloud.test. 60 IN A 198.51.100.10
$ dig @127.0.0.1 +noall +answer example.com A
example.com. 300 IN A 198.51.100.11
example.com. 300 IN A 198.51.100.10
The true CNAME returns two kinds of record: the alias line and the target's addresses, each with its own TTL (300 and 60). The apex answer is addresses only, under the apex's own name and a single TTL, so the client cannot tell an alias stood behind it, and the MX record at the same name still works because there is no CNAME there.
Recommendation
If the zone is on Route 53 and the target is an AWS load balancer, use the alias: it follows load balancer IP changes automatically and costs nothing per query. If the DNS host offers flattening and the load balancer is another cloud's, use that, and test that the result still follows the load balancer's address changes. If the DNS host offers neither, either put www on a CNAME and redirect the bare domain to www over HTTP from a small fixed-address service, or front the application with an address that does not change (a static-IP front end is a service that keeps one fixed address and forwards to the real servers; anycast announces the same address from many locations so the network routes each client to a nearby one) so a plain A record is enough.
Trade-offs and pitfalls
- Portability. Alias and flattening are not part of the DNS standard. Moving DNS providers means re-implementing the apex record, and zone-file exports may drop or mis-translate it.
- Steering (answering different clients with different addresses, usually the nearest). If the load balancer's own DNS returns different addresses depending on where the asker is, a flattening provider asks from its own location, not the user's, so user-aware routing can degrade.
- TTL and failover. You may not control the TTL of an AWS alias, and a flattened answer's lifetime is decided by the provider. Plan DNS-based failover around that.
- Do not strand mail. A CNAME at a name with MX and TXT records (SPF, domain verification) would break mail; this is exactly why the apex is excluded.