AWS Route 53 Records

By Vishvesh Patel · DevOps EngineerUpdated September 16, 2026

Every record type you will actually use, A, CNAME, MX, TXT, SRV and CAA, plus UPSERT semantics, SPF and DMARC, and the errors Route 53 returns.

Route 53 Records

A hosted zone is a collection of record sets. Route 53 calls them "record sets" rather than records because one entry can hold several values. An A record with three addresses is one record set, not three records, and that distinction matters when you update them.

The change batch API

Every write goes through change-resource-record-sets with a batch of changes, applied atomically:

aws route53 change-resource-record-sets --hosted-zone-id Z1ABCDEFGHIJKL --change-batch '{
  "Comment": "add api endpoint",
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "api.example.com",
      "Type": "A",
      "TTL": 60,
      "ResourceRecords": [{"Value": "203.0.113.10"}]
    }
  }]
}'
{ "ChangeInfo": { "Id": "/change/C2ABCDEFGHIJKL", "Status": "PENDING" } }

Use UPSERT, not CREATE. CREATE fails if the record exists, which makes scripts non-idempotent; UPSERT creates or replaces. DELETE requires the record's exact current contents: value, TTL and all, which is why deleting is fiddlier than it looks and another reason to prefer UPSERT.

Because a batch is atomic, one invalid change rejects the whole thing. That is useful: a coordinated cutover of several records happens together or not at all.

aws route53 wait resource-record-sets-changed --id /change/C2ABCDEFGHIJKL

Multiple values go in one record set, and DNS returns them in rotating order. Crude round-robin with no health awareness:

"ResourceRecords": [
  {"Value": "203.0.113.10"},
  {"Value": "203.0.113.11"}
]

A and AAAA

# A. IPv4
{"Name": "api.example.com", "Type": "A", "TTL": 60,
 "ResourceRecords": [{"Value": "203.0.113.10"}]}

# AAAA. IPv6
{"Name": "api.example.com", "Type": "AAAA", "TTL": 60,
 "ResourceRecords": [{"Value": "2001:db8::1"}]}

If you publish an AAAA record, the service behind it must work over IPv6. Clients with IPv6 connectivity prefer it, so a broken AAAA record produces slow or failing requests for a subset of users while everyone else is fine. One of the more frustrating reports to receive.

CNAME, and where it cannot go

{"Name": "www.example.com", "Type": "CNAME", "TTL": 300,
 "ResourceRecords": [{"Value": "example.com"}]}

Two rules, both from the DNS specification rather than AWS:

No CNAME at the zone apex. example.com cannot be a CNAME, because the apex must hold SOA and NS records and a CNAME cannot coexist with other records at the same name. Route 53 rejects it explicitly:

InvalidChangeBatch: RRSet of type CNAME with DNS name example.com. is not
permitted at apex in zone example.com.

Use an alias record instead.

A CNAME cannot share a name with any other record type. So www cannot be both a CNAME and hold a TXT record, which matters when a service asks you to add a verification TXT record on a hostname that is already a CNAME.

MX, and the mail records that go with it

{"Name": "example.com", "Type": "MX", "TTL": 3600,
 "ResourceRecords": [
   {"Value": "1 aspmx.l.google.com"},
   {"Value": "5 alt1.aspmx.l.google.com"},
   {"Value": "10 alt2.aspmx.l.google.com"}
 ]}

The number is priority, lowest first. A frequent misreading, since "highest priority" means the smallest number.

Mail needs three more records, and getting them wrong is why your application's email lands in spam:

# SPF, which servers may send as you. A TXT record; the SPF type is deprecated.
{"Name": "example.com", "Type": "TXT", "TTL": 3600,
 "ResourceRecords": [{"Value": "\"v=spf1 include:_spf.google.com include:amazonses.com ~all\""}]}

# DKIM. Signing keys, usually provided by the mail service
{"Name": "selector1._domainkey.example.com", "Type": "CNAME", "TTL": 3600,
 "ResourceRecords": [{"Value": "selector1.domainkey.u123.wl.sendgrid.net"}]}

# DMARC. What receivers should do when SPF and DKIM fail
{"Name": "_dmarc.example.com", "Type": "TXT", "TTL": 3600,
 "ResourceRecords": [{"Value": "\"v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com\""}]}

Only one SPF record per domain. Two TXT records both starting v=spf1 is a permanent error that silently breaks authentication. Combine them into one with multiple include: terms. Note the maximum of ten DNS lookups in an SPF record, which several include: entries can exceed.

Start DMARC at p=none with rua= reporting to see what is being sent as you, then tighten to quarantine and reject once legitimate senders are covered.

TXT records and quoting

TXT values must be quoted, and those quotes are part of the value, so in a shell-embedded JSON payload they need escaping, which is where most TXT mistakes come from:

{"Value": "\"v=spf1 include:amazonses.com ~all\""}

A single string is limited to 255 characters. Longer values are split into multiple quoted strings which receivers concatenate. DKIM keys frequently need this:

{"Value": "\"v=DKIM1; k=rsa; p=MIGfMA0GCS...first255\" \"...remainder\""}

Get the quoting wrong and the record exists, looks plausible in the console, and fails validation with no useful error from whichever service is checking it.

CAA

Restricts which certificate authorities may issue for your domain:

{"Name": "example.com", "Type": "CAA", "TTL": 3600,
 "ResourceRecords": [
   {"Value": "0 issue \"amazon.com\""},
   {"Value": "0 issue \"amazontrust.com\""},
   {"Value": "0 iodef \"mailto:security@example.com\""}
 ]}

