Portal | Level: L2: Operations | Topics: AWS Route 53, DNS, Cloud Deep Dive | Domain: Cloud
AWS Route 53 - Primer¶
Why This Matters¶
Route 53 is the DNS backbone of every AWS deployment. It is the first thing that touches every request to your infrastructure and the last thing anyone thinks about until it breaks. A misconfigured hosted zone means your entire application is unreachable. A botched migration means hours of downtime waiting for TTLs to expire. A missing health check means your failover does not fail over.
DNS is invisible when it works and catastrophic when it does not. Route 53 adds AWS-specific concepts on top of standard DNS — alias records, routing policies, health checks, resolvers — that you must understand to build reliable, multi-region architectures.
Core Concepts¶
1. Hosted Zones¶
A hosted zone is a container for DNS records for a single domain. Route 53 gives you two types.
Public hosted zones resolve queries from the internet. When you create one, Route 53 assigns four name servers. You configure your domain registrar to delegate to these NS records.
Private hosted zones resolve queries only from within associated VPCs. They are invisible to the public internet.
# Create a public hosted zone
aws route53 create-hosted-zone \
--name example.com \
--caller-reference "$(date +%s)" \
--query 'HostedZone.Id' --output text
# Returns: /hostedzone/Z1234567890ABC
# Create a private hosted zone associated with a VPC
aws route53 create-hosted-zone \
--name internal.example.com \
--caller-reference "$(date +%s)" \
--vpc VPCRegion=us-east-1,VPCId=vpc-abc123 \
--hosted-zone-config PrivateZone=true
# List all hosted zones
aws route53 list-hosted-zones \
--query 'HostedZones[].{Name:Name,Id:Id,Private:Config.PrivateZone,Records:ResourceRecordSetCount}'
# Get the NS records for your zone (needed for registrar delegation)
aws route53 get-hosted-zone --id Z1234567890ABC \
--query 'DelegationSet.NameServers'
Each public hosted zone costs $0.50/month. You are also charged per million queries ($0.40 for the first billion). Private hosted zones cost the same per zone but queries are priced differently.
2. Record Types¶
Route 53 supports all standard DNS record types plus the AWS-specific alias record.
| Type | Purpose | Example |
|---|---|---|
| A | Maps name to IPv4 address | app.example.com -> 203.0.113.10 |
| AAAA | Maps name to IPv6 address | app.example.com -> 2001:db8::1 |
| CNAME | Maps name to another name | www.example.com -> app.example.com |
| MX | Mail exchange servers | example.com -> 10 mail.example.com |
| NS | Authoritative name servers | example.com -> ns-1234.awsdns-56.org |
| SOA | Start of authority (zone metadata) | Serial number, refresh intervals |
| TXT | Arbitrary text (SPF, DKIM, verification) | "v=spf1 include:_spf.google.com ~all" |
| SRV | Service location (port, weight, priority) | _sip._tcp.example.com -> 10 60 5060 sip.example.com |
| Alias | AWS-specific: maps to AWS resources | example.com -> CloudFront, ALB, S3 |
# Create an A record
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "app.example.com",
"Type": "A",
"TTL": 300,
"ResourceRecords": [{"Value": "203.0.113.10"}]
}
}]
}'
# Create an MX record for email
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "example.com",
"Type": "MX",
"TTL": 3600,
"ResourceRecords": [
{"Value": "10 mail1.example.com"},
{"Value": "20 mail2.example.com"}
]
}
}]
}'
# Create a TXT record (SPF for email)
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "example.com",
"Type": "TXT",
"TTL": 3600,
"ResourceRecords": [{"Value": "\"v=spf1 include:_spf.google.com ~all\""}]
}
}]
}'
3. Alias Records vs CNAME¶
Fun fact: Alias records are a Route 53 proprietary extension — they do not exist in standard DNS. AWS invented them to solve the zone apex CNAME problem. Other DNS providers have similar solutions: Cloudflare calls them "CNAME flattening," DNSimple calls them "ALIAS records." None of these are interoperable.
This is one of the most important Route 53 concepts. Standard DNS does not allow a CNAME at the zone apex (the bare domain like example.com). Route 53's alias record solves this.
CNAME: Alias:
├── Cannot be at zone apex ├── CAN be at zone apex
├── Charges for each query ├── FREE for queries to AWS resources
├── Standard DNS, any target ├── AWS-only targets (ALB, CF, S3, etc.)
├── Returns target name ├── Returns target IP directly
└── Has its own TTL └── Uses target's TTL
Alias targets include: - CloudFront distributions - Elastic Load Balancers (ALB, NLB, CLB) - S3 website endpoints - API Gateway custom domains - VPC interface endpoints - Another Route 53 record in the same hosted zone - Elastic Beanstalk environments - Global Accelerator
# Alias record pointing to an ALB (at zone apex — impossible with CNAME)
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "example.com",
"Type": "A",
"AliasTarget": {
"HostedZoneId": "Z35SXDOTRQ7X7K",
"DNSName": "my-alb-1234567890.us-east-1.elb.amazonaws.com",
"EvaluateTargetHealth": true
}
}
}]
}'
# Alias record pointing to CloudFront
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "cdn.example.com",
"Type": "A",
"AliasTarget": {
"HostedZoneId": "Z2FDTNDATAQYW2",
"DNSName": "d1234567890.cloudfront.net",
"EvaluateTargetHealth": false
}
}
}]
}'
The HostedZoneId in the alias target is not your zone — it is the AWS service's zone ID. Each service has a fixed zone ID per region. CloudFront always uses Z2FDTNDATAQYW2. ALBs vary by region. Check the AWS documentation for the correct values.
4. Routing Policies¶
Route 53 supports seven routing policies that control how queries are answered.
Simple routing: One record, one or more values. If multiple values, Route 53 returns all of them in random order. No health checks.
# Simple routing — multiple IPs, random order
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "app.example.com",
"Type": "A",
"TTL": 60,
"ResourceRecords": [
{"Value": "203.0.113.10"},
{"Value": "203.0.113.11"}
]
}
}]
}'
Weighted routing: Distribute traffic by percentage. Useful for canary deploys and gradual migrations.
# Weighted: 90% to production, 10% to canary
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch '{
"Changes": [
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "app.example.com",
"Type": "A",
"SetIdentifier": "production",
"Weight": 90,
"TTL": 60,
"ResourceRecords": [{"Value": "203.0.113.10"}],
"HealthCheckId": "hc-prod-123"
}
},
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "app.example.com",
"Type": "A",
"SetIdentifier": "canary",
"Weight": 10,
"TTL": 60,
"ResourceRecords": [{"Value": "203.0.113.20"}],
"HealthCheckId": "hc-canary-456"
}
}
]
}'
Latency-based routing: Route to the region with the lowest latency for the querying resolver. Route 53 maintains a latency database between AWS regions and public resolver networks.
Failover routing: Active-passive. Primary gets all traffic unless its health check fails, then secondary takes over.
# Failover: primary in us-east-1, secondary in us-west-2
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch '{
"Changes": [
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "app.example.com",
"Type": "A",
"SetIdentifier": "primary",
"Failover": "PRIMARY",
"TTL": 60,
"ResourceRecords": [{"Value": "203.0.113.10"}],
"HealthCheckId": "hc-primary-789"
}
},
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "app.example.com",
"Type": "A",
"SetIdentifier": "secondary",
"Failover": "SECONDARY",
"TTL": 60,
"ResourceRecords": [{"Value": "198.51.100.10"}]
}
}
]
}'
Geolocation routing: Route based on the geographic location of the user. You map continents, countries, or US states to specific record sets. Always create a default record for locations you have not explicitly mapped.
Geoproximity routing: Route based on geographic location with an adjustable bias. Increase the bias to attract more traffic to a region, decrease to push traffic away. Requires traffic flow.
Multivalue answer routing: Returns up to eight healthy records in response to a query. Each record has its own health check. Similar to simple routing but with health check integration — unhealthy records are excluded from responses.
5. Health Checks¶
Health checks monitor endpoints and drive routing decisions. Route 53 health checkers run from multiple regions worldwide.
Endpoint health checks monitor an IP or domain name on a specific port and path.
# Create an HTTP health check
aws route53 create-health-check \
--caller-reference "app-prod-$(date +%s)" \
--health-check-config '{
"IPAddress": "203.0.113.10",
"Port": 443,
"Type": "HTTPS",
"ResourcePath": "/health",
"RequestInterval": 10,
"FailureThreshold": 3,
"EnableSNI": true
}'
# Health check with string matching (response body must contain this string)
aws route53 create-health-check \
--caller-reference "app-deep-$(date +%s)" \
--health-check-config '{
"IPAddress": "203.0.113.10",
"Port": 443,
"Type": "HTTPS_STR_MATCH",
"ResourcePath": "/health",
"SearchString": "\"status\":\"ok\"",
"RequestInterval": 30,
"FailureThreshold": 3
}'
Calculated health checks combine the results of other health checks. You can set thresholds like "healthy if at least 2 of 3 child checks are healthy."
# Calculated health check: healthy if at least 2 of 3 child checks pass
aws route53 create-health-check \
--caller-reference "app-calculated-$(date +%s)" \
--health-check-config '{
"Type": "CALCULATED",
"ChildHealthChecks": ["hc-id-1", "hc-id-2", "hc-id-3"],
"HealthThreshold": 2
}'
CloudWatch alarm health checks integrate with CloudWatch alarms. The health check status mirrors the alarm state.
# Health check based on a CloudWatch alarm
aws route53 create-health-check \
--caller-reference "app-cw-$(date +%s)" \
--health-check-config '{
"Type": "CLOUDWATCH_METRIC",
"AlarmIdentifier": {
"Region": "us-east-1",
"Name": "app-error-rate-high"
},
"InsufficientDataHealthStatus": "LastKnownStatus"
}'
Health check pricing: $0.50/month for AWS endpoints, $0.75/month for non-AWS endpoints. String matching and HTTPS add extra. Fast interval (10s vs 30s) doubles the cost.
6. TTL Management¶
TTL (Time to Live) controls how long resolvers cache your records. Getting TTL right is critical for migrations, failover speed, and query costs.
TTL tradeoffs:
Long TTL (3600s / 1 hour): Short TTL (60s / 1 minute):
├── Fewer queries → lower cost ├── More queries → higher cost
├── Less load on Route 53 ├── Faster failover response
├── Slower failover ├── Faster migration cutover
└── Stale records persist longer └── More responsive to changes
Best practices: - Normal operations: 300s (5 minutes) is a reasonable default - Before a migration: lower TTL to 60s at least 48 hours before the change (wait for old TTL to expire everywhere) - During failover: 60s or less for records that need fast switching - Static records (MX, TXT): 3600s or higher - Alias records: TTL is inherited from the target, you cannot set it
7. Domain Registration¶
Route 53 can also act as a domain registrar. You can register new domains or transfer existing ones.
# Check domain availability
aws route53domains check-domain-availability \
--domain-name example.com \
--query 'Availability'
# Register a domain (creates the hosted zone automatically)
aws route53domains register-domain \
--domain-name example.com \
--duration-in-years 1 \
--admin-contact '{...}' \
--registrant-contact '{...}' \
--tech-contact '{...}' \
--auto-renew
# Transfer a domain to Route 53
aws route53domains transfer-domain \
--domain-name example.com \
--duration-in-years 1 \
--auth-code "TRANSFER_AUTH_CODE" \
--admin-contact '{...}'
Registration and transfer operations must be run against the us-east-1 region regardless of where your resources live.
Default trap: Route 53 query logging, DNSSEC key signing, and domain registration all require
us-east-1even if all your infrastructure is in another region. If your CLI default region isus-west-2and these commands fail with "not available in this region," this is why.
8. DNS Failover Architecture¶
A common multi-region failover setup using Route 53:
Route 53
(failover)
/ \
PRIMARY SECONDARY
us-east-1 us-west-2
| |
ALB ALB
/ | \ / | \
EC2 EC2 EC2 EC2 EC2 EC2
| |
RDS Primary RDS Read Replica
(multi-AZ) (cross-region)
The health check monitors the primary ALB. When it fails, Route 53 returns the secondary ALB's IP. The secondary region's read replica can be promoted to a standalone primary.
9. Private Hosted Zones and Split-View DNS¶
Private hosted zones resolve only within associated VPCs. This enables split-view DNS: the same domain name resolves to different IPs depending on whether the query comes from inside or outside the VPC.
# Associate a private hosted zone with additional VPCs
aws route53 associate-vpc-with-hosted-zone \
--hosted-zone-id Z0987654321XYZ \
--vpc VPCRegion=us-west-2,VPCId=vpc-def456
# List VPC associations for a private hosted zone
aws route53 get-hosted-zone --id Z0987654321XYZ \
--query 'VPCs[].{Region:VPCRegion,VPC:VPCId}'
Split-view example: api.example.com resolves to the public ALB from the internet but to a private IP from within the VPC, bypassing the load balancer for internal service-to-service calls.
10. DNSSEC Signing¶
Route 53 supports DNSSEC signing for public hosted zones. DNSSEC adds cryptographic signatures to DNS responses, preventing DNS spoofing attacks.
# Enable DNSSEC signing on a hosted zone
aws route53 create-key-signing-key \
--hosted-zone-id Z1234567890ABC \
--name example-ksk \
--key-management-service-arn arn:aws:kms:us-east-1:123456789012:key/key-id \
--status ACTIVE
aws route53 enable-hosted-zone-dnssec \
--hosted-zone-id Z1234567890ABC
# After enabling, you must create a DS record at the parent zone (your registrar)
# Route 53 provides the DS record value
aws route53 get-dnssec --hosted-zone-id Z1234567890ABC \
--query 'KeySigningKeys[].{Name:Name,DSRecord:DSRecord}'
DNSSEC is powerful but adds operational complexity. If the KMS key is deleted or the signing chain breaks, your domain becomes unresolvable for DNSSEC-validating resolvers.
11. Route 53 Resolver¶
The Route 53 Resolver handles DNS resolution for VPCs. By default, it resolves public DNS names and private hosted zone records. For hybrid environments (on-premises to AWS), you need Resolver endpoints.
Inbound endpoints: Allow on-premises DNS servers to forward queries to Route 53 Resolver. On-prem resolvers send queries to the inbound endpoint IP in your VPC.
Outbound endpoints: Allow VPC resources to forward queries to on-premises DNS servers. You create forwarding rules that match specific domains and send them out.
# Create an inbound endpoint (on-prem → AWS)
aws route53resolver create-resolver-endpoint \
--creator-request-id "inbound-$(date +%s)" \
--name "on-prem-to-aws" \
--security-group-ids sg-abc123 \
--direction INBOUND \
--ip-addresses SubnetId=subnet-priv1a,Ip=10.0.1.10 SubnetId=subnet-priv1b,Ip=10.0.2.10
# Create an outbound endpoint (AWS → on-prem)
aws route53resolver create-resolver-endpoint \
--creator-request-id "outbound-$(date +%s)" \
--name "aws-to-on-prem" \
--security-group-ids sg-def456 \
--direction OUTBOUND \
--ip-addresses SubnetId=subnet-priv1a SubnetId=subnet-priv1b
# Create a forwarding rule: corp.internal → on-prem DNS
aws route53resolver create-resolver-rule \
--creator-request-id "fwd-corp-$(date +%s)" \
--name "forward-corp-internal" \
--rule-type FORWARD \
--domain-name "corp.internal" \
--resolver-endpoint-id rslvr-out-abc123 \
--target-ips "Ip=10.100.1.53,Port=53" "Ip=10.100.2.53,Port=53"
# Associate the rule with a VPC
aws route53resolver associate-resolver-rule \
--resolver-rule-id rslvr-rule-abc123 \
--vpc-id vpc-abc123
12. Traffic Flow¶
Traffic flow is a visual editor for complex routing configurations. It lets you chain routing policies (e.g., geolocation first, then weighted within each region) into a traffic policy that you version and attach to records.
# Create a traffic policy (JSON document)
aws route53 create-traffic-policy \
--name "multi-region-weighted" \
--document '{
"AWSPolicyFormatVersion": "2015-10-01",
"RecordType": "A",
"Endpoints": {
"us-east-alb": {"Type": "elastic-load-balancer", "Value": "my-alb-east.elb.amazonaws.com"},
"us-west-alb": {"Type": "elastic-load-balancer", "Value": "my-alb-west.elb.amazonaws.com"}
},
"Rules": {
"geo-rule": {
"RuleType": "geo",
"Locations": [
{"EndpointReference": "us-east-alb", "IsDefault": true},
{"EndpointReference": "us-west-alb", "Continent": "NA"}
]
}
},
"StartRule": "geo-rule"
}'
# Attach the policy to a DNS record
aws route53 create-traffic-policy-instance \
--hosted-zone-id Z1234567890ABC \
--name app.example.com \
--ttl 60 \
--traffic-policy-id policy-id \
--traffic-policy-version 1
Traffic policies cost $50/month per policy record. They are worth it for complex multi-region setups but overkill for simple failover.
13. Query Logging¶
Route 53 can log all DNS queries to CloudWatch Logs. Useful for debugging resolution issues, security auditing, and understanding traffic patterns.
# Create a query logging configuration
aws route53 create-query-logging-config \
--hosted-zone-id Z1234567890ABC \
--cloud-watch-logs-log-group-arn \
"arn:aws:logs:us-east-1:123456789012:log-group:/aws/route53/example.com"
# Query the logs (example: find all queries for a specific name)
aws logs filter-log-events \
--log-group-name /aws/route53/example.com \
--filter-pattern "app.example.com" \
--start-time $(date -d '1 hour ago' +%s000) \
--query 'events[].message'
Query logs include: timestamp, hosted zone ID, query name, query type, response code, resolver IP, and the protocol used. Logs must be in us-east-1 for public hosted zones.
Key Takeaways¶
- Use alias records instead of CNAME whenever the target is an AWS resource — they are free, work at the zone apex, and support health check evaluation
- Lower TTLs before any migration or failover configuration change, and wait for the old TTL to expire before making the switch
- Health checks drive routing decisions — without them, failover and weighted routing are just static configurations
- Private hosted zones require explicit VPC association — they do not automatically resolve in all VPCs
- DNSSEC protects against DNS spoofing but introduces operational risk if the signing chain breaks
- Route 53 Resolver endpoints are required for hybrid DNS (on-prem to AWS and back)
- Query logging is invaluable for debugging but must be configured in us-east-1 for public zones
- Traffic flow is expensive ($50/month per record) — use it only for routing logic that cannot be expressed with individual routing policies
Wiki Navigation¶
Prerequisites¶
- Cloud Ops Basics (Topic Pack, L1)
- DNS Deep Dive (Topic Pack, L1)
Related Content¶
- AWS CloudWatch (Topic Pack, L2) — Cloud Deep Dive
- AWS Devops Flashcards (CLI) (flashcard_deck, L1) — Cloud Deep Dive
- AWS EC2 (Topic Pack, L1) — Cloud Deep Dive
- AWS ECS (Topic Pack, L2) — Cloud Deep Dive
- AWS General Flashcards (CLI) (flashcard_deck, L1) — Cloud Deep Dive
- AWS IAM (Topic Pack, L1) — Cloud Deep Dive
- AWS Lambda (Topic Pack, L2) — Cloud Deep Dive
- AWS Networking (Topic Pack, L1) — Cloud Deep Dive
- AWS S3 Deep Dive (Topic Pack, L1) — Cloud Deep Dive
- Azure Flashcards (CLI) (flashcard_deck, L1) — Cloud Deep Dive
Pages that link here¶
- AWS CloudWatch
- AWS EC2
- AWS ECS
- AWS IAM
- AWS Lambda
- AWS Networking
- AWS Route 53
- AWS S3 Deep Dive
- Anti-Primer: AWS Route53
- Certification Prep: AWS SAA — Solutions Architect Associate
- Cloud Ops Basics
- DNS Deep Dive
- DNS Resolution Taking 5+ Seconds Intermittently
- Symptoms
- Symptoms: DNS Looks Broken, TLS Is Expired, Fix Is in Cert-Manager