
How to Manage DNS Records in AWS Route 53?
Amazon Route 53 is AWS's authoritative DNS service, and if your infrastructure already lives in AWS it's often the natural place to host your zones. It integrates with load balancers, CloudFront, S3, and health checks, and every change can be scripted through the API. It also has its own vocabulary — hosted zones, alias records, change batches, routing policies — and a few behaviours that surprise people coming from a simpler DNS host. If you need a refresher on the record types themselves, what DNS records are covers the basics.
This guide focuses on day-to-day record management in Route 53: creating a hosted zone and delegating your domain to it, adding and editing records in the console, using alias records, scripting changes with the AWS CLI and Python, and avoiding the common mistakes.
Key Route 53 Concepts
Hosted zone. A hosted zone is Route 53's container for a domain's records — the equivalent of a zone file. There are two kinds:
- Public hosted zones answer queries from the internet.
- Private hosted zones answer only queries from inside the VPCs you associate with them, which is useful for internal hostnames. The general idea is covered in what split-horizon DNS is.
When you create a public hosted zone, Route 53 automatically creates an NS record listing four nameservers (a "delegation set") spread across different TLDs, such as ns-123.awsdns-15.com, ns-1024.awsdns-00.org, ns-567.awsdns-07.net, and ns-1800.awsdns-25.co.uk, plus an SOA record. The exact four names are assigned per zone.
Record set. Route 53 groups all values with the same name and type into one record (historically called a resource record set). An A record for example.com with two IP addresses is a single record with two values, not two records.
Alias record. A Route 53-specific extension that points a name at an AWS resource — a CloudFront distribution, Application or Network Load Balancer, S3 website endpoint, API Gateway, another record in the same zone, and others. Alias records work at the zone apex, track the target's IP changes automatically, and queries to alias records that point to AWS resources aren't charged. The general concept is explained in what an ALIAS or ANAME record is.
Routing policy. Each record has a routing policy that controls how Route 53 answers:
| Policy | What it does |
|---|---|
| Simple | Returns the record's values; the default |
| Weighted | Splits traffic across records by relative weight |
| Latency | Answers with the record in the AWS region closest in latency to the user |
| Failover | Returns a primary record while it's healthy, a secondary otherwise |
| Geolocation | Answers based on the user's continent, country, or US state |
| Geoproximity | Routes by geographic distance, with an adjustable bias |
| Multivalue answer | Returns up to eight healthy values chosen at random |
| IP-based | Routes based on the client's source IP range (CIDR collections) |
Pricing. Route 53 charges a monthly fee per hosted zone plus a per-query fee for standard queries, with alias queries to AWS resources free. Prices change, so check the official pricing page before planning many zones.
Step 1: Create a Hosted Zone
In the console, open Route 53, choose Hosted zones, then Create hosted zone. Enter the domain name (example.com), choose Public hosted zone, and create it.
The same thing from the AWS CLI:
aws route53 create-hosted-zone \
--name example.com \
--caller-reference "example-com-$(date +%s)" \
--hosted-zone-config Comment=production-zone,PrivateZone=false
--caller-reference must be unique for each request; it protects against creating duplicate zones if you retry a command. The output includes the zone's ID (like /hostedzone/Z0123456789ABCDEFGHIJ) and its four nameservers under DelegationSet.
To look up an existing zone ID later:
aws route53 list-hosted-zones-by-name --dns-name example.com \
--query "HostedZones[0].Id" --output text
Step 2: Delegate the Domain to Route 53
A hosted zone does nothing until the domain's nameservers point at it.
- If the domain is registered with Route 53, and you created the zone when you registered it, delegation is already set. If you created a new zone later, update the nameservers under Registered domains to match the new zone's NS record.
- If the domain is registered elsewhere, copy the four nameservers from the hosted zone's NS record and enter them as custom nameservers at your registrar. The general process is in how to update nameservers for a domain.
Before switching nameservers on a live domain, create all the existing records in the new zone first, so there's no gap. Then confirm the delegation:
dig NS example.com +short
dig example.com A @ns-123.awsdns-15.com +short
The first command should return the four Route 53 nameservers once the change has reached the TLD; the second queries one of your zone's nameservers directly to verify that it serves the right answers even before delegation finishes. Substitute your own nameserver names.
Step 3: Create and Edit Records in the Console
Inside the hosted zone, choose Create record. The console's quick create view asks for:
- Record name — the label in front of the zone, such as
www. Leave it blank for the apex. - Record type — A, AAAA, CNAME, MX, TXT, CAA, SRV, and so on.
- Value — one value per line. For MX, include the priority (
10 mail.example.com). For TXT, wrap each string in double quotes. - TTL — in seconds.
- Routing policy — Simple unless you need something else.
- Alias toggle — switch this on to choose an AWS resource as the target instead of typing a value.
To edit a record, select it and choose Edit record. Route 53 doesn't let you change a record's name or type in place; you delete it and create a new one.
Step 4: Manage Records with the AWS CLI
All record changes go through one API call, change-resource-record-sets, which takes a change batch: a JSON document listing one or more changes. Each change has an action:
CREATE— fails if a record with that name and type already exists.UPSERT— creates the record, or replaces it if it exists. The safest choice for scripts.DELETE— removes a record. The name, type, TTL, and values must match the existing record exactly.
All changes in a batch are applied atomically — either all succeed or none do.
Here's a change batch that upserts an apex A record, a www CNAME, an MX record, and an SPF TXT record. Save it as change-batch.json:
{
"Comment": "Website and mail records for example.com",
"Changes": [
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "example.com",
"Type": "A",
"TTL": 300,
"ResourceRecords": [
{ "Value": "203.0.113.10" },
{ "Value": "203.0.113.11" }
]
}
},
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "www.example.com",
"Type": "CNAME",
"TTL": 300,
"ResourceRecords": [{ "Value": "example.com" }]
}
},
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "example.com",
"Type": "MX",
"TTL": 3600,
"ResourceRecords": [
{ "Value": "10 mail1.example.com" },
{ "Value": "20 mail2.example.com" }
]
}
},
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "example.com",
"Type": "TXT",
"TTL": 3600,
"ResourceRecords": [{ "Value": "\"v=spf1 ip4:203.0.113.0/24 -all\"" }]
}
}
]
}
Notice that TXT values include literal double quotes, escaped as \" inside the JSON string. Route 53 requires each TXT string to be quoted. The MX value combines priority and hostname in one string.
Apply the batch:
aws route53 change-resource-record-sets \
--hosted-zone-id Z0123456789ABCDEFGHIJ \
--change-batch file://change-batch.json
The response contains a change ID and a status of PENDING. Route 53 propagates changes to all of its nameservers, usually within about a minute. You can block until the change is complete:
aws route53 wait resource-record-sets-changed --id C0123456789ABCDEFGHIJ
aws route53 get-change --id C0123456789ABCDEFGHIJ --query "ChangeInfo.Status" --output text
The wait command polls until the status is INSYNC; get-change shows the current status at any time.
To list what's in the zone, filtered with a JMESPath query:
aws route53 list-resource-record-sets \
--hosted-zone-id Z0123456789ABCDEFGHIJ \
--query "ResourceRecordSets[?Type=='TXT'].[Name,ResourceRecords[].Value]" \
--output json
And to see exactly what Route 53 would answer for a name, without waiting for caches:
aws route53 test-dns-answer \
--hosted-zone-id Z0123456789ABCDEFGHIJ \
--record-name example.com \
--record-type A
Long TXT records
A single TXT string can hold at most 255 characters. Longer values, such as 2048-bit DKIM keys, must be split into several quoted strings within one value, separated by a space:
{
"Value": "\"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx\" \"second-part-of-the-key-continues-here-IDAQAB\""
}
Receivers join the strings back together, so the split position doesn't matter.
Step 5: Use Alias Records for AWS Resources
You can't put a CNAME at the zone apex, so pointing example.com at a CloudFront distribution or a load balancer requires an alias record. In the console, turn on Alias, choose the endpoint type, region, and resource. In a change batch, an alias uses AliasTarget instead of TTL and ResourceRecords:
{
"Comment": "Point the apex at CloudFront",
"Changes": [
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "example.com",
"Type": "A",
"AliasTarget": {
"HostedZoneId": "Z2FDTNDATAQYW2",
"DNSName": "d111111abcdef8.cloudfront.net",
"EvaluateTargetHealth": false
}
}
}
]
}
HostedZoneId here is the hosted zone of the target, not your zone. For CloudFront it is always Z2FDTNDATAQYW2. For load balancers, S3 website endpoints, and other services it varies by region — the console fills it in for you, and the AWS documentation lists the values. Create a matching AAAA alias as well if the target supports IPv6. Alias records take the target's TTL, so you don't set one.
Step 6: Weighted Records and Health Checks
Routing policies other than Simple use multiple records with the same name and type, distinguished by a SetIdentifier. A weighted pair sending roughly 90 percent of traffic to one server and 10 percent to another looks like this:
{
"Changes": [
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "app.example.com",
"Type": "A",
"SetIdentifier": "blue",
"Weight": 90,
"TTL": 60,
"ResourceRecords": [{ "Value": "198.51.100.20" }]
}
},
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "app.example.com",
"Type": "A",
"SetIdentifier": "green",
"Weight": 10,
"TTL": 60,
"ResourceRecords": [{ "Value": "198.51.100.21" }]
}
}
]
}
Adding a HealthCheckId to each record lets Route 53 stop returning endpoints that fail their health checks. This is the building block for weighted canary releases and active-passive setups; the design trade-offs are covered in what DNS failover is and how to configure it and how DNS can be used for load balancing.
Managing Records with Python (boto3)
For automation inside applications, the boto3 SDK calls the same API:
import boto3
route53 = boto3.client("route53")
response = route53.change_resource_record_sets(
HostedZoneId="Z0123456789ABCDEFGHIJ",
ChangeBatch={
"Comment": "Add staging host",
"Changes": [
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "staging.example.com",
"Type": "A",
"TTL": 300,
"ResourceRecords": [{"Value": "192.0.2.50"}],
},
}
],
},
)
change_id = response["ChangeInfo"]["Id"]
route53.get_waiter("resource_record_sets_changed").wait(Id=change_id)
print(f"Change {change_id} is in sync")
This upserts an A record for staging.example.com, then uses boto3's built-in waiter to block until Route 53 reports the change as INSYNC. The IAM identity running it needs route53:ChangeResourceRecordSets and route53:GetChange permissions; restrict the former to the specific hosted zone ARN.
Common Mistakes in Route 53
- Recreating a hosted zone. Deleting and recreating a zone assigns a new set of four nameservers. If you don't update the registrar, the domain keeps pointing at the old (now non-existent) zone and stops resolving.
- Duplicate hosted zones. Two public zones for the same domain can coexist; only the one matching the registrar's nameservers is used. Edits to the other appear to do nothing.
- Unquoted TXT values. Route 53 rejects or mangles TXT values that aren't wrapped in quotes, and long values must be split into 255-character strings.
- DELETE mismatches. A
DELETEfails withInvalidChangeBatchunless the TTL and every value match exactly. List the record first and copy it. - Editing the SOA or NS records casually. Changing the zone's NS record doesn't change delegation — the registrar controls that — but it can confuse resolvers.
- Forgetting cleanup. Records that point at deleted load balancers, S3 buckets, or Elastic IPs can be claimed by someone else. Audit alias and CNAME targets regularly.
AWS Route 53 FAQ
A hosted zone is the container for all DNS records of a single domain, similar to a zone file. Public hosted zones answer internet queries; private hosted zones answer only within associated VPCs.
A CNAME points one name to another and can't be used at the zone apex. An alias record is a Route 53 feature that points directly at AWS resources or other records in the zone, works at the apex, and alias queries to AWS resources aren't billed.
Changes typically reach all Route 53 nameservers within about a minute, when the change status becomes INSYNC. Resolvers that cached the old record keep it until its TTL expires.
Use UPSERT in scripts that should be safe to run more than once. Use CREATE when you want the call to fail if the record already exists, to avoid overwriting something unexpectedly.
No. You can keep the domain at any registrar and point its nameservers to the four nameservers in your Route 53 hosted zone.
Each TXT string must be enclosed in double quotes and be no longer than 255 characters. Split longer values into multiple quoted strings separated by spaces within the same value.
Yes. In the hosted zone, the console offers an Import zone file option that accepts BIND-format records. Review the result afterwards, especially TXT quoting and SOA/NS records, which aren't imported.
Yes. Create a private hosted zone and associate it with one or more VPCs. Only resources inside those VPCs can resolve its records.
Conclusion
Route 53 is easy to work with once its model clicks: a hosted zone holds your records, the registrar must delegate to that zone's four nameservers, and every change is an atomic change batch made up of CREATE, UPSERT, or DELETE actions. Alias records solve the apex problem for AWS resources, and routing policies with health checks give you weighted, latency-based, and failover behaviour without extra infrastructure.
For anything beyond occasional edits, script your changes with the AWS CLI or boto3 so they're reviewable and repeatable, wait for INSYNC before testing, and keep an eye on stale records pointing at resources you've deleted. That combination keeps Route 53 zones accurate and predictable as your infrastructure grows.
Here are some useful references for working with Route 53:
- AWS Documentation: Amazon Route 53 Developer Guide — the official guide to hosted zones, records, routing policies, and health checks.
- AWS CLI Reference: change-resource-record-sets — full syntax for the change batch command used throughout this guide.
- AWS Documentation: Choosing between alias and non-alias records — when to use alias records instead of CNAMEs.
- AWS Pricing: Amazon Route 53 pricing — current charges for hosted zones, queries, and health checks.
- RFC 1035: Domain Names - Implementation and Specification — the core DNS specification, including record formats and TXT string limits.


