Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help


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 / termStands forWhat it is / its roleWhere you meet it
DNSDomain Name SystemThe 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.
Apexzone apex / root / naked domainThe 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.
AAddress (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.
CNAMECanonical 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.
NSName ServerThe 4 authoritative servers for the zone. Mandatory at the apex.Auto-created in the zone.
SOAStart Of AuthorityZone metadata (primary NS, refresh timers). Mandatory, one per zone.Auto-created in the zone.
TTLTime To LiveHow long resolvers cache a record (seconds). Alias records inherit the target’s TTL (no manual field).On non-alias records.
ACMAWS Certificate ManagerIssues/renews free public TLS certs. For CloudFront the cert must be in us-east-1.Console → Certificate Manager (region N. Virginia).
SANSubject Alternative NameExtra names a cert covers beyond the primary. We used *.mimifasol.com.The cert’s domain list.
Wildcard*.domainA SAN covering one level of sub-domains (docs., benoit.) — not the apex, not nested.The *.mimifasol.com SAN.
TLS / SSLTransport Layer SecurityEncrypts 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.
OACOrigin Access ControlLets 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.

PropertyWhat it meansValues / notesOur value
Record nameThe hostname, relative to the zoneEmpty = apex; docs = docs.mimifasol.comempty + docs
Record typeWhat kind of answerA, AAAA, CNAME, MX, TXT, NS, SOA…A and AAAA
AliasToggle: is this a Route 53 Alias?ON → points at an AWS resource; OFF → you type a value/IPON
Route traffic to (Alias ON)Which AWS resource the alias targetsCloudFront, ELB, S3 website, API GW, or another R53 recordAlias to CloudFront distribution
Value/Route traffic to (Alias OFF)The raw record dataAn IP (A/AAAA), a hostname (CNAME), text (TXT)…n/a (we used Alias)
TTLCache lifetime in secondsEditable for non-alias; hidden/managed for Aliasn/a (Alias)
Routing policyHow R53 chooses among multiple recordsSimple, Weighted, Latency, Failover, Geolocation, Multi-valueSimple
Evaluate target healthAlias-only: skip this target if unhealthyYes/No; off for a single static CloudFront targetNo

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

CommandDescription
aws route53domains check-domain-availability --domain-name X --region us-east-1Is the domain free? (route53domains is us-east-1 only)
aws route53domains list-domains --region us-east-1List registered domains
aws route53 list-hosted-zones-by-name --dns-name XFind a zone’s ID
aws acm request-certificate --domain-name X --subject-alternative-names '*.X' --validation-method DNS --region us-east-1Request 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-1Block 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 IDBlock until the distribution finishes deploying
aws route53 change-resource-record-sets --hosted-zone-id Z… --change-batch file://batch.jsonCreate/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 with get-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.
  • route53domains is 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 CANOnly 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 domainCreate / 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 / dig can never reach list-resource-record-sets or 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]]