Get in touch →
← All posts
Services2026.07.15

Zone Transfers Instead of Manual DNS

DNS here has one authoritative source of truth. Every zone file (every record for every name this infrastructure is authoritative for) lives on a single primary. Secondary servers don’t get their own hand-maintained copies of those records; they replicate from the primary via zone transfer, authenticated so a secondary only accepts a transfer from a primary it can verify. Anything this infrastructure isn’t authoritative for (a public domain it doesn’t own, a name out on the wider internet) gets resolved the ordinary way, by recursing out. The resolver only reaches outward for names it has no authority over; for its own zones, the answer always traces back to the one place those records are maintained.

What manual sync actually looks like once you’ve done it

Before this, keeping more than one DNS server in sync meant editing a zone file (or a record in whatever tool manages it) and then remembering to make the same edit everywhere else that server’s data needed to exist. That “remembering” is the entire problem. It works fine for the first change, works fine for the second, and by the tenth change across two or three servers you’ve either forgotten one of them or made a typo that doesn’t match between copies. The failure mode isn’t dramatic (nothing crashes), it’s just quiet inconsistency: one server answers a query one way, another answers it slightly differently, and now which answer a client gets depends on which server happened to handle that particular resolution. That’s a genuinely bad property for infrastructure whose entire job is giving consistent answers.

Zone transfer removes the remembering. There’s one place to make a change (the primary’s zone file) and the mechanism that gets it to every secondary is a protocol, not a person’s memory.

How the transfer mechanism actually works

DNS has had a zone transfer mechanism since the original DNS specifications, and the pattern used across this setup is close to the standard secondary-DNS model rather than anything custom. A secondary periodically checks the primary’s SOA (start-of-authority) record for its serial number; if the serial has incremented, the zone has changed and a transfer is due. Rather than waiting out that polling interval, the primary can also push a NOTIFY message the moment a zone changes, telling secondaries to check immediately: that mechanism is defined in RFC 1996, “A Mechanism for Prompt Notification of Zone Changes (DNS NOTIFY).”

The transfer itself can happen two ways. A full zone transfer (AXFR), formally specified in RFC 5936, sends the secondary the entire zone, every record, every time. That’s simple and always correct, but wasteful for a large zone where only a handful of records changed. An incremental zone transfer (IXFR), defined in RFC 1995, sends only the differences between the version the secondary already has and the current version on the primary: the primary keeps track of the deltas between recent zone versions specifically so it can answer “here’s what changed since serial N” without re-sending the whole zone. In practice a secondary asks for an IXFR when it has a recent-enough serial, and falls back to a full AXFR when it doesn’t (a brand-new secondary, or one whose serial is too old for the primary to have retained deltas for).

Authentication matters here as much as the transfer mechanism itself, and it’s not something to skip: an unauthenticated zone transfer will hand your complete zone (every hostname, every internal name you’d rather not publish) to anything that asks nicely on the right port. TSIG, transaction signatures defined in RFC 2845, lets a secondary and primary share a secret key so the secondary only accepts a transfer that’s signed by a primary holding that key, and the primary only serves a transfer to a request signed the same way. Without that, “zone transfer” is really just “anyone who can reach this port can dump your DNS,” which defeats a good part of the reason to centralize the zone data in the first place: you’d have replaced manual inconsistency with an automated leak.

One authoritative primary, secondaries replicated via authenticated AXFR/IXFR, external names resolved by recursion.

Why the resolver only recurses for names it doesn’t own