Worth setting, with one caution: a CAA record that omits your CA blocks issuance and renewal. If ACM certificates suddenly fail to validate, check whether someone added a CAA record without including amazon.com.

SRV

{"Name": "_sip._tcp.example.com", "Type": "SRV", "TTL": 300,
 "ResourceRecords": [{"Value": "10 60 5060 sipserver.example.com"}]}

The four fields are priority, weight, port and target. Used by SIP, XMPP, Minecraft and Kubernetes service discovery.

Listing and auditing

aws route53 list-resource-record-sets --hosted-zone-id Z1ABCDEFGHIJKL \
  --query 'ResourceRecordSets[].[Name,Type,TTL,ResourceRecords[0].Value]' \
  --output table
# Find records with long TTLs before a migration
aws route53 list-resource-record-sets --hosted-zone-id Z1ABCDEFGHIJKL \
  --query 'ResourceRecordSets[?TTL>`600`].[Name,Type,TTL]' --output table

That second query is worth running before any planned cutover. It tells you which records will keep serving stale answers, and for how long.

Common errors

InvalidChangeBatch: RRSet of type CNAME with DNS name example.com. is not
permitted at apex in zone example.com.

Use an alias record.

InvalidChangeBatch: Tried to create resource record set [name='api.example.com.',
type='A'] but it already exists

CREATE on an existing record. Use UPSERT.

InvalidChangeBatch: Tried to delete resource record set ... but it was not found

DELETE needs the exact current value and TTL. Read it with list-resource-record-sets first.

InvalidChangeBatch: RRSet with DNS name www.example.com. is not permitted
because a CNAME record exists with the same name

A CNAME cannot coexist with another type at the same name.

Quick reference

RecordPurposeNote
AIPv4 address
AAAAIPv6 addressOnly if the service really supports IPv6
CNAMEAlias to another nameNot at the apex; not alongside other types
AliasRoute 53 alias to an AWS resourceFree, works at the apex
MXMail serversLower number wins
TXTArbitrary textQuoted; 255-char strings
NSDelegationAlso delegates subdomains
CAAPermitted certificate authoritiesOmitting your CA blocks issuance
SRVService locationpriority weight port target

Key takeaways

  • Use UPSERT. CREATE is not idempotent and DELETE needs exact current contents.
  • Change batches are atomic: one bad change rejects the lot, which makes coordinated cutovers safe.
  • No CNAME at the apex, and no CNAME alongside another record type.
  • MX priority is lowest-wins.
  • Exactly one SPF record, combined with multiple include: terms.
  • TXT quoting is part of the value, and single strings cap at 255 characters.
  • A CAA record without your CA blocks certificate issuance.
  • Audit TTLs before a migration: that is what decides how long stale answers persist.

Check your understanding

Exercise

Two SPF records

A domain has one TXT record with "v=spf1 include:_spf.google.com ~all" and a second with "v=spf1 include:amazonses.com ~all". Mail from SES is failing authentication. Why?

Exercise

A verification record that cannot be added

A SaaS provider asks for a TXT verification record at www.example.com. That name is already a CNAME to the application. Route 53 refuses to create the TXT record. Why?


Frequently Asked Questions

What is the difference between CREATE, UPSERT and DELETE in a change batch?

CREATE adds a record and fails if one already exists with that name and type, which makes scripts non-idempotent. UPSERT creates the record or replaces it entirely, so re-running a deployment is safe. It is the action to use by default. DELETE removes a record but requires you to supply its exact current contents, including every value and the TTL, so it usually means reading the record first. Batches are applied atomically, so an invalid change rejects the whole submission.

Why can't I create a CNAME at my apex domain?

Because the DNS specification forbids it. The zone apex must carry SOA and NS records, and a CNAME cannot coexist with any other record type at the same name, so an apex CNAME is impossible in any DNS system, not just Route 53. The Route 53 answer is an alias record, which behaves like a CNAME pointing at an AWS resource but is resolved internally and returns an address, so it is legal at the apex. It is also free to query and can reflect the target's health.

How do I set up SPF, DKIM and DMARC in Route 53?

SPF is a single TXT record at the apex listing permitted senders, and it must be exactly one record, with multiple include: terms rather than multiple records, since two v=spf1 records is a permanent error. DKIM is normally a set of CNAMEs your mail provider gives you, pointing at keys they host. DMARC is a TXT record at _dmarc.example.com; start at p=none with a rua= reporting address to see what is being sent as your domain, then tighten to quarantine and reject.

Why is my TXT record not being recognised?

Usually quoting. The value must include literal double quotes, which means escaping them inside a JSON change batch, "\"v=spf1 include:amazonses.com ~all\"". Get it wrong and the record exists, looks plausible, and fails whatever validation is checking it. The other cause is length: a single TXT string is capped at 255 characters, so a longer value such as a DKIM public key must be split into multiple quoted strings within the same record, which receivers concatenate.

What does the number in an MX record mean?

Priority, and lower wins, which is the opposite of how "priority" reads to most people. 1 aspmx.l.google.com is tried before 10 alt2.aspmx.l.google.com, and the higher numbers act as fallbacks. Equal numbers share traffic. All of a domain's MX values belong in a single record set with one value per line, not as separate record sets, since they collectively describe where mail for that domain should go.

Should I add a CAA record?

Yes, with care. A CAA record restricts which certificate authorities may issue certificates for your domain, which limits the damage from a mis-issued certificate. The caution is that omitting a CA you use blocks issuance and renewal, so if you use ACM, include amazon.com and amazontrust.com, and add any other authority in use before publishing the record. Sudden unexplained ACM validation failures are worth checking against CAA before anything else.

Continue Learning

Explore Related Topics

When it breaks

Related Resources