Module 03 — Delegation, Recursion & Caching
Updated 20 August 2026
Module 01 said a recursive resolver "does the work". Module 02 said an NS record hands authority over. This module is the machinery in between — how a resolver walks from the root to an answer, what it stores along the way, and why a change you made an hour ago is still invisible to half the world.
🧠 concept → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (answers hidden)
Prerequisite: Modules 01 and 02. You need NS and SOA records, the aa flag, NXDOMAIN vs NODATA, and how to read a dig header.
Part A · How a resolver actually finds an answer
A1 · Where the walk starts: root hints
You call it once, and the first thing you ask for is the current, correct list. The printed number only has to be good enough to make that first call, which is why it can be years out of date and everything still works.
A resolver starting from nothing has a bootstrapping problem: to look anything up it must ask a server, but to find a server it must look something up.
The problem is solved by not solving it — the answer is shipped in the software. Every recursive resolver ships a small file, conventionally called named.root or root hints, containing the names and IP addresses of the 13 root server identities. That file is the only DNS data a resolver is given; everything else it learns.
🧪 Exercise A1.1 — Fetch the root zone's own view of itself
dig . NS +noall +answer | head -4
dig +short a.root-servers.net A
dig +short a.root-servers.net AAAA✅ Expected result — click to reveal
. 87203 IN NS a.root-servers.net.
. 87203 IN NS b.root-servers.net.
. 87203 IN NS c.root-servers.net.
. 87203 IN NS d.root-servers.net.
198.41.0.4
2001:503:ba3e::2:30The owner name is a single dot. That is the root, whose label is the empty string — Module 01 A3 in its most literal form. The root zone is a real zone with a real NS RRset, and you can query it like any other.
The TTL of 87203 tells you something useful: it is a counted-down value, so your resolver learned this from the root itself and is ageing it. The published value is 518400 — six days. Root data is cached aggressively because it changes almost never.
Every root server has both an IPv4 and an IPv6 address, and both are in the hints file. On an IPv6-only network the v4 addresses are unusable, which is why the hints file carries both and why a resolver on such a network still bootstraps cleanly.
A2 · The referral — what a server says when it is not authoritative
The trick is telling that apart from "this is my department, and we have nothing for you" — which sounds similar and means something completely different.
In Module 01 C4.1 you queried a root server and got this, and I promised to explain it later:
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 13, ADDITIONAL: 27That is a referral. The root server did not answer your question and did not fail. It said: I do not hold that data, but I know who is closer — here they are.
| Part of the referral | What it contains |
|---|---|
| status: NOERROR | Nothing went wrong. A referral is a successful response |
| ANSWER: 0 | No data for your question. The referral is not an answer |
| No aa flag | This server is not authoritative for the name you asked about |
| AUTHORITY section | The NS records of the child zone — go and ask these |
| ADDITIONAL section | Addresses for those nameservers, where the parent has them. This is glue — A4 |
- AUTHORITY contains NS records → referral. "Not me, ask them."
- AUTHORITY contains an SOA record → NODATA. "It is me, and there is nothing here."
One record type, two completely different meanings. When you are debugging a delegation, this is the distinction you are reading for.
A3 · The whole walk, with dig +trace
+trace is you doing that walk in person — which means nobody's memory of a previous visit can mislead you. That is exactly why it is the first command to run when an answer is disputed.
dig +trace does what a recursive resolver does, and prints every step. It is the single most educational command in DNS.
It also means +trace can succeed while normal resolution fails, and vice versa. That divergence is a diagnosis in itself: it tells you the fault is in your resolver, not in the zone.
🧪 Exercise A3.1 — Watch the tree being walked
dig +trace seamless.se✅ Expected result — click to reveal
; <<>> DiG 9.18.39 <<>> +trace seamless.se
;; global options: +cmd
. 518400 IN NS a.root-servers.net.
. 518400 IN NS b.root-servers.net.
... 13 in total ...
. 518400 IN RRSIG NS 8 0 518400 20260901050000 ...
;; Received 525 bytes from 127.0.0.53#53(127.0.0.53) in 4 ms
se. 172800 IN NS a.ns.se.
se. 172800 IN NS b.ns.se.
se. 172800 IN NS c.ns.se.
se. 172800 IN NS f.ns.se.
se. 172800 IN NS g.ns.se.
se. 172800 IN NS i.ns.se.
se. 172800 IN NS m.ns.se.
se. 172800 IN NS x.ns.se.
se. 172800 IN NS y.ns.se.
se. 172800 IN NS z.ns.se.
se. 86400 IN DS ...
se. 86400 IN RRSIG DS 8 1 86400 ...
;; Received 771 bytes from 198.41.0.4#53(a.root-servers.net) in 12 ms
seamless.se. 86400 IN NS ns-135.awsdns-16.com.
seamless.se. 86400 IN NS ns-722.awsdns-26.net.
seamless.se. 86400 IN NS ns-1490.awsdns-58.org.
seamless.se. 86400 IN NS ns-1760.awsdns-28.co.uk.
;; Received 631 bytes from 192.36.144.107#53(a.ns.se) in 42 ms
seamless.se. 60 IN A 52.77.52.233
seamless.se. 3600 IN NS ns-135.awsdns-16.com.
seamless.se. 3600 IN NS ns-722.awsdns-26.net.
seamless.se. 3600 IN NS ns-1490.awsdns-58.org.
seamless.se. 3600 IN NS ns-1760.awsdns-28.co.uk.
;; Received 175 bytes from 205.251.192.135#53(ns-135.awsdns-16.com) in 8 msYour TTLs and timings will differ, and the .se zone's exact server list changes occasionally. The shape will be identical.
Four blocks, and each ;; Received line names who answered. Read those lines first — they are the story.
Block 1 — from 127.0.0.53, your own machine. This is the hints being loaded and the root NS set confirmed. Nothing has left your network yet.
Block 2 — from 198.41.0.4, a root server. It did not answer "where is seamless.se". It referred you one label down, to .se. Ten NS records, no A record for your question, no aa. This is A2's referral in the wild.
Block 3 — from 192.36.144.107, one of the .se servers. Another referral, one label further: here are seamless.se's four nameservers. Still no address.
Block 4 — from 205.251.192.135, a Route 53 server. Finally, A 52.77.52.233. That server is authoritative, so it answers rather than referring.
Now the three insights that make this worth doing.
1. Nobody knew the answer until the last step. The root does not know where seamless.se is and never did. It knows who runs .se. Each step narrows by exactly one label. This is why the system scales: no server anywhere holds more than its own slice, and the root's job — knowing about ~1,500 TLDs — is tiny.
2. Look at the TTLs in the last two blocks: 86400 from the parent, 3600 from the child. The same NS RRset, two different TTLs, because it is published in two places by two different parties. That is not a bug, it is the structure — and C1 is about what happens when the two disagree on content rather than just TTL.
3. The DS and RRSIG records in block 2 are DNSSEC. .se is a signed zone, and the root is vouching for it cryptographically. Notice there is no DS record in block 3 for seamless.se — the chain of trust stops at .se, because seamless.se is unsigned. Module 07 makes that whole chain the subject.
🎯 Interview questions — Recursion and referrals
Q. Walk me through what happens when a resolver looks up www.example.com with an empty cache.
It starts from its root hints and asks a root server. The root does not know the answer; it returns a referral — the NS records for com, plus glue addresses. The resolver asks a com server, which refers it to example.com's nameservers. It asks one of those, which is authoritative and answers.
Each step resolves exactly one more label, and the resolver caches every referral along the way, so the next lookup for anything under com skips the root entirely.
The framing that shows understanding rather than recitation: no server in that chain except the last one knows the answer, and none of them ever did. The hierarchy is not a directory being searched — it is a chain of pointers, each maintained by a different organisation. That is precisely why the system scales to hundreds of millions of domains with a root zone of only about 1,500 entries.
Q. What is the difference between iterative and recursive resolution?
Recursive describes what the client asks for: "give me the final answer, do whatever work is needed". Iterative describes what the resolver does: a sequence of queries to different servers, each returning either a referral or an answer.
So a stub asks recursively, the recursive resolver resolves iteratively, and authoritative servers only ever answer iteratively — they refer or they answer, never both.
The concrete detail worth adding: it is not a capability difference, it is one bit. The client sets recursion desired; a server willing to do the work sets recursion available. An authoritative-only server never sets ra, which is what you observed in Module 01 C4.1 — and dig +trace is you personally performing the iterative walk instead of delegating it.
A4 · Glue records, and exactly when they are needed
Somebody has to put the branch's address on a sign out on the main road. That sign is a glue record — and note who owns the road: not you. It is put up by the level above you, which is why it is the piece people forget to update when they move.
A delegation gives you nameserver names. But to send a packet you need an address — so the resolver must now look up the nameserver's name. Sometimes that creates a circle.
Glue breaks the circle. The parent zone — com — publishes the address of ns1.example.com alongside the delegation, in the ADDITIONAL section. It is not authoritative for that data and says so by not setting aa; it carries it purely so resolution can begin.
And the rule falls straight out of the mechanism: glue is required only when the nameserver's name is inside the zone being delegated. If the nameservers live in a different zone, no circle exists and no glue is needed.
🧪 Exercise A4.1 — Two real delegations, one needing glue and one not
# Case 1: .se is served by nameservers that are INSIDE .se
dig +short se NS | sort
# Case 2: seamless.se is served by nameservers that are NOT inside seamless.se
dig +short seamless.se NS | sort✅ Expected result — click to reveal
$ dig +short se NS | sort
a.ns.se.
b.ns.se.
c.ns.se.
f.ns.se.
g.ns.se.
i.ns.se.
m.ns.se.
x.ns.se.
y.ns.se.
z.ns.se.
$ dig +short seamless.se NS | sort
ns-135.awsdns-16.com.
ns-1490.awsdns-58.org.
ns-1760.awsdns-28.co.uk.
ns-722.awsdns-26.net.Case 1 — every one of .se's nameservers ends in .se. They are in-bailiwick: inside the very zone they serve. The circle exists, so the root zone must carry glue for all ten of them. Without it, nobody could ever resolve anything in Sweden.
Case 2 — none of seamless.se's nameservers are inside seamless.se. They are in awsdns-16.com, awsdns-26.net, awsdns-58.org and awsdns-28.co.uk — four different zones, all resolvable independently. No circle, therefore no glue needed. The .se zone publishes only the four names.
This is a deliberate design choice by AWS and it is worth understanding why. Out-of-zone nameservers mean:
- No glue to go stale. Glue lives in the parent and is updated through your registrar. It is a notoriously neglected record, and stale glue is a classic hard-to-diagnose outage
- Addresses can change freely. Route 53 can renumber a nameserver without touching a single TLD zone anywhere
- Four different TLDs — .com, .net, .org, .co.uk — so no single TLD outage removes all four
The practical consequence for you. If you ever run your own nameservers named inside your own domain, you must keep the glue at your registrar correct, and you must update it before you renumber the server, never after. If your nameservers are your provider's, glue is not your problem — and knowing which of those two worlds you are in is a two-second check with the command above.
🎯 Interview questions — Glue
Q. What is a glue record and when is one required?
An address record for a nameserver, published by the parent zone alongside the delegation, so that resolution can begin.
It is required exactly when the nameserver's name is inside the zone being delegated — ns1.example.com serving example.com. Without glue, finding that nameserver's address requires querying the zone it serves, which is circular.
If the nameservers are in a different zone — as with any managed provider, where they are typically under the provider's own domain — there is no circle and no glue is needed.
Where this earns real credit: glue lives in the parent, so it is changed through the registrar, not in your own zone file. That makes it the record people forget. Stale glue produces a delegation where some resolvers reach you and others do not, depending on whether they had cached the correct address — an intermittent, resolver-dependent failure that is miserable to diagnose. The fix in the design is what AWS does: put your nameservers outside the zone and the problem cannot occur.
A5 · What a modern resolver actually sends
Older resolvers told everyone the whole story at every step. Modern ones say only as much as each person needs to point them onwards — which is more polite, and occasionally exposes a badly-behaved receptionist who was never asked a simple question before.
The walk in A3 has a privacy problem that went unnoticed for thirty years: at every step, the resolver sent the full name.
When resolving api.internal.seamless.se, the classic algorithm tells a root server operator, and then the .se registry, the entire name — even though neither needs to know more than the next label in order to refer you.
It is now the default in BIND, Unbound, Knot Resolver and the major public resolvers.
The operational side effect you will actually meet: it changes what a broken or non-compliant authoritative server does. Some old servers answer NXDOMAIN for an intermediate name that has no records of its own but does have children — which is wrong, and used to be invisible because nobody ever queried those intermediate names. With QNAME minimisation they are queried on every lookup, and such a server breaks resolution for everything beneath it. If a name suddenly stops resolving on modern resolvers but still works on old ones, this is a strong suspect.
🧪 Exercise A5.1 — Query an intermediate name that has no records of its own
# admin-vfo.seamless.se exists. Does the parent label chain behave?
dig +noall +comments seamless.se A
dig +noall +comments admin-vfo.seamless.se A
# now an intermediate name in a deeper hierarchy - it should be NODATA,
# NOT NXDOMAIN, because names exist beneath it
dig +noall +comments _tcp.jabber.org A
dig +noall +comments +answer _xmpp-client._tcp.jabber.org SRV✅ Expected result — click to reveal
$ dig +noall +comments _tcp.jabber.org A
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18822
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
$ dig +noall +comments +answer _xmpp-client._tcp.jabber.org SRV
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51002
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
_xmpp-client._tcp.jabber.org. 60 IN SRV 30 30 5222 scarlet.jabber.org._tcp.jabber.org returns NOERROR with zero answers — NODATA, not NXDOMAIN. That is the correct behaviour and it is what QNAME minimisation depends on.
Why the distinction is load-bearing. _tcp.jabber.org has no records of its own; it exists only as a container for _xmpp-client._tcp.jabber.org beneath it. The correct answer for a name that has children but no records is NODATA — "this node exists, nothing here". A server that answers NXDOMAIN is claiming the name does not exist at all, which by definition means nothing below it exists either.
With QNAME minimisation, a resolver asks about _tcp.jabber.org on its way down. Against a correct server it gets NODATA and continues. Against a broken one it gets NXDOMAIN, concludes the whole branch is absent, and everything below stops resolving.
The signature of this bug in production: a name works from an old resolver and fails from a new one, with no change on your side. The cause is usually an appliance or an old load-balancer-embedded DNS server sitting in front of a real zone.
Part B · The cache
B1 · What gets cached, and for how long
And here is the part that matters: once you know how to get to the city centre, every shop in the centre becomes easy — you only need the last few streets. That is why a first-ever lookup of a brand-new domain is usually fast, not slow.
The walk in A3 took four round trips. If every lookup cost four round trips the internet would be unusable — so the resolver caches every record it learns at every step, not just the final answer.
| Cached from the walk | Typical TTL | What it saves next time |
|---|---|---|
| Root NS set | 6 days | The root step, for every lookup, forever |
| se NS set + glue | 2 days | The root and TLD steps for every .se name |
| seamless.se NS set | 1–2 days | Everything except the final query, for every name in the zone |
| The A record itself | 60 seconds | The final query |
And it explains the shape of a cold-start incident. Restart a resolver and it does not get uniformly slower — it gets dramatically slower for a few seconds while it re-learns the infrastructure, then returns to normal. If you see that pattern, you are looking at a cache that was emptied.
🧪 Exercise B1.1 — Measure a cache miss and a cache hit
# pick a name you have not looked up recently
dig example.org +noall +stats | grep -E 'Query time|SERVER'
# immediately again
dig example.org +noall +stats | grep 'Query time'✅ Expected result — click to reveal
;; Query time: 16 msec
;; SERVER: 8.8.8.8#53(8.8.8.8) (UDP)
;; Query time: 4 msec16 ms then 4 ms. The second query never left the resolver.
Read the ratio, not the absolute numbers. On a public resolver with a warm shared cache the miss is cheap because the .org infrastructure was already cached — only the last step was real work. Against a resolver with a genuinely cold cache the first query would be 100–300 ms as it walks all four steps.
The diagnostic value of Query time. Single-digit milliseconds means a cache hit. Tens to hundreds means real work happened. If a name that should be cached is consistently expensive, either its TTL is tiny or something is evicting it — and that is worth knowing before you start blaming the network.
B2 · Negative caching, properly
And counter-intuitively, remembering the "no" saves more effort than remembering the "yes" — because the popular shop gets asked about once and everyone benefits, while the closed one gets asked about again and again by people who have not heard.
Module 02 B4 established that a "no" is cached, and that its lifetime is min(SOA MINIMUM, SOA TTL). Here is why it matters more than the positive cache.
And that inverts the usual intuition about which cache entry is expensive. A popular name's A record is cheap to re-fetch, because everyone wants it and one fetch serves everybody. A nonexistent name may be queried by exactly one broken client, forever, and each query is a full walk. Negative caching is what keeps that from being a denial of service against the authoritative servers.
🧪 Exercise B2.1 — Watch a "no" age
dig nx-probe-1.seamless.se +noall +authority | head -1
sleep 5
dig nx-probe-1.seamless.se +noall +authority | head -1✅ Expected result — click to reveal
seamless.se. 900 IN SOA ns-1490.awsdns-58.org. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400
seamless.se. 895 IN SOA ns-1490.awsdns-58.org. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400900 → 895 across a five-second sleep. The non-existence of nx-probe-1.seamless.se is now a cached object with a countdown, exactly like a positive answer.
Note which number is counting down. It is the TTL of the SOA record in the AUTHORITY section, not the MINIMUM field inside it — MINIMUM is still showing its published 86400. The effective negative TTL is min(86400, 900) = 900 seconds, and the countdown you are watching confirms it. This is the arithmetic from Module 02 B4, demonstrated rather than asserted.
The operational rule, one more time because it costs people hours: do not query a name before you create it. The NXDOMAIN you cache is your own. Deployment scripts that poll "does the record exist yet?" in a loop are actively making the wait longer on every resolver they touch.
B3 · Resolvers do not obey your TTL
So a short TTL is a request, never a guarantee — which is why a failover plan that depends on everyone honouring it is not really a plan.
You publish a TTL. Resolvers treat it as advice.
| Resolver control | Effect on your published TTL |
|---|---|
| max-cache-ttl | Caps it. A published TTL of 7 days may be held for only 1 day |
| min-cache-ttl | Raises it. A published TTL of 30 seconds may be held for 300 — this is the one that hurts |
| max-ncache-ttl | Caps how long a negative answer is kept, typically at 3 hours |
| Serve-stale | Keeps serving an expired record when the authoritative servers are unreachable — B4 |
So never design a failover mechanism whose correctness depends on clients honouring a short TTL. Design it so that a client using a stale address still reaches something — a load balancer, an anycast address, a health-checked endpoint. DNS should move traffic eventually; something else must handle it immediately.
B4 · Serve-stale
The catch is the tenth time: it also hides the fact that something changed. During an outage that is a kindness. During a migration it means a broken cutover looks like a working one.
If a record's TTL expires and the authoritative servers cannot be reached, a resolver has two options: return SERVFAIL, or return the expired record it still has.
The consequence to hold in your head: after your authoritative servers fail, your domain does not disappear — it freezes. Whatever was cached keeps being served. That is usually what you want during an outage, and it is emphatically not what you want during a migration, because a resolver that cannot reach your new nameservers will happily keep serving the old answers rather than telling you something is wrong.
Which produces a genuinely confusing failure mode: a migration that appears to work for most users and silently keeps sending a minority to the old infrastructure, with no error anywhere. If you are debugging that, dig +trace bypasses every cache and shows you the truth.
B5 · "DNS propagation" — what is actually happening
So some friends reach you immediately, some keep ringing the old number for weeks, and both are behaving perfectly normally. That is the whole of "DNS propagation" — not a wave spreading outwards, but a lot of separate people each checking at their own moment.
Here is the whole of it: you change a record on your authoritative servers. That change is live immediately for anyone who asks them. Every resolver in the world that already holds the old value keeps serving it until its own copy expires — independently, on its own clock, starting from whenever it happened to fetch.
So the honest answer to "how long will it take" is: up to the TTL that was in force on the record before you changed it, per resolver, plus whatever your provider takes to distribute the change across its own authoritative servers, plus any resolver-side TTL floor, plus any application cache.
Diagram source
flowchart LR
CH["you change the record<br>at 12:00:00"] --> AUTH["authoritative servers<br>NEW value, immediately"]
AUTH --> R1["resolver A<br>fetched at 11:59:50<br>TTL 300<br>serves OLD until 12:04:50"]
AUTH --> R2["resolver B<br>fetched at 11:57:00<br>TTL 300<br>serves OLD until 12:02:00"]
AUTH --> R3["resolver C<br>cold cache<br>serves NEW at once"]
AUTH --> R4["resolver D<br>min-cache-ttl 3600<br>serves OLD for an hour"]
R1 --> U["users see<br>OLD or NEW<br>depending only on<br>WHICH resolver<br>they happen to use"]
R2 --> U
R3 --> U
R4 --> U
style AUTH fill:#22c55e,color:#fff
style R4 fill:#ef4444,color:#fff
style U fill:#f59e0b,color:#fff🧪 Exercise B5.1 — Catch two resolvers holding the same record at different ages
for r in 1.1.1.1 8.8.8.8 9.9.9.9; do
printf '%-10s ' "$r"
dig @$r seamless.se A +noall +answer | head -1
done✅ Expected result — click to reveal
1.1.1.1 seamless.se. 41 IN A 52.77.52.233
8.8.8.8 seamless.se. 12 IN A 52.77.52.233
9.9.9.9 seamless.se. 58 IN A 52.77.52.233Same value, three different remaining lifetimes. Cloudflare fetched about 19 seconds ago, Google about 48, Quad9 about 2. Nothing is wrong with any of them.
Now picture the same three columns during a change. For the next 60 seconds one of them would show the new address and two the old, and a user's experience would be decided entirely by which resolver their ISP happens to run. That is "propagation" in full: not a wave spreading outward, but a set of independent timers expiring at different moments.
Which is also the practical verification technique. After any change, poll several public resolvers plus one authoritative server. The authoritative server tells you the change landed; the resolvers tell you how far the old answer still reaches. When all of them agree, you are done — and not before.
🎯 Interview questions — Caching
Q. How long does a DNS change take to take effect?
Up to the TTL that was in force before the change, independently per resolver — because nothing is pushed anywhere. Each cache holds its own copy on its own clock and re-fetches only when that copy expires.
So it is not one duration, it is a spread: some users see the change instantly because their resolver had nothing cached, and others see the old value for the full TTL.
The three things that make it take longer than the TTL suggests, and naming them is what marks out experience. Resolvers apply their own floors — min-cache-ttl can hold your 30-second record for five minutes. Serve-stale keeps expired records alive if your authoritative servers become unreachable. And application-level caches ignore DNS TTLs entirely; a JVM with default settings caches a lookup for the life of the process.
Q. Why is negative caching important?
Because queries for names that do not exist are constant and unbounded — typos, decommissioned services, malware beaconing, misconfigured clients. Without a cached "no", every one of them triggers a full walk from the root to prove the same absence again.
The negative TTL comes from the zone's SOA, specifically the lesser of the MINIMUM field and the SOA record's own TTL.
The inversion worth pointing out: intuitively you would think the popular records matter most in a cache, but a popular record is cheap — one fetch serves everyone. A nonexistent name might be queried by a single broken client forever, and each query is a full walk. Negative caching is what stops that being a denial-of-service against the authoritative servers.
Q. What is serve-stale and when would it hurt you?
It lets a resolver keep answering with an expired record when the authoritative servers are unreachable, on the basis that a slightly old address beats no address. RFC 8767, on by default in BIND 9.16+ and most public resolvers.
It is the right default for an outage: your domain freezes rather than vanishing.
Where it hurts is a migration. A resolver that cannot reach your new nameservers will keep serving the old answers instead of failing, so a broken cutover looks like a working one for most users while quietly sending a minority to the old infrastructure — with no error anywhere to alert you. dig +trace is the tool there, because it bypasses every cache and shows the truth from the source.
Part C · When delegation goes wrong
C1 · Parent NS versus child NS
Repaint the building sign and forget the road sign, and everyone keeps arriving at the old place. That is why updating your zone file changes nothing until the registrar updates the delegation.
A zone's NS records exist in two places: in the parent zone as the delegation, and in the child zone at its own apex. You saw both in A3 — 86400 from .se, 3600 from Route 53.
Which is why the failure is asymmetric. Add a nameserver to your zone and forget the registrar, and resolvers never use it — the delegation does not mention it. Remove a nameserver from your zone but leave it in the delegation, and resolvers keep sending it queries it can no longer answer. The second is worse, and it has a name: C2.
🧪 Exercise C1.1 — Compare the parent's view with the child's
# what the CHILD says about itself
dig +short seamless.se NS | sort
# what a .se TLD server says - the delegation.
# +norecurse stops the resolver from helpfully answering from the child.
dig @$(dig +short se NS | head -1) seamless.se NS +norecurse +noall +authority✅ Expected result — click to reveal
$ dig +short seamless.se NS | sort
ns-135.awsdns-16.com.
ns-1490.awsdns-58.org.
ns-1760.awsdns-28.co.uk.
ns-722.awsdns-26.net.
$ dig @a.ns.se seamless.se NS +norecurse +noall +authority
seamless.se. 86400 IN NS ns-135.awsdns-16.com.
seamless.se. 86400 IN NS ns-722.awsdns-26.net.
seamless.se. 86400 IN NS ns-1490.awsdns-58.org.
seamless.se. 86400 IN NS ns-1760.awsdns-28.co.uk.Identical sets. That is what healthy looks like, and it is a two-command check worth running on any domain you inherit.
Note the TTL difference — 3600 from the child, 86400 from the parent — and that it is not a problem. The two copies are published by two different parties with different update cadences. Only a content mismatch matters.
Why +norecurse is essential here. Without it your resolver will helpfully go and fetch the child's answer, and you will compare the child against itself and see agreement that is not there. This is the single most common mistake when checking a delegation, and it makes a broken delegation look fine.
When they disagree, the symptom is intermittent and resolver-dependent: resolvers that happened to cache the child's set behave one way, resolvers that only ever saw the parent's behave another. Users report a problem that you cannot reproduce, which is the worst kind.
C2 · Lame delegation
The other three get through perfectly. So the complaint rate is low, it never happens twice to the same person, and nobody believes there is a problem — which is exactly why this failure is so hard to pin down.
A lame delegation is a nameserver listed in a delegation that does not actually serve the zone. It is reachable — it answers — but it disclaims authority, usually with REFUSED or a non-authoritative answer.
The result is a domain that works, mostly. A few per cent of requests fail. It never fails for you when you test it, and it never fails twice in a row for the same user. Teams spend days on the application before anyone checks DNS.
The usual causes are all mundane: a nameserver decommissioned in the zone but never removed at the registrar; a secondary whose zone transfer has been failing silently for months so its copy expired; or a provider migration where the old provider's servers were left in the delegation.
🧪 Exercise C2.1 — Test every delegated nameserver individually
DOMAIN=seamless.se
for ns in $(dig +short $DOMAIN NS); do
printf '%-28s ' "$ns"
out=$(dig @"$ns" "$DOMAIN" SOA +norecurse +time=3 +tries=1 2>/dev/null)
st=$(echo "$out" | grep -o 'status: [A-Z]*' | head -1 | cut -d' ' -f2)
aa=$(echo "$out" | grep -o 'flags:[^;]*' | grep -c 'aa')
if [ -z "$st" ]; then echo "NO RESPONSE <-- unreachable"
elif [ "$aa" -eq 1 ]; then echo "$st aa=yes OK"
else echo "$st aa=NO <-- LAME"; fi
done✅ Expected result — click to reveal
ns-1490.awsdns-58.org. NOERROR aa=yes OK
ns-722.awsdns-26.net. NOERROR aa=yes OK
ns-135.awsdns-16.com. NOERROR aa=yes OK
ns-1760.awsdns-28.co.uk. NOERROR aa=yes OKAll four authoritative. That is a healthy delegation and it takes about two seconds to prove.
The two things this script tests, and why both are needed. Reachability is the easy half. The half people skip is aa — a server can answer perfectly while disclaiming authority, and that is exactly what a lame server does. Testing only "did it respond?" passes a lame delegation.
What a lame server looks like:
ns-old.previous-provider.net. REFUSED aa=NO <-- LAMEThe nameserver is alive and well; it simply does not host this zone any more. It was never removed from the delegation.
Add this to your post-migration checklist. Every DNS provider migration should end with this loop returning aa=yes on every line, and the count of lines matching what you expect. "The website loads" is not a test of that.
C3 · Forwarding versus recursion
That is exactly what a forward-only resolver is: fast, convenient, and a single point of failure you did not realise you had chosen.
A forwarding resolver does not walk the tree. It passes the whole question to another recursive resolver and relays the answer back.
| Mode | Behaviour |
|---|---|
| Full recursion | Walks root → TLD → authoritative itself. Independent, but every cold lookup costs several round trips |
| Forward-first | Tries the forwarder; falls back to walking the tree itself if the forwarder does not answer |
| Forward-only | Uses the forwarder exclusively. If it is unreachable, resolution fails — no fallback |
| Conditional forwarding | Forwards only certain zones — corp.internal to the internal servers, everything else recursed normally. The standard hybrid-network pattern |
The tell in an incident: dig against your local resolver fails, dig +trace succeeds. That combination means the data is fine and your resolution path is broken, and forwarding is the first thing to check.
Conditional forwarding is the version worth knowing well, because it is how nearly every corporate and hybrid-cloud network resolves internal names while still reaching the public internet — and it is the mechanism underneath the Kubernetes and split-horizon setups in Module 08.
🎯 Interview questions — Delegation problems
Q. What is a lame delegation and how do you detect one?
A nameserver listed in a zone's delegation that does not actually serve the zone — it responds, but disclaims authority, typically REFUSED or an answer without the aa flag.
I detect it by querying each delegated nameserver directly for the zone's SOA with +norecurse, and checking two things: that it answers at all, and that aa is set. Testing only reachability misses it entirely.
Why it deserves attention out of proportion to how simple it is: it produces partial failure. Resolvers spread queries across the delegated set, so one lame server out of four means a few per cent of cold lookups fail, never reproducibly, never twice for the same person. Teams burn days on the application layer first. It is nearly always a decommissioned server left in the delegation, or a secondary whose transfers have been failing silently.
Q. Your zone's apex NS records and the parent's delegation disagree. Which wins?
The parent's — that is the set a resolver follows to reach the zone in the first place, and at that moment it has never seen the child's records.
The child's apex NS set is what a resolver may refresh from once it is already in conversation with your servers, so the two sets can both be in play in different caches at the same time.
Which is exactly why the mismatch is so nasty: the failure is resolver-dependent and intermittent. And it explains a specific real trap — adding a nameserver to your zone file does nothing until the delegation is updated at the registrar, which is a different system, a different login, and often a different team.
Part D · Field recipes
D1 · Where is this wrong answer coming from?
The point where the story changes is where the problem is — and until you find it, arguing about whether the rumour is true gets you nowhere.
Someone reports a stale or wrong answer. There are exactly three places it can come from, and three commands separate them.
Diagram source
flowchart TD
S["a wrong or stale answer"] --> T1["1. dig +trace name<br>bypasses ALL caches"]
T1 -->|"trace shows the WRONG value"| ZONE["the ZONE is wrong<br>your change never landed<br>-> fix the record"]
T1 -->|"trace shows the RIGHT value"| T2["2. dig @auth-ns name<br>confirm at the source"]
T2 -->|"authoritative disagrees<br>with one another"| SYNC["SECONDARIES OUT OF SYNC<br>-> check serial + transfers"]
T2 -->|"all authoritative agree,<br>value is right"| T3["3. dig @1.1.1.1 / @8.8.8.8<br>+ the user's own resolver"]
T3 -->|"some resolvers still old"| CACHE["CACHE<br>-> wait out the TTL<br>nothing to fix"]
T3 -->|"all resolvers right,<br>user still broken"| CLIENT["CLIENT SIDE<br>-> /etc/hosts, app cache,<br>VPN resolver, split horizon"]
style ZONE fill:#ef4444,color:#fff
style SYNC fill:#ef4444,color:#fff
style CACHE fill:#f59e0b,color:#fff
style CLIENT fill:#8b5cf6,color:#fff🧪 Exercise D1.1 — Run the three-command triage on a real name
NAME=seamless.se
echo "=== 1. ground truth, no caches ==="
dig +trace $NAME 2>/dev/null | tail -6
echo "=== 2. every authoritative server, do they agree? ==="
for ns in $(dig +short $NAME NS); do
printf '%-28s %s\n' "$ns" "$(dig @$ns $NAME A +short +norecurse | head -1)"
done
echo "=== 3. public resolvers, how far has it reached? ==="
for r in 1.1.1.1 8.8.8.8 9.9.9.9; do
printf '%-10s %s\n' "$r" "$(dig @$r $NAME A +noall +answer | head -1)"
done✅ Expected result — click to reveal
=== 1. ground truth, no caches ===
seamless.se. 60 IN A 52.77.52.233
;; Received 175 bytes from 205.251.192.135#53(ns-135.awsdns-16.com) in 8 ms
=== 2. every authoritative server, do they agree? ===
ns-1490.awsdns-58.org. 52.77.52.233
ns-722.awsdns-26.net. 52.77.52.233
ns-135.awsdns-16.com. 52.77.52.233
ns-1760.awsdns-28.co.uk. 52.77.52.233
=== 3. public resolvers, how far has it reached? ===
1.1.1.1 seamless.se. 41 IN A 52.77.52.233
8.8.8.8 seamless.se. 12 IN A 52.77.52.233
9.9.9.9 seamless.se. 58 IN A 52.77.52.233All three sections agree, so this domain is healthy. The value of the recipe is what it looks like when they don't, and each disagreement points somewhere completely different:
Section 2 disagrees with itself → your secondaries are out of sync. The serial was not incremented, or a zone transfer is failing. Nothing to do with caching, and waiting will not help. Module 05.
Section 1 and 2 are right, section 3 has old values → pure caching. Nothing is broken and there is nothing to fix. Read the TTLs to tell people exactly how much longer, rather than guessing.
Everything above is right and the user is still broken → the fault is on their side of the resolver: /etc/hosts, an application cache, a VPN pushing a different resolver, or split-horizon DNS giving corporate clients a different answer. That is Module 06's territory, and you have now proved DNS itself is fine before spending anyone's time on it.
The reason to run all three in order rather than picking one. Each one eliminates a layer. After three commands you can say which team owns the problem, with evidence — and being able to hand over a narrowed problem is worth more than a guess about the answer.
D2 · Full delegation health check
The dead number is always the one nobody has called in two years.
#!/usr/bin/env bash
# delegation-check.sh <domain>
set -uo pipefail
D="${1:?usage: delegation-check.sh <domain>}"
PARENT="${D#*.}"
echo "== 1. PARENT's delegation (the set that actually matters) =="
PNS=$(dig @"$(dig +short "$PARENT" NS | head -1)" "$D" NS \
+norecurse +noall +authority +time=3 | awk '$4=="NS"{print $5}' | sort)
echo "${PNS:- <none - is this domain delegated at all?>}"
echo "== 2. CHILD's own apex NS set =="
CNS=$(dig +short "$D" NS | sort)
echo "$CNS"
echo "== 3. do they match? =="
if [ "$PNS" = "$CNS" ]; then echo " MATCH"
else echo " MISMATCH <-- intermittent, resolver-dependent failures"
diff <(echo "$PNS") <(echo "$CNS") | sed 's/^/ /'; fi
echo "== 4. is every delegated server actually authoritative? =="
for ns in $CNS; do
printf ' %-30s ' "$ns"
out=$(dig @"$ns" "$D" SOA +norecurse +time=3 +tries=1 2>/dev/null)
st=$(echo "$out" | grep -o 'status: [A-Z]*' | head -1 | cut -d' ' -f2)
if [ -z "$st" ]; then echo "NO RESPONSE <-- unreachable"
elif echo "$out" | grep -q 'flags:.* aa'; then echo "$st aa=yes OK"
else echo "$st aa=NO <-- LAME DELEGATION"; fi
done
echo "== 5. do all of them serve the same serial? =="
for ns in $CNS; do
printf ' %-30s serial %s\n' "$ns" \
"$(dig @"$ns" "$D" SOA +short +norecurse 2>/dev/null | awk '{print $3}')"
done🧪 Exercise D2.1 — Run it on your own domain
chmod +x delegation-check.sh
./delegation-check.sh seamless.se✅ Expected result — click to reveal
== 1. PARENT's delegation (the set that actually matters) ==
ns-135.awsdns-16.com.
ns-1490.awsdns-58.org.
ns-1760.awsdns-28.co.uk.
ns-722.awsdns-26.net.
== 2. CHILD's own apex NS set ==
ns-135.awsdns-16.com.
ns-1490.awsdns-58.org.
ns-1760.awsdns-28.co.uk.
ns-722.awsdns-26.net.
== 3. do they match? ==
MATCH
== 4. is every delegated server actually authoritative? ==
ns-135.awsdns-16.com. NOERROR aa=yes OK
ns-1490.awsdns-58.org. NOERROR aa=yes OK
ns-1760.awsdns-28.co.uk. NOERROR aa=yes OK
ns-722.awsdns-26.net. NOERROR aa=yes OK
== 5. do all of them serve the same serial? ==
ns-135.awsdns-16.com. serial 1
ns-1490.awsdns-58.org. serial 1
ns-1760.awsdns-28.co.uk. serial 1
ns-722.awsdns-26.net. serial 1Five checks, and each catches a different real failure:
- An empty section 1 means the domain is not delegated at all — either it does not exist or the registrar entry is missing. That is the whole problem, and everything else is noise
- A section 3 mismatch is the intermittent, resolver-dependent failure from C1
- aa=NO in section 4 is a lame delegation — the few-per-cent failure that never reproduces
- NO RESPONSE is a firewall or a dead host
- Differing serials in section 5 means your secondaries are out of sync and users are getting different answers depending on which server they reached
Serial 1 on every line here is Route 53's constant serial, from Module 02 B4 — it replicates internally rather than by AXFR. On a BIND primary/secondary setup you would expect a date-based serial, and any difference between servers would be the finding.
Run this after every DNS provider migration and every registrar change. "The website loads" tests one resolver's cache, not the delegation.
D3 · The post-change verification playbook
| When | Do | Why |
|---|---|---|
| T-48h | Read the current TTL. Lower it. Then wait the OLD TTL. | Caches holding the old record never see the new low TTL until their copy expires. Lowering it at cutover time achieves nothing |
| T-1h | Confirm the low TTL is visible on several public resolvers | Proves the old TTL has drained everywhere you can see |
| T-0 | Make the change | — |
| T+1m | dig @each-authoritative-ns — all agree? | Proves the change landed and secondaries are in sync. If this fails, nothing else matters |
| T+2m | dig +trace — does the walk from the root find the new value? | Bypasses every cache, including serve-stale, which can hide a broken cutover |
| T+5m | Poll 1.1.1.1, 8.8.8.8, 9.9.9.9 and the office resolver until all show the new value | This is the only real measure of "propagation" — read the TTLs to say how much longer |
| T+1d | Raise the TTL back up | Low TTLs are query volume and a hard dependency on your authoritative servers being reachable |
| Always | Keep the old endpoint alive until every cache has drained | Serve-stale and app-level caches mean some clients keep using the old address well past the TTL. Decommissioning on schedule is how you turn a clean migration into an incident |
Part E · Putting it together
E1 · How this all fits — the complete picture
Diagram source
flowchart TD
STUB["stub resolver<br>asks once, waits"] --> REC
REC["RECURSIVE RESOLVER"] --> C{"in cache<br>and not expired?"}
C -->|"yes"| HIT["answer from cache<br>TTL counted down<br>no aa flag<br>single-digit ms"]
C -->|"no"| W["walk the tree"]
W --> R["ROOT<br>from root hints<br>-> referral to TLD<br>+ glue if in-bailiwick"]
R --> T["TLD<br>-> referral to the zone"]
T --> A["AUTHORITATIVE<br>-> the answer, aa set"]
A --> STORE["cache EVERY step:<br>root NS 6d · TLD NS 2d<br>zone NS 1-2d · record its own TTL<br>negative answers too"]
STORE --> HIT
A -.->|"unreachable +<br>TTL expired"| STALE["serve-stale<br>expired record<br>rather than SERVFAIL"]
style HIT fill:#22c55e,color:#fff
style STORE fill:#f59e0b,color:#fff
style STALE fill:#ef4444,color:#fffFour ideas, and everything in this module follows from them.
- Each step narrows by exactly one label. No server holds more than its own slice — which is why the root zone has ~1,500 entries and the system serves hundreds of millions of domains.
- Everything learned on the way is cached, not just the answer. Infrastructure records have huge TTLs and are shared across every domain beneath them, so a first-ever lookup usually costs one round trip, not four.
- The parent's delegation is what a resolver follows. Your own apex NS records are not how anyone finds you.
- Nothing propagates. Independent caches expire on independent clocks, and resolvers may ignore your TTL in both directions.
E2 · Production practice
| Habit | Why |
|---|---|
| Use dig +trace as the first command when an answer is disputed | It bypasses every cache including serve-stale, so it separates "the data is wrong" from "a cache is stale" in one step |
| Always pass +norecurse when querying a specific server | Without it your resolver helpfully fetches the child's answer and you compare a set against itself |
| Check the parent's delegation, not just your zone's NS records | The parent's set is the one resolvers follow. Adding a nameserver to your zone file changes nothing until the registrar is updated |
| Test aa on every delegated nameserver, not just reachability | A lame server answers happily while disclaiming authority — reachability tests pass it |
| Prefer nameservers outside the zone they serve | No glue means no stale glue at the registrar, and no circular dependency to get wrong |
| Lower TTLs and wait the OLD TTL before a change | Caches holding the old record never see the new low value until their own copy expires |
| Never build a failover whose correctness needs clients to honour a short TTL | min-cache-ttl, serve-stale and JVM-style app caches all ignore you. DNS moves traffic eventually; something else must do it immediately |
| Keep the old endpoint alive well past the TTL after a migration | The tail of clients on stale answers is longer than the TTL, and decommissioning on schedule is how migrations become incidents |
| Avoid forward-only resolvers for anything that matters | It makes your DNS depend entirely on one upstream, with no fallback to the tree |
| Never poll for a name before you publish it | You cache your own NXDOMAIN and lengthen the wait on every resolver you touch |
E3 · Capstone exercise
Brief. For a domain you are authorised to work on, produce a delegation and cache report answering all seven:
- Trace the full path from the root and name who answered at each step and what type of response it was.
- Say which steps in that path would be skipped on a second lookup of a different name in the same zone, and why.
- State whether this zone requires glue records, and justify it from the NS records rather than by convention.
- Prove the parent's delegation and the child's apex NS set agree — using a method that cannot accidentally compare the child against itself.
- Prove no delegated nameserver is lame, testing the thing that reachability checks miss.
- Measure how far a hypothetical change would have already reached, across three public resolvers, and state the maximum remaining wait.
- Give the exact ordered procedure to repoint this zone's apex, including the two steps people skip.
✅ Model answer — attempt it first, then click
1. The path. dig +trace seamless.se, reading the ;; Received lines:
- 127.0.0.53 — local, root hints loaded, root NS set confirmed. Not a network step
- 198.41.0.4 (a root server) — referral to .se. NOERROR, ANSWER: 0, no aa, NS records in AUTHORITY
- 192.36.144.107 (a.ns.se) — referral to seamless.se
- 205.251.192.135 (Route 53) — answer, aa set
2. What is skipped next time. Steps 1–3. The root NS set (6 days), the .se NS set (2 days) and seamless.se's NS set (1–2 days) are all cached. A different name in the same zone costs one query — straight to Route 53. A different .se domain entirely still skips the root and TLD steps. This is why cold-cache latency is a startup phenomenon, not a steady state.
3. Glue. Not required. seamless.se's nameservers are ns-135.awsdns-16.com, ns-722.awsdns-26.net, ns-1490.awsdns-58.org and ns-1760.awsdns-28.co.uk — none of them is inside seamless.se, so there is no circular dependency to break. Glue would be required only if a nameserver were named ns1.seamless.se. Contrast .se itself, whose servers are all *.ns.se — in-bailiwick, so the root zone must carry glue for them.
4. Parent versus child.
dig +short seamless.se NS | sort # child
dig @a.ns.se seamless.se NS +norecurse +noall +authority | \
awk '$4=="NS"{print $5}' | sort # parent+norecurse is the part that matters. Without it the resolver fetches the child's answer for you and you compare the child against itself — agreement that proves nothing. Both sets return the same four names: match.
5. Lameness. Query each delegated server for the SOA with +norecurse and check aa, not just that it responded:
for ns in $(dig +short seamless.se NS); do
dig @$ns seamless.se SOA +norecurse +noall +comments | grep -o 'flags:[^;]*'
doneAll four return flags: qr aa rd — authoritative. A lame server would answer REFUSED, or answer without aa; a reachability test passes both, which is exactly why aa is the check.
6. Reach. dig @1.1.1.1 / @8.8.8.8 / @9.9.9.9 and read the remaining TTLs — for example 41, 12 and 58 against a published 60. Maximum remaining wait is therefore under a minute for these three. Since the published TTL is 60, no resolver should hold an old value for more than 60 seconds after a change — subject to any resolver-side min-cache-ttl floor and to application caches, neither of which I can see from outside.
7. The repointing procedure.
T-48h read current TTL; lower it to 60; THEN WAIT THE OLD TTL <-- skipped step 1
T-1h confirm the low TTL is visible on 1.1.1.1 / 8.8.8.8 / 9.9.9.9
T-0 make the change
T+1m dig @every authoritative NS - do all agree? same serial?
T+2m dig +trace - does the walk from the root find the new value?
T+5m poll public resolvers until all show the new value
T+1d raise the TTL back up
KEEP THE OLD ENDPOINT ALIVE well past the TTL <-- skipped step 2The five things most people miss:
- Waiting the old TTL after lowering it. Lowering the TTL and changing the record in the same maintenance window helps nobody who was already holding the old copy — which is everybody who mattered
- +norecurse in requirement 4. Omit it and the check silently becomes meaningless while still printing a reassuring "match"
- Checking aa rather than reachability in requirement 5. Lame servers are reachable; that is what makes them lame rather than dead
- Justifying glue from the NS records rather than saying "managed providers don't need glue". The rule is in-bailiwick or not, and you can read it off the output in two seconds
- Keeping the old endpoint alive. Serve-stale and application caches extend the tail well past any TTL you published, so the endpoint must outlive the DNS change by a wide margin
E4 · Official documentation
| Link | Covers |
|---|---|
| RFC 1034 §4.3.2, §4.3.3, §5.3.3 | The resolver algorithm, referrals, glue and caching. Part A in its original form |
| RFC 2308 — Negative Caching | NXDOMAIN and NODATA caching, and the min(MINIMUM, SOA TTL) rule |
| RFC 1912 §2.8 — NS record errors | Lame delegations, parent/child mismatches, glue mistakes |
| RFC 8767 — Serving Stale Data | Why an expired record beats SERVFAIL, and when that hides a broken migration |
| RFC 9156 — QNAME Minimisation | What modern resolvers actually send, and the NXDOMAIN-vs-NODATA bug it exposes |
| RFC 9499 — DNS Terminology | Referral, delegation, lame delegation, in-bailiwick, forwarder — precise definitions |
| BIND 9 — Name Server Operations · Configuration Reference | max-cache-ttl, min-cache-ttl, max-ncache-ttl, stale-answer-enable, forwarders |
| BIND 9 Troubleshooting | The vendor's own version of Part D |
| IANA Root Servers · Root Zone Database | The 13 identities and their operators; every TLD and who runs it |
RFC 1034 §4.3.2 is the resolver algorithm in pseudocode, and it is genuinely readable. If you only read one section of one RFC in this whole track, make it that one — everything in Part A is a paraphrase of it.
RFC 1912 §2.8 is half a page and describes lame delegation better than any blog post.
Use the BIND configuration reference by searching for the directive name, not by reading it. It is a reference, not a document.
The offline route. dig +trace is itself the best documentation of Part A — run it against a domain you know and read the ;; Received lines. man dig covers +norecurse, +trace and +nssearch with no browser.
E5 · Self-assessment
1. Why does a resolver start at the root, and how does it know where the root is?
It starts at the root because that is the only place guaranteed to know something about every name — specifically, which servers run each TLD.
It knows where the root is from a root hints file shipped with the software: the names and addresses of the 13 root identities. They are hints, not configuration — the resolver uses one to contact a root server, then fetches and caches the authoritative root NS set. That is why an out-of-date hints file still works.
2. How do you tell a referral from a NODATA answer?
Both are NOERROR with ANSWER: 0 and something in the AUTHORITY section. The difference is what that section holds.
NS records → referral: "not me, ask them." SOA record → NODATA: "it is me, and there is nothing of that type here."
A referral also never has aa set, because the server is not authoritative for the name you asked about.
3. When is a glue record required, and where does it live?
Only when a nameserver's name is inside the zone being delegated — the circular case, where finding the server's address requires querying the zone it serves.
It lives in the parent zone, published alongside the delegation and returned in the ADDITIONAL section. The parent is not authoritative for it.
Practically: it is changed through the registrar, not your zone file, which is why it goes stale — and why providers deliberately name their nameservers outside your zone so the problem cannot arise.
4. A first-ever lookup of a brand-new .se domain takes 15 ms. How, when the walk has four steps?
Because three of the four steps were already cached. The root NS set and the .se NS set have TTLs of days and are shared by every domain beneath them, so any earlier .se lookup by anyone using that resolver paid for them.
Only the final query to the zone's own nameservers was real work.
This is also why a restarted resolver is briefly and dramatically slow, then normal — it is re-learning the infrastructure.
5. Which NS set does a resolver follow, the parent's or the child's?
The parent's. That is the delegation, and it is how a resolver reaches your zone at all — at that moment it has never seen your apex records.
The child's apex NS set is what a resolver may refresh from once it is already talking to your servers, so both can be live in different caches simultaneously.
Consequence: adding a nameserver to your zone file does nothing until the registrar updates the delegation.
6. What is a lame delegation and why is it so hard to spot?
A nameserver in the delegation that does not serve the zone — it responds but disclaims authority, usually REFUSED or an answer with no aa.
It is hard to spot because it causes partial failure. Resolvers spread queries across the delegated set, so one lame server in four means a small percentage of cold lookups fail, non-reproducibly.
Detection is querying each delegated server directly with +norecurse and checking aa — reachability tests pass a lame server.
7. You publish a 30-second TTL. Name three reasons a client might still use the old value an hour later.
A resolver's min-cache-ttl raising your 30 seconds to its own floor. Serve-stale, keeping an expired record alive because your authoritative servers became unreachable. And application-level caching — a JVM with default settings caches a lookup for the life of the process and never sees your TTL at all.
The design conclusion: never build a failover whose correctness depends on clients honouring a short TTL.
8. What exactly is "DNS propagation"?
A misleading name for the absence of a mechanism. Nothing is pushed. The change is live on your authoritative servers immediately; every cache holding the old value keeps serving it until its own copy expires, independently.
So the duration is "up to the previous TTL, per resolver" — plus provider distribution time, plus resolver TTL floors, plus application caches.
The way to measure it is to poll several public resolvers and read the remaining TTLs, not to consult a propagation-checking website.
9. Why must you pass +norecurse when comparing a parent's delegation with a child's NS set?
Without it, your resolver will follow the referral and fetch the child's answer on your behalf. You then compare the child's set with the child's set, see a match, and conclude the delegation is healthy when it may not be.
It is the single most common mistake in delegation checking, and it fails in the direction of false reassurance.
10. dig against your resolver fails; dig +trace succeeds. What does that tell you?
The data is fine and your resolution path is broken. +trace walks from the root itself and bypasses your resolver entirely, so a working trace proves the zone, the delegation and the authoritative servers are all healthy.
Suspects: a forward-only resolver whose upstream is down, a firewall blocking your resolver's egress, a poisoned or broken cache entry, or split-horizon DNS answering differently for your source address.
11. Why did QNAME minimisation break some domains?
Because resolvers now query intermediate names that nobody used to query. A name that has children but no records of its own must return NODATA; some non-compliant servers return NXDOMAIN instead, which claims the entire branch beneath it does not exist.
Under the old algorithm those intermediate names were never queried, so the bug was invisible. Now it breaks resolution for everything below.
The signature is a name that works from an old resolver and fails from a modern one, with no change on your side.
You have read dozens of dig headers by now — flags, RCODEs, section counts, and an OPT PSEUDOSECTION that has appeared in every single response without ever being explained. Module 04 takes the message apart byte by byte: the 512-byte limit and where it came from, EDNS(0) and buffer sizes, the TC bit and TCP fallback, why fragmentation is a security problem, and why "DNS uses UDP" is only half an answer.
📚 Sources for the interview questions
All record data — root and .se NS sets and their addresses, seamless.se delegation, cache timings, negative-TTL countdowns and the _tcp.jabber.org NODATA case — was captured live on 17 August 2026 with dig 9.18. The +trace transcript in A3.1 is assembled from those verified NS sets and reflects dig 9.18's output format; run it yourself and the shape will match, though TTLs and timings will differ.
Specifications verified directly: RFC 1034, RFC 1035, RFC 1912, RFC 2308, RFC 8767, RFC 9156, RFC 9499, plus the BIND 9 ARM and IANA root server list.
Question selection cross-referenced against publicly published 2026 DNS and networking interview question sets:
- Top 25 DNS Interview Questions and Answers for 2026 — nitizsharma.com — the lookup process step by step, authoritative vs recursive, caching, propagation, forwarders vs conditional forwarders
- Top 30 Most Common DNS Interview Questions — Verve AI — client-to-root-to-authoritative resolution, TTL and caching in troubleshooting
- Interview Questions & Answers for DNS — DevOpsSchool — resolution sequence, zones
- 75+ Network Engineer Interview Questions for 2026 — Taggd
Answers were rewritten and deepened rather than reproduced. The published sets stop at "the resolver asks the root, then the TLD, then the authoritative server"; the operational half — lame delegations, parent/child mismatch, +norecurse, serve-stale hiding a broken migration, TTL floors — is what actually gets probed in the room.