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.
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/C2ABCDEFGHIJKLMultiple 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. 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.
{"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.
{"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 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.
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.
{"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.
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 tableThat second query is worth running before any planned cutover. It tells you which records will keep serving stale answers, and for how long.
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.
| Record | Purpose | Note |
|---|---|---|
A | IPv4 address | |
AAAA | IPv6 address | Only if the service really supports IPv6 |
CNAME | Alias to another name | Not at the apex; not alongside other types |
Alias | Route 53 alias to an AWS resource | Free, works at the apex |
MX | Mail servers | Lower number wins |
TXT | Arbitrary text | Quoted; 255-char strings |
NS | Delegation | Also delegates subdomains |
CAA | Permitted certificate authorities | Omitting your CA blocks issuance |
SRV | Service location | priority weight port target |
UPSERT. CREATE is not idempotent and DELETE needs exact current contents.include: terms.<MCQExercise exercise={{ id: 'aws-r53-records-quiz1', title: 'Two SPF records', description: '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?', options: [ { id: 'a', text: 'A domain may publish only one SPF record. Two v=spf1 TXT records is a permanent error, so they must be combined into a single record with both includes' }, { id: 'b', text: 'SES requires a record of type SPF rather than TXT' }, { id: 'c', text: 'The second record needs a lower TTL so receivers prefer it' }, { id: 'd', text: 'SES authentication uses DKIM only, so the SPF records are irrelevant' }, ], correctId: 'a', explanation: 'The SPF specification permits exactly one SPF record per domain. When a receiver finds two, the result is a permerror rather than a merge, so authentication fails even though each record looks correct in isolation, and the failure is silent from your side. Combine them: "v=spf1 include:_spf.google.com include:amazonses.com ~all". Keep an eye on the ten-lookup limit too, since each include consumes lookups and exceeding it also causes a permerror.', wrongExplanations: { b: 'The dedicated SPF record type was deprecated years ago; TXT is correct.', c: 'TTL affects caching, not which of two conflicting records wins. Both are seen, which is the problem.', d: 'SES supports both SPF and DKIM, and DMARC alignment generally wants both configured correctly.', }, }} />
<MCQExercise exercise={{ id: 'aws-r53-records-quiz2', title: 'A verification record that cannot be added', description: '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?', options: [ { id: 'a', text: 'DNS does not allow a CNAME to coexist with any other record type at the same name, so either the CNAME or the TXT record must move' }, { id: 'b', text: 'TXT records can only exist at the zone apex' }, { id: 'c', text: 'Route 53 limits each name to one record set of any type' }, { id: 'd', text: 'The TXT record must be created before the CNAME, not after' }, ], correctId: 'a', explanation: 'A CNAME means "this name is an alias for another name", so by definition nothing else can be published at it. The DNS specification forbids mixing a CNAME with any other type at the same label. Route 53 enforces that. The usual resolutions are asking the provider whether verification can go on a different hostname or a _prefixed subdomain, or replacing the CNAME with an alias or A record so other types can coexist at that name. The same constraint is why an apex CNAME is impossible, since the apex must carry SOA and NS.', wrongExplanations: { b: 'TXT records are valid at any name; verification records on subdomains are routine.', c: 'A name can hold several record sets of different types. A, TXT and MX together is fine. The CNAME is the exception.', d: 'Order is irrelevant. The conflict exists whichever is created first.', }, }} />
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.
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.
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.
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.
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.
Choose a Route 53 routing policy: weighted, latency, failover, geolocation, geoproximity or multivalue. Learn the Set Identifier rule and why canaries drift.
Learn DynamoDB if you know SQL: partition and sort keys, designing for access patterns first, avoiding hot partitions, conditional writes, streams and TTL.
Create a DynamoDB table with the AWS CLI. Choose the key schema you cannot change later, enable deletion protection and PITR, and take on-demand backups.