The other half of this setup is what happens when a query comes in for a name this infrastructure has no authority over, some public site out on the internet, say. The resolver doesn’t try to have an opinion about that; it recurses, walking the normal DNS hierarchy out to the public internet the same way any recursive resolver would, and caches the answer for the ordinary TTL. That’s the same DNS-resolution step covered as one of the six independent layers a request has to clear before it reaches anything: this post is about keeping the authoritative side of that layer correct, not about the recursive side. The two behaviors are deliberately kept apart: authoritative answers for this infrastructure’s own zones come only from data replicated down from the single primary, and everything else is resolved fresh (or from cache) via ordinary recursion. A resolver that blurred those two roles (that was willing to authoritatively answer for zones it wasn’t kept in sync for, or that cached authoritative-looking answers for names it doesn’t own) is a resolver that can start giving wrong answers about its own infrastructure with no clear signal that anything’s wrong. Keeping “what am I authoritative for” and “what do I merely recurse for” as two clearly separated categories is what makes it possible to reason about which answers are guaranteed correct (backed by the primary’s zone data) and which are just whatever the wider DNS hierarchy currently says.

What this buys, concretely

The property I wanted out of this isn’t fancy: change a record once, and know that every server answering for this infrastructure will eventually (usually within seconds, given NOTIFY) answer with that same change, with no step where I have to remember to also update server two and server three. Adding a new secondary is now “point it at the primary with the right key” rather than “copy today’s zone file and hope it doesn’t drift from here.” And because the transfer is authenticated, adding that secondary doesn’t come at the cost of handing the zone out to anything on the network that happens to ask for it.

It’s not a sophisticated setup by DNS-infrastructure standards — this is exactly the primary/secondary model DNS was designed around from early on, not something novel. What it replaced was worse specifically because it was manual, and manual synchronization of anything that needs to be consistent across more than one place is a problem that gets worse, not better, the longer you leave it unautomated.

The SOA serial is the one thing this whole model leans on

Everything above depends on one small, easy-to-get-wrong detail: the SOA record’s serial number has to increase every time the zone changes, and has to increase monotonically. That serial is what a secondary compares against its own last-known value to decide “is there something new to fetch.” Bump it inconsistently (forget to increment it after an edit, or roll it backward after restoring an old zone file from a backup) and the whole replication model quietly stops working while looking fine. A secondary that already has a serial equal to or higher than the primary’s current one has no reason to ask for a transfer at all, so a zone change made without bumping the serial can sit un-replicated indefinitely, with no error, no log line, nothing but secondaries silently continuing to answer with stale data. This is exactly the kind of failure this setup was supposed to eliminate, just relocated one level down: instead of forgetting to sync a record, it’s possible to forget to sync the signal that a record changed. Most tooling that manages zone files handles the increment automatically now, which is the main reason this isn’t a bigger operational hazard than it sounds like on paper, but it’s worth naming as the one place where the whole mechanism’s correctness rests on a detail that isn’t itself self-verifying.

What I actually watch for

Given that failure mode, the monitoring that matters here isn’t “is the primary up,” it’s “do the secondaries agree with the primary.” A periodic check that queries the SOA serial on every secondary and compares it against the primary’s current serial catches exactly the failure described above: a secondary stuck on an old serial because a transfer failed silently, got blocked by a firewall change, or never got triggered because NOTIFY didn’t reach it. That check is boring and I don’t run it as often as I probably should, but it’s the one piece of validation that confirms the property this whole setup exists to guarantee, not “the mechanism is configured,” but “the mechanism is currently working and every secondary’s answer matches the primary’s.”

The alternative I didn’t want back

It’s worth being explicit about what “manual sync” would mean if I ever had to fall back to it, because the comparison is what justifies the extra moving parts here. Manual sync means: every record change gets applied by hand to every server authoritative for that zone, in the same order, without a typo, every single time, for as long as more than one server exists. There’s no protocol enforcing that the copies match — there’s only discipline, and discipline is the one thing that reliably degrades under time pressure, tiredness, or simply not being the thing on your mind at the moment you’re making an unrelated change elsewhere. Zone transfer isn’t a more sophisticated goal than manual sync was chasing: it’s the same goal, “every authoritative server agrees,” achieved by a mechanism instead of by remembering. That’s a small technical change with an outsized effect on how much I trust the answer any given server gives.

← All posts