RRSets vs. DNS Records: Why Your Provider's API Looks the Way It Does
If you've ever bounced between AWS Route 53 and Cloudflare and wondered why the same conceptual change — "update the A records for api.example.com" — feels like a totally different operation in each, you've bumped into one of the oldest schisms in DNS tooling: records vs. RRSets.
It looks like a UI choice. It isn't. It's a fundamental disagreement about what the unit of change in DNS actually is, and that disagreement reaches all the way down into DNSSEC, atomicity guarantees, and how you should think about change management for your zones.
The Vocabulary
A resource record (RR) is a single line in a zone:
api.example.com. 300 IN A 192.0.2.10Five fields: owner name, TTL, class, type, and rdata. One row. One fact about the world.
A resource record set (RRSet) is the complete collection of all records that share the same owner name, class, and type:
api.example.com. 300 IN A 192.0.2.10api.example.com. 300 IN A 192.0.2.11api.example.com. 300 IN A 192.0.2.12That's three records, but one RRSet. RFC 2181 §5 is unambiguous about this: those three records are not independent objects that happen to share a name. They are a single indivisible unit, and every record in the set must have the same TTL. A resolver that receives them must treat them as one answer.
This isn't a stylistic preference. It's the data model the DNS protocol actually uses on the wire.
Why the protocol cares
Three reasons RRSets exist as a first-class concept:
1. Atomic answers. When a resolver asks "what are the A records for api.example.com?", the authoritative server returns the entire RRSet in one response. There is no "partial" answer. Either the client gets all three IPs or it gets a failure.
2. TTL coherence. Caches index by RRSet, not by individual record. If two records in the same RRSet had different TTLs, half the set could expire before the other half — and now resolvers around the world disagree about what api.example.com resolves to. RFC 2181 forbids this for exactly that reason.
3. DNSSEC. This is the big one. DNSSEC signatures (RRSIG records) cover RRSets, not individual records. You cannot sign one A record and leave its sibling unsigned — the signature is over the canonical concatenation of the entire set. The moment you adopt DNSSEC, the RRSet becomes the only meaningful unit of change. Adding or removing one record means re-signing the whole set.
If you internalize nothing else: the protocol thinks in RRSets. Individual records are a UI fiction layered on top.
## Why provider APIs disagree anyway
Given all that, you'd expect every DNS provider to expose RRSet-shaped APIs. They don't, and the split is roughly:
RRSet-native APIs
- AWS Route 53 ChangeResourceRecordSets — the operation literally has "Sets" in the name)
- Google Cloud DNS ResourceRecordSets resource)
- Azure DNS (record sets are top-level objects; you PUT the whole set)
- NS1 (records are addressed by zone + domain + type, not by individual rdata)
- Most DNSSEC-first providers
Per-record APIs
- Cloudflare (each record has its own ID; you create, update, and delete them individually)
- DigitalOcean
- Namecheap, GoDaddy, most registrar-DNS combos
- Many older providers and most "DNS as a side feature" providers
The split tracks history and audience pretty cleanly. Per-record APIs are what most engineers expect from a CRUD REST API: each thing has an ID, you POST to create, DELETE to remove. They're easy to wrap, easy to reason about for a single change, and they map naturally to a UI where you click an "Add Record" button.
RRSet APIs feel weirder at first. To "add" a fourth IP to that A RRSet on Route 53, you don't POST a new record — you submit a CHANGE action that replaces the entire RRSet with a new four-record version. To remove one, same deal: replace the set. The unit of change is the set, not the row.
The hidden cost of per-record APIs
Per-record APIs feel friendlier, but they're hiding work that the protocol still requires somebody to do.
Atomicity. If you're changing two A records (say, removing one IP and adding another) on Cloudflare, that's two API calls. Between those calls, the RRSet is in an intermediate state — two old IPs and one new — and any resolver that queries during that window will cache the intermediate state for the TTL. On Route 53, both changes go in one CHANGE batch and either both apply or neither does. The atomicity is enforced for you.
TTL drift. Per-record APIs let you set a different TTL on each record in a set. The provider then has to silently reconcile this — usually by picking one TTL and applying it to all of them — or by serving an inconsistent set and hoping resolvers don't notice. Either way, the TTL field on individual records becomes a lie.
DNSSEC. This is where per-record APIs leak the most. When you "delete a record" from a signed zone via a per-record API, the provider has to figure out the RRSet that record belonged to, regenerate the signature for the new (smaller) set, and publish both the change and the new RRSIG atomically. The API gives you the illusion of a one-record operation, but under the hood it's an RRSet operation regardless.
Change tracking. This is where things get messy if you build tooling on top. If you're diffing zone state — which is, not coincidentally, what we do at ZoneWatcher — a per-record API forces you to reconstruct RRSets yourself before you can meaningfully compare "before" and "after." Otherwise a swap of one IP looks like an unrelated delete and an unrelated create, and you lose the semantic that those two changes were really one change to one set.
Why providers picked what they picked
Route 53 was built by AWS for AWS customers, who tend to be programmatic, multi-region, and care a lot about not having half-applied changes break production. Atomic RRSet replacement maps to that worldview. The API is more verbose for simple cases, but it makes "change three records together or change none of them" trivial.
Cloudflare grew up as a consumer-friendly CDN and reverse proxy. Their DNS dashboard had to be approachable to people who had never edited a zone file. "Click row, edit, save" is the obvious UX, and a per-record API falls out of that UX naturally. They've added DNSSEC, batch operations, and Terraform support since, but the underlying model is still per-record because changing it would break every integration ever written against their API.
Google Cloud DNS and Azure DNS arrived later and looked at what Route 53 was doing — the RRSet model was the obviously correct choice for an enterprise audience that already understood the protocol. NS1 was built by people who came out of the DNS industry and treated RFC 2181 as scripture from day one.
Neither model is wrong. They're optimized for different users.
What this means in practice
If you're picking a provider:
- If your zones change rarely and you mostly use the dashboard, per-record APIs are fine and probably nicer to live with.
- If you generate DNS programmatically (Terraform, GitOps, custom controllers), prefer RRSet-native APIs. The atomicity guarantee is worth the verbosity, and your IaC tool will translate "I want this set to look like X" much more cleanly into one PUT than into a diff-and-reconcile dance against a per-record API.
- If you run DNSSEC, strongly prefer RRSet-native. You'll have fewer surprises about when signatures get regenerated.
If you're monitoring DNS — which is our beat — always normalize to RRSets internally, regardless of how the provider exposes things. Comparing zones at the record level produces noisy, semantically meaningless diffs. Comparing at the RRSet level matches how the protocol and resolvers actually see your zone, which is the only view that matters when something breaks.
The takeaway
The DNS protocol thinks in RRSets. Some APIs reflect that honestly; others paper over it with a friendlier per-record abstraction. Both styles ship working DNS. But the further you get from a single human clicking buttons in a dashboard — the more automation, the more DNSSEC, the more multi-record changes, the more change auditing — the more the RRSet model stops being a quirk and starts being the only model that actually matches reality.
The providers that picked RRSets first weren't being pedantic. They were being honest about what DNS is.