title: “Route 53 + ACM + CloudFront Custom Domain — Cheat Sheet” created: 2026-10-28 updated: 2026-10-28 type: cheat-sheet tags:
- aws
- certification
- cheat-sheet
- route53
- acm
- cloudfront
- dns
- tls
- clf-c02
Route 53 + ACM + CloudFront Custom Domain — Cheat Sheet
Quick reference for putting a custom domain in front of a CloudFront
distribution: register a domain in Route 53, issue an ACM cert, wire DNS. Built
from the real mimifasol.com deployment — full procedure in the runbook
[[deploy-custom-domain-route53-acm-cloudfront]], theory in
[[route53-alias-vs-cname]] and [[route53-dns-records]].
Acronyms & Terms
| Acronym / term | Stands for | What it is / its role | Where you meet it |
|---|---|---|---|
| DNS | Domain Name System | The phone book: turns a name (mimifasol.com) into an address (an IP). | Everywhere; the whole point of Route 53. |
| Route 53 | (AWS’s 53 = DNS port) | AWS’s managed DNS + domain registrar. Holds your records, can buy domains. | Console → Route 53. |
| Hosted zone | — | The container of all DNS records for one domain. Auto-created when you register in Route 53. | Route 53 → Hosted zones → mimifasol.com. |
| Apex | zone apex / root / naked domain | The domain with nothing in front: mimifasol.com (vs docs.mimifasol.com). Always carries NS + SOA. | The record with an empty Name in the zone. |
| Sub-domain | — | A label in front of the apex: docs., benoit., www.. | A record whose Name is docs, etc. |
| A | Address (IPv4) | Maps a name → an IPv4 address (18.66.112.6). | Record Type = A. |
| AAAA | “quad-A” (IPv6) | Maps a name → an IPv6 address (2600:9000:...). Add it so IPv6 clients resolve too. | Record Type = AAAA. |
| CNAME | Canonical Name | “This name is an alias for another name.” Illegal at the apex (coexists with nothing). | Record Type = CNAME (sub-domains only). |
| Alias | — | Route 53-only record that looks like an A/AAAA but points at an AWS resource (CloudFront, ELB, S3…). Legal at the apex; free to resolve. | Record with Alias = ON. |
| NS | Name Server | The 4 authoritative servers for the zone. Mandatory at the apex. | Auto-created in the zone. |
| SOA | Start Of Authority | Zone metadata (primary NS, refresh timers). Mandatory, one per zone. | Auto-created in the zone. |
| TTL | Time To Live | How long resolvers cache a record (seconds). Alias records inherit the target’s TTL (no manual field). | On non-alias records. |
| ACM | AWS Certificate Manager | Issues/renews free public TLS certs. For CloudFront the cert must be in us-east-1. | Console → Certificate Manager (region N. Virginia). |
| SAN | Subject Alternative Name | Extra names a cert covers beyond the primary. We used *.mimifasol.com. | The cert’s domain list. |
| Wildcard | *.domain | A SAN covering one level of sub-domains (docs., benoit.) — not the apex, not nested. | The *.mimifasol.com SAN. |
| TLS / SSL | Transport Layer Security | Encrypts HTTPS; the cert proves the server’s identity. | The padlock; CloudFront “Custom SSL certificate”. |
| CloudFront | — | AWS CDN; terminates HTTPS and serves cached content from edges. | The distribution (ID in private config). |
| Alternate domain name (CNAME) | — | The custom hostnames a distribution accepts (confusingly labelled “CNAMEs” in the console — unrelated to CNAME records). | Distribution → Settings → Alternate domain names. |
| Distribution domain | — | The *.cloudfront.net name CloudFront assigns (e.g. dXXXXXXXXXXXXX.cloudfront.net). Not derived from the distribution ID. | get-distribution … DomainName; the Alias target. |
| OAC | Origin Access Control | Lets only CloudFront read the private S3 origin. (Phase 1 concept.) | See [[cloudfront-s3-cli-cheat-sheet]]. |
Route 53 Record Properties (the fields you set)
When you create a record in a hosted zone, these are the attributes to understand. Shown for the apex-Alias-to-CloudFront case we built.
| Property | What it means | Values / notes | Our value |
|---|---|---|---|
| Record name | The hostname, relative to the zone | Empty = apex; docs = docs.mimifasol.com | empty + docs |
| Record type | What kind of answer | A, AAAA, CNAME, MX, TXT, NS, SOA… | A and AAAA |
| Alias | Toggle: is this a Route 53 Alias? | ON → points at an AWS resource; OFF → you type a value/IP | ON |
| Route traffic to (Alias ON) | Which AWS resource the alias targets | CloudFront, ELB, S3 website, API GW, or another R53 record | Alias to CloudFront distribution |
| Value/Route traffic to (Alias OFF) | The raw record data | An IP (A/AAAA), a hostname (CNAME), text (TXT)… | n/a (we used Alias) |
| TTL | Cache lifetime in seconds | Editable for non-alias; hidden/managed for Alias | n/a (Alias) |
| Routing policy | How R53 chooses among multiple records | Simple, Weighted, Latency, Failover, Geolocation, Multi-value | Simple |
| Evaluate target health | Alias-only: skip this target if unhealthy | Yes/No; off for a single static CloudFront target | No |
Apex rule in one line: apex (
mimifasol.com) → Alias A/AAAA (CNAME is illegal there); sub-domain (docs.) → Alias or CNAME (we used Alias).
The one magic constant
When creating an Alias-to-CloudFront via CLI/JSON, the AliasTarget needs
CloudFront’s fixed global hosted-zone ID:
Z2FDTNDATAQYW2
Same value for every CloudFront distribution worldwide — it tells Route 53 “the target is CloudFront”. (In the console you don’t type it; the dropdown handles it.)
Quick Reference — Commands
| Command | Description |
|---|---|
aws route53domains check-domain-availability --domain-name X --region us-east-1 | Is the domain free? (route53domains is us-east-1 only) |
aws route53domains list-domains --region us-east-1 | List registered domains |
aws route53 list-hosted-zones-by-name --dns-name X | Find a zone’s ID |
aws acm request-certificate --domain-name X --subject-alternative-names '*.X' --validation-method DNS --region us-east-1 | Request a public cert (us-east-1 for CloudFront) |
aws acm describe-certificate --certificate-arn ARN --region us-east-1 --query 'Certificate.Status' | Cert status: PENDING_VALIDATION → ISSUED |
aws acm wait certificate-validated --certificate-arn ARN --region us-east-1 | Block until issued |
aws cloudfront get-distribution --id ID --query 'Distribution.DomainName' | The assigned *.cloudfront.net domain (the Alias target) |
aws cloudfront wait distribution-deployed --id ID | Block until the distribution finishes deploying |
aws route53 change-resource-record-sets --hosted-zone-id Z… --change-batch file://batch.json | Create/update records (Alias A/AAAA) |
aws route53 list-resource-record-sets --hosted-zone-id Z… | List the zone’s records |
Common Patterns
1. Request + validate a cert (us-east-1)
CERT_ARN=$(aws acm request-certificate \
--domain-name mimifasol.com \
--subject-alternative-names '*.mimifasol.com' \
--validation-method DNS --region us-east-1 \
--query 'CertificateArn' --output text)
# In the ACM console: "Create records in Route 53" (writes the validation CNAME).
aws acm wait certificate-validated --certificate-arn "$CERT_ARN" --region us-east-1
2. Apex + sub-domain Alias → CloudFront (A + AAAA)
DIST_ID=$(grep DIST_ID ~/work/skills/bhu-docs/public/.deploy-state | cut -d= -f2)
ZONE_ID=$(aws route53 list-hosted-zones-by-name --dns-name mimifasol.com \
--query 'HostedZones[0].Id' --output text)
CF_DOMAIN=$(aws cloudfront get-distribution --id "$DIST_ID" \
--query 'Distribution.DomainName' --output text) # e.g. dXXXXXXXXXXXXX.cloudfront.net
cat > /tmp/alias.json <<JSON
{"Changes":[
{"Action":"UPSERT","ResourceRecordSet":{"Name":"mimifasol.com","Type":"A",
"AliasTarget":{"HostedZoneId":"Z2FDTNDATAQYW2","DNSName":"$CF_DOMAIN","EvaluateTargetHealth":false}}},
{"Action":"UPSERT","ResourceRecordSet":{"Name":"mimifasol.com","Type":"AAAA",
"AliasTarget":{"HostedZoneId":"Z2FDTNDATAQYW2","DNSName":"$CF_DOMAIN","EvaluateTargetHealth":false}}},
{"Action":"UPSERT","ResourceRecordSet":{"Name":"docs.mimifasol.com","Type":"A",
"AliasTarget":{"HostedZoneId":"Z2FDTNDATAQYW2","DNSName":"$CF_DOMAIN","EvaluateTargetHealth":false}}},
{"Action":"UPSERT","ResourceRecordSet":{"Name":"docs.mimifasol.com","Type":"AAAA",
"AliasTarget":{"HostedZoneId":"Z2FDTNDATAQYW2","DNSName":"$CF_DOMAIN","EvaluateTargetHealth":false}}}
]}
JSON
aws route53 change-resource-record-sets --hosted-zone-id "$ZONE_ID" \
--change-batch file:///tmp/alias.json
3. Verify end-to-end (no dig needed)
getent ahosts mimifasol.com # resolves to CloudFront IPs (A + AAAA)
curl -sI http://mimifasol.com | head -n1 # 301 -> HTTPS
curl -sI https://mimifasol.com | head -n1 # HTTP/2 200
echo | openssl s_client -connect mimifasol.com:443 -servername mimifasol.com 2>/dev/null \
| openssl x509 -noout -subject -ext subjectAltName # CN + SAN match
Tips & Tricks
- ACM region trap: a CloudFront cert MUST be in
us-east-1, or it won’t appear in the distribution’s cert dropdown — no matter where the bucket is. - Apex CNAME is illegal (RFC 1034): the apex already has NS + SOA. Use an Alias at the apex; it looks like an A record but points at the AWS target.
- Add both A and AAAA: miss AAAA and IPv6-only clients can’t reach you. Alias resolution to an AWS target is free.
- DNS validation auto-renews: leave the
_<hash>validation CNAME in the zone forever — ACM re-checks it to renew the cert silently. - CloudFront domain ≠ distribution ID: it’s assigned
(
dXXXXXXXXXXXXX.cloudfront.net). Always read it withget-distribution. - Two legs per custom name: (1) add it as an alternate domain name on the distribution and (2) create the DNS record. One without the other fails.
route53domainsis us-east-1 only — the registrar API lives nowhere else.
Publicly Observable vs Private
A public hosted zone answers DNS queries from anyone — that is its job. But DNS resolution (public) and the Route 53 management API (private) are two different planes. Know which is which.
| Anyone on the internet CAN | Only an authenticated principal CAN |
|---|---|
| Resolve a known name (A, AAAA, CNAME, MX, TXT, NS, SOA, CAA) | list-resource-record-sets — dump all records in a zone |
See where the apex / docs. point (CloudFront IPs) | See the hosted-zone ID, distribution ID, OAC ID, ARNs |
| Read NS + SOA for the domain | Create / change / delete records |
Confirm a guessed sub-domain exists (www., api.) | Query the route53 / route53domains APIs |
Key limits that protect you:
- No “list all” over DNS. DNS is lookup-by-name; you cannot ask a public resolver to enumerate a zone. Attackers must guess names (dictionary / brute-force), they cannot dump them.
- Zone transfer (AXFR) is blocked. AXFR is the only protocol that dumps a
whole zone — and Route 53 refuses public AXFR (only its own name servers
do it internally). So
dig AXFR …fails by design. - The management API needs SigV4-signed credentials. Plain
curl/digcan never reachlist-resource-record-setsor see IDs/ARNs. That is why redacting account/resource IDs from published docs matters — those IDs are not otherwise discoverable from the outside.
See what the public can observe (DNS-over-HTTPS, no AWS creds)
# Google DoH (JSON). type: 1=A, 28=AAAA, 2=NS, 6=SOA, 5=CNAME, 257=CAA
curl -s 'https://dns.google/resolve?name=mimifasol.com&type=A' | jq
curl -s 'https://dns.google/resolve?name=docs.mimifasol.com&type=A' | jq
curl -s 'https://dns.google/resolve?name=mimifasol.com&type=NS' | jq
# Cloudflare DoH (needs the accept header)
curl -s -H 'accept: application/dns-json' \
'https://cloudflare-dns.com/dns-query?name=mimifasol.com&type=AAAA' | jq
# AXFR (full-zone dump) — fails against Route 53 by design:
dig AXFR mimifasol.com @<one-of-the-zone-NS> # -> Transfer failed
Takeaway: your apex/
docs.records being resolvable is normal and not a leak — that is public DNS working. The private plane (IDs, ARNs, the ability to list/modify) stays behind authenticated AWS APIs.
Exam Gotchas (CLF-C02)
- Apex → Alias, never CNAME. Classic trap.
- CloudFront cert → us-east-1 only.
- Alias to an AWS resource is free; CNAME lookups are billed queries.
- A record = IPv4, AAAA = IPv6, CNAME = name→name, Alias = name→AWS resource.
- Hosted zone = $0.50/month; public ACM cert = free;
.com≈ $14–16/year. - Public DNS resolution ≠ the Route 53 API: resolvers answer known names; only signed API calls can list/modify records. AXFR is blocked on Route 53.
See Also
- [[deploy-custom-domain-route53-acm-cloudfront]]
- [[route53-alias-vs-cname]]
- [[route53-dns-records]]
- [[cloudfront-s3-cli-cheat-sheet]]
- [[cloudfront-custom-domain-vs-default-url]]
- [[deploy-static-site-s3-cloudfront-oac]]