Apex Domain Setup on Cloudflare versus Route 53
After working through this guide you will be able to put example.com in front of a CDN or load balancer on either platform, predict exactly what a resolver receives on the wire, and defend the choice on the four grounds that actually differ between them: TTL control, health integration, query billing, and how hard it is to leave.
Both platforms solve the same problem — the apex cannot hold a CNAME — and both solve it by answering apex queries with synthesized address records. That is where the similarity stops. Cloudflare re-resolves a configured hostname and republishes whatever it finds; Route 53 holds an internal pointer to a specific AWS resource and answers from its own routing state. The consequences show up in an outage, in a bill, and on the day you decide to move providers. This guide walks both configurations end to end, then compares them on the axes that decide the answer.
Key objectives:
- Read the wire difference between a flattened answer and an alias answer, and know which TTL each one carries.
- Configure the apex on both platforms with working API calls, including the AAAA and MX cases.
- Predict failover, target-IP-change, and query-cost behavior before committing to a platform.
- Verify the result with a repeatable
digprocedure and pick the mechanism with an explicit decision rule.
The wire looks the same; the contract does not
Capture both answers and you will see the same record type, the same owner name, and a plausible address set. A packet capture cannot tell you which platform produced it. What differs is everything behind the answer.
Cloudflare’s flattening is a resolution: the nameserver takes the hostname you configured at @, resolves it like any recursive resolver would, and copies the resulting addresses into the answer. The target can be any hostname on the public internet — a Fastly service, a Vercel deployment, an ALB in someone else’s account. Because it is a real resolution, the answer inherits the target’s TTL, and Cloudflare cannot honestly serve those addresses for longer than their upstream owner says they are valid.
Route 53’s alias is not a resolution at all. AliasTarget names a specific AWS resource by hosted zone ID and DNS name, and Route 53 answers from its own knowledge of that resource’s current addresses. There is no upstream TTL to inherit, so Route 53 publishes a fixed 60-second TTL for alias answers to non-AWS-resource targets and evaluates target health internally when you ask it to. The price of that integration is scope: the target must be an AWS resource or another record in the same hosted zone. You cannot alias to app.fastly.net.
That single asymmetry drives most of the rest of the comparison. One mechanism is a general-purpose pointer with borrowed TTL semantics; the other is a tightly coupled routing primitive with owned TTL semantics. The general protocol background is covered in CNAME Flattening Explained, and the record-type distinction itself in CNAME Flattening vs ALIAS Records at the Apex Domain.
Configuration walkthrough
Cloudflare: a CNAME at the root
Create a CNAME on the @ label. Flattening applies automatically at the root; nothing else needs enabling.
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"type":"CNAME","name":"@","content":"app.cdn.net","proxied":false,"ttl":300}' \
| jq '{name: .result.name, type: .result.type, content: .result.content}'
Expected output names the apex and the target. With proxied: false, resolvers receive the target’s own addresses. Flip to proxied: true and the answer becomes Cloudflare’s anycast addresses instead, the published TTL drops to 300 seconds regardless of your setting, and your origin address stops appearing in public DNS entirely.
Zone-wide policy is a separate setting. flatten_at_root is the default and is what you want; flatten_all flattens every CNAME in the zone, which removes the indirection your subdomains may be relying on for vendor-side failover.
curl -s "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/cname_flattening" \
-H "Authorization: Bearer $CF_API_TOKEN" | jq -r '.result.value'
IPv6 needs no extra record: if the target publishes AAAA, the flattened apex answers AAAA too.
Route 53: an alias record set
Route 53 needs one change batch per address family, and the HostedZoneId inside AliasTarget is the service’s zone ID, not yours — Z2FDTNDATAQYW2 for CloudFront, a per-region value for an ALB, Z3AQBSTGFYJSTF for an S3 website endpoint in us-east-1.
{
"Comment": "apex + IPv6 apex onto CloudFront",
"Changes": [
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "example.com.",
"Type": "A",
"AliasTarget": {
"HostedZoneId": "Z2FDTNDATAQYW2",
"DNSName": "d1234567890.cloudfront.net.",
"EvaluateTargetHealth": false
}
}
},
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "example.com.",
"Type": "AAAA",
"AliasTarget": {
"HostedZoneId": "Z2FDTNDATAQYW2",
"DNSName": "d1234567890.cloudfront.net.",
"EvaluateTargetHealth": false
}
}
}
]
}
aws route53 change-resource-record-sets \
--hosted-zone-id Z123456ABCDEFG --change-batch file://apex.json \
--query 'ChangeInfo.{Id:Id,Status:Status}'
aws route53 get-change --id /change/C1234567890ABC --query 'ChangeInfo.Status'
The batch returns PENDING and reaches INSYNC within a minute. There is no TTL field, and supplying one is a validation error — the caching contract is Route 53’s, not yours. Managing these records through code makes the HostedZoneId constants far less error-prone; see Managing DNS Zones as Code with Terraform.
Side-by-side comparison
| Dimension | Cloudflare flattening | Route 53 alias |
|---|---|---|
| Configured as | CNAME at @ |
A/AAAA with AliasTarget |
| Legal targets | Any resolvable hostname | AWS resources and same-zone records only |
| Answer on the wire | Synthesized A/AAAA | Synthesized A/AAAA |
| TTL source | Inherited from the target record, capped by your setting | Fixed 60s, or the referenced record’s TTL for in-zone targets |
| Reaction to a target IP change | Next re-resolution, then one target-TTL window | Immediate at the authority, then up to 60s |
| Health integration | Proxy health, or Load Balancing add-on | EvaluateTargetHealth, plus failover routing policies |
| Query billing | Included in the plan | Alias queries to AWS targets are free; standard queries billed per million |
| DNSSEC | Signed online over the synthesized set | Signed by Route 53 when DNSSEC signing is enabled |
| Portability | Target is a public hostname, trivially re-pointed | Alias is AWS-only; a move means re-modelling the apex |
The two rows that decide most arguments are TTL source and portability. Everything else can be worked around.
Target IP changes and the stale window
The practical question is: my CDN rotated its address pool at 12:00 — when does the last client stop talking to the old one? On Route 53 the authority updates immediately and the exposure is bounded by the 60-second published TTL. On Cloudflare the exposure is the sum of two clocks: however long Cloudflare’s own cache holds the target resolution, plus the TTL it publishes to resolvers, which is itself derived from the target’s TTL. A target published with a 300-second TTL therefore yields roughly a five-minute worst case even though nothing in your zone says 300.
Health checking works the same way. EvaluateTargetHealth: true lets Route 53 withdraw the apex answer when the referenced ELB or CloudFront distribution is unhealthy, and a failover routing policy can put a static maintenance page behind it — the mechanics are in Configuring Route 53 Health Checks with DNS Failover. Cloudflare’s flattened apex has no health opinion of its own: it republishes whatever the target resolves to, healthy or not. Health awareness comes either from the target hostname’s own DNS-level failover, from proxying the apex so Cloudflare’s edge handles origin failure, or from the Load Balancing add-on. Compare the trade-off against moving the decision to the edge entirely in DNS Failover versus Edge Load Balancing.
Mail and other apex records
Neither mechanism touches record types other than A and AAAA, but the failure modes differ. On Cloudflare, MX, TXT, and CAA records at @ coexist with a flattened apex without special handling — the flattening only ever synthesizes address records. One caveat: if you proxy the apex, the published addresses are Cloudflare’s, so any SPF policy that relies on a or mx mechanisms resolving to your real mail server silently stops matching. Use explicit ip4/ip6 or include mechanisms instead, as covered in Configuring SPF Records to Prevent Spoofing.
On Route 53, the alias occupies the A and AAAA record sets at the apex; MX and TXT are separate record sets and are unaffected. The trap is a change batch built with DELETE plus CREATE for the apex during a migration: the delete lands first and the apex has no address for the duration of the propagation, so always use UPSERT.
Verification
Query both authoritatively and recursively, and read three things: the record type, the address set, and the TTL.
dig @ns1.cloudflare.com example.com A +noall +answer
dig @ns-1234.awsdns-56.org example.com A +noall +answer
dig @1.1.1.1 example.com A +noall +answer
example.com. 300 IN A 104.18.32.47
example.com. 60 IN A 13.33.12.45
A CNAME line at the apex means the mechanism is not engaged and the answer is being served by something you did not intend. Confirm no indirection survives anywhere in the chain, then watch the TTL count down to see which side owns it:
dig +trace example.com A | grep -iE 'CNAME|IN[[:space:]]+A'
dig @1.1.1.1 example.com A +noall +answer; sleep 20
dig @1.1.1.1 example.com A +noall +answer
The second answer should show a TTL about 20 lower than the first. If it resets to the full value each time, you are hitting different nodes in an anycast resolver pool rather than a single cache — repeat against 8.8.8.8 and 9.9.9.9 before concluding anything about TTL handling. Finish by proving the addresses terminate where you think they do:
curl -sI https://example.com | grep -iE '^(server|via|x-amz-cf-id|cf-ray):'
Decision guide
Pick Route 53 alias when the apex fronts an AWS resource you already run, when you want DNS-level health withdrawal without an add-on, and when apex query volume is high enough for free alias queries to matter. Pick Cloudflare flattening when the target is not an AWS resource, when you want the apex to be a hostname you can re-point at a different vendor in one API call, or when you plan to run logic in front of the origin at the edge. When the apex fronts a service that may move between clouds, flattening is the choice that does not need re-architecting later.
Troubleshooting
InvalidChangeBatch when creating the Route 53 alias. The AliasTarget.HostedZoneId is the service’s zone ID, not your hosted zone. Confirm with aws elbv2 describe-load-balancers --query 'LoadBalancers[0].CanonicalHostedZoneId', and remember it is region-specific for load balancers while CloudFront is always Z2FDTNDATAQYW2.
Apex returns NXDOMAIN shortly after enabling flattening. The target was not resolvable at the moment the first query arrived, and resolvers cached the negative answer for the zone’s SOA minimum. Confirm the target resolves with dig +short app.cdn.net A, then wait out the negative cache — the mechanics are in Negative Caching and the SOA Minimum TTL. Always publish the target before pointing the apex at it.
Apex TTL is not the value you configured. Expected on both platforms. Cloudflare clamps to the target’s TTL and forces 300 seconds when proxied; Route 53 ignores TTL entirely for alias records. If a specific TTL is a hard requirement, you need a plain A record and an automation loop that updates it, which reintroduces the staleness you were trying to avoid.
Apex works but www does not, or vice versa. They are independent records. A flattened apex does not create www, and a Route 53 alias at the apex does not cover subdomains. Publish both, and check that a zone-wide flatten_all setting has not removed a CNAME your vendor relies on for their own failover.
IPv6 clients fail while IPv4 works. On Route 53 the AAAA alias is a separate change batch that is easy to forget. On Cloudflare the apex answers AAAA only if the target publishes AAAA — verify with dig +short app.cdn.net AAAA before assuming dual-stack, and cross-check the dual-stack rollout guidance in How to Configure A vs AAAA Records for IPv6 Migration.
Frequently Asked Questions
Which mechanism gives faster failover at the apex?
Route 53 alias, when the target is an AWS resource and EvaluateTargetHealth is enabled: the authority stops handing out the unhealthy target immediately and the 60-second TTL bounds client exposure. Cloudflare flattening has no health opinion unless you proxy the apex or add Load Balancing, and its TTL is inherited from the target.
Does a Route 53 alias cost less than a normal record? Queries to an alias that points at an AWS resource are not billed, while queries to standard records are billed per million. On a high-traffic apex that is a real line item, though it only applies to in-AWS targets — an alias to another record in the same hosted zone is billed as a normal query.
Can I point a Cloudflare flattened apex at an AWS load balancer?
Yes. Flattening resolves any public hostname, so an ALB DNS name works fine. You lose EvaluateTargetHealth, and you inherit the ALB’s 60-second record TTL, which is short enough that the practical difference is small.
Will either mechanism break MX or TXT records at the apex?
No. Both synthesize only A and AAAA records, and other types at @ are served normally. The one real hazard is proxying the apex on Cloudflare, which changes the published addresses and therefore breaks SPF policies written with a or mx mechanisms.
How hard is it to move the apex from Route 53 to Cloudflare? The alias has no equivalent outside AWS, so you replace it with a flattened CNAME pointing at the target’s public hostname — straightforward for CloudFront or an ALB, since both have stable DNS names. Lower the surrounding TTLs first, run both authorities in parallel, and only then move the delegation.
Related
- CNAME Flattening vs ALIAS Records at the Apex Domain
- Configuring Route 53 Health Checks with DNS Failover
- Managing DNS Zones as Code with Terraform
- Negative Caching and the SOA Minimum TTL
Back to CNAME Flattening Explained