Home / AWS

AWSHow-toBeginner

Amazon Route 53 basics: hosted zones, records and safe DNS changes from the CLI

Learn Route 53 by doing - find a hosted zone, read its records, create and update A, CNAME and alias records, test the answer with dig and fix the usual reasons a change doesn't show up.

Deepak Bhardwaj5 min read

Applies toAmazon Route 53 public hosted zones, AWS CLI v2

The goal

You've been asked to point app.contoso.com at a new server or load balancer. This post covers the words you need, how to read what's there, how to make the change and how to prove it worked.

The basic terms

Term Meaning
Hosted zone The container for one domain's DNS records
A record A name that points to an IPv4 address
AAAA record A name that points to an IPv6 address
CNAME A name that points to another name
Alias A Route 53 feature: a name that points to an AWS resource such as a load balancer
TTL How many seconds resolvers may cache the answer
NS records The name servers that hold the zone

Step 1: find the zone and read its records

aws route53 list-hosted-zones \
  --query "HostedZones[].{Name:Name,Id:Id,Private:Config.PrivateZone}" --output table

aws route53 list-resource-record-sets \
  --hosted-zone-id Z0123456789ABCDEFGHIJ \
  --query "ResourceRecordSets[].{Name:Name,Type:Type,TTL:TTL,Value:ResourceRecords[0].Value,Alias:AliasTarget.DNSName}" \
  --output table

Look at one name only:

aws route53 list-resource-record-sets \
  --hosted-zone-id Z0123456789ABCDEFGHIJ \
  --query "ResourceRecordSets[?Name=='app.contoso.com.']"

The trailing dot matters here. Route 53 stores names as app.contoso.com..

Step 2: back up before you change anything

aws route53 list-resource-record-sets \
  --hosted-zone-id Z0123456789ABCDEFGHIJ > zone-backup-$(date +%F).json

That file gives you the old values if you need to put them back.

Step 3: create or update a record

Save this as change.json. UPSERT means "create it, or update it if it exists":

{
  "Comment": "Point app to new server, ticket 4521",
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "app.contoso.com",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [{ "Value": "203.0.113.10" }]
      }
    }
  ]
}

Apply it and wait until it's in sync:

CHANGE_ID=$(aws route53 change-resource-record-sets \
  --hosted-zone-id Z0123456789ABCDEFGHIJ \
  --change-batch file://change.json \
  --query "ChangeInfo.Id" --output text)

aws route53 wait resource-record-sets-changed --id "$CHANGE_ID"
aws route53 get-change --id "$CHANGE_ID" --query "ChangeInfo.Status"

INSYNC means every Route 53 name server has the change.

Step 4: point a name at a load balancer

Use an alias instead of an IP address, because a load balancer's addresses change. First, get its DNS name and its own zone ID:

aws elbv2 describe-load-balancers --names alb-app \
  --query "LoadBalancers[0].{DNS:DNSName,Zone:CanonicalHostedZoneId}"
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "app.contoso.com",
        "Type": "A",
        "AliasTarget": {
          "HostedZoneId": "Z35SXDOTRQ7X7K",
          "DNSName": "alb-app-1234567890.ap-south-1.elb.amazonaws.com",
          "EvaluateTargetHealth": true
        }
      }
    }
  ]
}

The HostedZoneId is the load balancer's zone ID from the command above, not your own zone's. The value shown here is just an example.

Alias or CNAME?

Question Use
Is it the root of the domain, like contoso.com? Alias. A CNAME isn't allowed there
Is the target an AWS resource, like an ALB or CloudFront? Alias
Is the target outside AWS, on a subdomain? CNAME

Step 5: test the answer

Ask Route 53 directly. This skips caches and checks your zone's data:

aws route53 test-dns-answer \
  --hosted-zone-id Z0123456789ABCDEFGHIJ \
  --record-name app.contoso.com --record-type A

Then ask from the outside:

dig app.contoso.com +short
dig app.contoso.com @8.8.8.8 +noall +answer

On Windows:

Resolve-DnsName app.contoso.com -Server 8.8.8.8

When the change doesn't show up

What you see Cause Fix
Old IP for a while Cached until the old TTL runs out Wait for the old TTL to pass
test-dns-answer is right, public DNS is wrong The domain's registrar uses other name servers Compare the NS records below
CNAME ... is not permitted A CNAME can't share a name with other records Remove the conflict or use an alias
InvalidChangeBatch Bad value or a DELETE that doesn't match exactly Read the message and copy exact values
Works outside but not inside the VPC A private zone with the same name hides it See private hosted zones

Compare the name servers in the zone with those the internet is using:

aws route53 get-hosted-zone --id Z0123456789ABCDEFGHIJ --query "DelegationSet.NameServers"
dig contoso.com NS +short

These two lists must match. If they don't, your zone isn't the one that's answering, and you need to fix it at the registrar.

Warning

Lower the TTL a day ahead of a planned move, for example to 60 seconds, then raise it again afterwards. Changing the TTL at the same moment as the IP doesn't help, because resolvers still hold the old answer for the old TTL.

Caution

Don't delete the zone's own NS and SOA records. Deleting a hosted zone and recreating it gives you different name servers, and the domain stops resolving until you update the registrar.

Prevent it next time

  • Export the zone to a file on a schedule, and keep it safe.
  • Manage records with Terraform or CloudFormation for anything important.
  • Find out who changed what with CloudTrail. If records vanish, follow the deleted records runbook.