Back to guides
Runbook

The Missing NSEC.

Our DNSSEC-signed zone delegates one subdomain to another nameserver, a completely ordinary arrangement. On one DNS provider, validating resolvers refused that subdomain outright: SERVFAIL, extended error 22, "No Reachable Authority". The zone was signed correctly. The delegation was legal. What was missing was a single proof record the provider never generated. This is the diagnosis across six providers, the bug report, and the fix, with every claim executable with dig.

By Ozy-666, creator and operator of dnsdoh.art · Published · 14 min read
One DS query, two answers dig leak.dnsdoh.art DS +dnssec → the parent zone THE BROKEN ANSWER leak.dnsdoh.art. NS ns.leak… leak.dnsdoh.art. RRSIG NS … (no NSEC - nothing proves DS absence) Resolver must assume the worst a stripped DS and a missing DS look identical, so the delegation is treated as bogus: SERVFAIL · EDE 22 for the whole subtree THE CORRECT ANSWER leak.dnsdoh.art. NS ns.leak… leak.dnsdoh.art. NSEC … NS RRSIG NSEC leak.dnsdoh.art. RRSIG NSEC … bitmap has no DS = signed proof of absence Verifiably insecure, by design the parent proved there is no DS, so the child is an intentional unsigned delegation: resolution proceeds normally DNSSEC has no "trust me, it is unsigned". Absence must be proven, and the proof is one NSEC or NSEC3 record. Miss it at the parent and every validating resolver drops the entire delegated subtree.

The whole incident in one picture: same query, and the only difference is one signed record proving a negative.

DNSSEC is famous for signing what exists. This incident is about the other half of the job: proving what does not.

The setup: a perfectly ordinary delegation

The dnsdoh.art zone is DNSSEC-signed, with its chain anchored at the .art registry. One subdomain, leak.dnsdoh.art, is delegated by NS record to a separate nameserver: it powers our DNS leak test, which needs to observe queries for unique random names arriving from resolvers. The delegation is intentionally insecure - no DS record at the parent, so the child zone is outside the signed chain. That is not a misconfiguration. Delegating a subdomain to infrastructure that does not do DNSSEC, without breaking the parent's signatures, is exactly what insecure delegations are for, and the RFCs support the pattern explicitly.

For this to work, one thing must hold: when a validating resolver asks the parent "does leak.dnsdoh.art have a DS record?", the parent has to answer with proof. Not silence - proof. This incident is what happens when a provider serves the delegation but skips the proof.

The same delegation produced a second, unrelated SERVFAIL later, that one on the child side rather than the parent: the host it points at ran only a recursive resolver, with nothing authoritative for the zone. That is a separate story with a separate fix, told in when our leak test invented a leak.

The symptom: SERVFAIL with a diagnosis attached

In early May the zone was hosted on bunny.net's DNS. Names under the delegation started failing on every major validating resolver. This is what Google's 8.8.8.8 returned on 7 May (trace preserved from the incident):

7 May 2026, zone hosted on bunny.net
$ dig @8.8.8.8 440b52664ed24b3c-06-haj18hw1-w1nrvb4.leak.dnsdoh.art DS +dnssec
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL
; EDE: 22 (No Reachable Authority):
;     (At delegation leak.dnsdoh.art for …leak.dnsdoh.art/ds)

Two details in that answer do the diagnostic work. SERVFAIL alone says almost nothing - it is DNS's shrug. But the EDE line is an Extended DNS Error (RFC 8914), a machine-readable diagnosis modern resolvers attach to failures: code 22 means the resolver could not establish a usable authority for the name, and the parenthetical even names the delegation where it gave up. If you debug DNS and are not reading EDE codes yet, start - they regularly turn an afternoon of guessing into one dig.

The site itself was fine. The parent zone validated. Only the delegated subtree was dead, and only through validating resolvers - which today means Google, Cloudflare, Quad9 and most ISP resolvers. A subtree that fails exactly there and nowhere else points at one layer: the security status of the delegation itself.

The rule: absence must be proven, not implied

DNSSEC validation walks down from the root: each zone's DS record at its parent vouches for the child's keys. When a delegation has no DS record, the resolver needs to know that securely, because the alternative is catastrophic: if "no DS in the answer" were simply taken at face value, an attacker in the middle could strip the DS record out of any referral and downgrade the entire subtree to unsigned, silently disabling DNSSEC for it. Spoofing the answer would become trivial again.

So RFC 4035 (section 3.1.4) requires the parent to prove the negative: a DS query for an unsigned delegation must be answered with an NSEC or NSEC3 record, signed by the parent, whose type bitmap shows that a DS record does not exist at that name. The resolver checks the signature, sees no DS bit, and concludes: this delegation is verifiably insecure - unsigned on purpose, not under attack. Resolution proceeds. NSEC and NSEC3 exist precisely for this kind of authenticated denial.

A provider that signs a zone but never emits that proof produces the failure we saw: the resolver cannot distinguish "no DS ever existed" from "the DS was just stripped by an attacker", must assume the worst, and marks the delegation bogus. One missing record, and the whole subtree returns SERVFAIL for the majority of the internet.

Six providers, one question

Because a DS query is answered by the parent's nameservers alone, the same zone hosted at different providers can be compared directly. During the incident we created identical test zones at several providers and asked each one the same question. The May results, from the incident traces:

Provider DS answer for the insecure delegation (May 2026) Verdict
bunny.netNS + RRSIG only, no NSECbroken - subtree SERVFAILs
AWS Route 53NSEC … NS RRSIG NSECcorrect, minimal bitmap
IBM NS1NSEC … NS RRSIG NSECcorrect, minimal bitmap
GcoreNSEC … A NS HINFO TXT AAAA LOC SRV CERT SSHFP …correct - inflated bitmap, DS absent
deSECNSEC3 (real chain, 0 iterations, no salt) … NScorrect, NSEC3 form
Google Cloud DNSNSEC3 (salted) … NScorrect, NSEC3 form

Five different implementations, five valid answers, one genuine bug. The broken one, verbatim from 7 May: the AUTHORITY section carried the delegation and its signature - and nothing else.

7 May 2026 - the broken answer
$ dig @kiki.bunny.net leak.dnsdoh.art DS +dnssec
;; status: NOERROR · ANSWER: 0, AUTHORITY: 2

;; AUTHORITY SECTION:
leak.dnsdoh.art.  300  IN  NS     ns.leak.dnsdoh.art.
leak.dnsdoh.art.  300  IN  RRSIG  NS 13 3 300 …
;; no NSEC, no NSEC3 - DS absence is asserted by silence, which validators reject

And a correct one from the same day, Route 53's minimal form - the NSEC's type bitmap lists only what exists at the delegation point (NS RRSIG NSEC), and DS is conspicuously not among them:

7 May 2026 - Route 53, correct minimal answer
$ dig @ns-592.awsdns-10.net leak.dnsdoh.art DS +dnssec
;; status: NOERROR · ANSWER: 0, AUTHORITY: 4

;; AUTHORITY SECTION (SOA + RRSIG omitted):
leak.dnsdoh.art.  86400  IN  NSEC   leak\000.dnsdoh.art. NS RRSIG NSEC
leak.dnsdoh.art.  86400  IN  RRSIG  NSEC 13 3 86400 …

Reporting it, and what made the report work

We opened a ticket with bunny.net on 6 May. Paraphrasing the exchange rather than reprinting it: the report stated the RFC requirement (4035 §3.1.4), gave a one-line reproduction against their own nameserver, showed the resulting SERVFAIL with EDE 22 from public resolvers, and - after a day without movement - added the same query answered correctly by four other providers. Support escalated it to their DNS team; within a day the team confirmed a fix was ready, and it was deployed platform-wide shortly after. The ticket closed on 27 May with the issue resolved globally. Meanwhile we had already migrated the production zone, first to Route 53 and eventually to Google Cloud DNS, because uptime could not wait on a vendor timeline - but the fix now benefits every bunny.net customer running a signed zone with delegations.

A parallel pre-sales question to Gcore produced a different kind of answer: their support confirmed NSEC support (not NSEC3), and we passed our observations about their inflated-but-conformant NSEC bitmap to their engineering team. No commitment was made, but the feedback was acknowledged.

The anatomy of the report that got a platform bug fixed in days, as a reusable checklist:

  • A one-line reproduction against the vendor's own infrastructure - dig @their-ns yourzone DS +dnssec - that any engineer can run in ten seconds.
  • The exact standard citation, not "DNSSEC seems broken". A section number turns a debate into a lookup.
  • The observed impact from the user side: SERVFAIL + EDE 22 from named public resolvers, so the severity is undeniable.
  • A cross-provider comparison proving the zone configuration is not the problem. Four providers answering correctly for the identical delegation removes every "works as intended" exit.

Nothing in that list requires insider access. Every piece is a public dig query.

Verified today: the fix is real

We told bunny.net we would verify the fix once deployed, and this article is that verification. Our old test zone still answers on their nameservers, which makes the before/after directly comparable. The same query that returned a bare delegation in May, executed on 10 July:

10 July 2026 - same nameserver, fixed answer
$ dig @kiki.bunny.net leak.dnsdoh.art DS +dnssec
;; status: NOERROR

;; AUTHORITY SECTION (SOA + RRSIGs omitted):
leak.dnsdoh.art.  900  IN  NSEC   \000.leak.dnsdoh.art. NS RRSIG NSEC
leak.dnsdoh.art.  900  IN  RRSIG  NSEC 13 3 900 …

A minimal, correct NSEC - the same shape Route 53 and NS1 produce. Credit where due: the bug was real, the report was taken seriously, and the fix that shipped is the textbook one.

Gcore's answer changed too. In May their NSEC carried an inflated type bitmap (claiming A, TXT, TLSA and a dozen other types at a pure delegation point - untidy, though conformant, since the DS bit was correctly absent). Re-tested on 10 July, their nameservers now return the minimal form as well:

10 July 2026 - Gcore, now minimal as well
$ dig @ns1.gcorelabs.net leak.dnsdoh.art DS +dnssec
leak.dnsdoh.art.  300  IN  NSEC  \000.leak.dnsdoh.art. NS RRSIG NSEC

We cannot attribute Gcore's change to our feedback with certainty - anycast fleets change for many reasons - but the shape we flagged is gone from every node we can reach. The production zone, meanwhile, lives on Google Cloud DNS, whose salted NSEC3 answer for the same question is in the next section.

Three shapes of a correct answer

Comparing providers surfaced something useful beyond the bug: authenticated denial comes in three valid dialects, and knowing them keeps you from mistaking a correct answer for a wrong one. All three below are real answers for our delegation, captured 10 July.

1. White-lie NSEC with a minimal bitmap (Route 53, NS1, bunny.net post-fix, Gcore today). None of these servers reveals a real neighbouring name; each synthesizes the tightest possible successor on the fly - the RFC 4470 "white lie" technique, used to prevent zone enumeration. Both successor encodings appear in our data: \000.leak.dnsdoh.art (bunny.net, Gcore) and leak\000.dnsdoh.art (Route 53, NS1). The bitmap lists exactly the types present at a delegation point and nothing more:

leak.dnsdoh.art.  IN  NSEC  \000.leak.dnsdoh.art. NS RRSIG NSEC

2. The same white lie with an inflated bitmap (Gcore in May). Identical synthesized successor, but the bitmap claimed A, TXT, TLSA and a dozen other types at a pure delegation point where they cannot exist. Still conformant - the DS bit was correctly absent, which is all a validator strictly needs - but untidy. The answer, preserved from 8 May, speaks for itself:

8 May 2026 - Gcore, white lie with an inflated bitmap (since replaced by the minimal form)
$ dig @ns1.gcorelabs.net leak.dnsdoh.art DS +dnssec
leak.dnsdoh.art.  300  IN  NSEC  \000.leak.dnsdoh.art. A NS HINFO TXT AAAA LOC SRV
                                  CERT SSHFP RRSIG NSEC TLSA HIP OPENPGPKEY SPF
# fourteen types asserted at a delegation point that can only hold NS, RRSIG and NSEC -
# yet no DS bit, so validators accepted it (1.1.1.1 returned this answer with the ad flag)

One observation from that period, recorded as an observation and not a finding: while the production zone sat on those nameservers, Cloudflare's 1.1.1.1 showed up noticeably less consistently in our leak-test data than other resolvers, and the effect disappeared after migration. We have no packet captures to establish causation, but boundary-case answers are exactly where resolver implementations diverge. By July, the inflated form was gone from every Gcore node we could reach.

3. A precomputed NSEC3 chain (deSEC, Google Cloud DNS). Here the proof is not synthesized: it is a real link in a real chain of hashed names, and unlike a white lie, that claim is checkable. Hash the delegation name with deSEC's parameters and you get exactly the record's owner name; hash the zone apex and you get exactly its next field:

proving deSEC's chain is real (ldnsutils package)
$ ldns-nsec3-hash -a 1 -t 0 leak.dnsdoh.art
5ad2uleg9u6ij4mochfm346irj7ppfsr.   ← the NSEC3 owner name
$ ldns-nsec3-hash -a 1 -t 0 dnsdoh.art
gu9q1j29gvru5pgftjvdog2muhdjbqc4.   ← the NSEC3 next field

A worthwhile detail for anyone choosing parameters: deSEC's zero iterations and empty salt are not laziness, they are the current best practice (RFC 9276 - extra iterations and salts add cost without adding security). Google Cloud DNS, which hosts the production zone today, answers the same question with a salt and one extra iteration - an older style, harmless in practice:

10 July 2026
# deSEC - real NSEC3 chain, 0 iterations, no salt (RFC 9276 parameters)
$ dig @ns1.desec.io leak.dnsdoh.art DS +dnssec
5ad2uleg…fsr.dnsdoh.art.  IN  NSEC3  1 0 0 - GU9Q1J29…BQC4  NS

# Google Cloud DNS - NSEC3 chain with salt and one extra iteration (production today)
$ dig @ns-cloud-e1.googledomains.com leak.dnsdoh.art DS +dnssec
7d3vj32a…5ke2.dnsdoh.art.  IN  NSEC3  1 0 1 7009EC2D54AAB8B6 8MB0V6NS…L1D5  NS

Different trade-offs - white lies avoid precomputing a chain and disclose nothing, a real NSEC3 chain makes the denial independently checkable - but the cryptographic effect is identical: the parent proves, under its own signature, that no DS exists. Any of the three satisfies a validator. Only silence does not.

Audit your provider before you depend on it

If you run a signed zone and delegate any subdomain without a DS - or ever might - this is a five-minute pre-flight check, and it works on a throwaway test zone before you migrate anything:

# 1. in your (signed) zone at the candidate provider, create an insecure delegation:
#    sub.example.com NS ns-of-anything.example.net   (no DS record)

# 2. ask the provider's own nameserver for the DS:
$ dig @their-ns1 sub.example.com DS +dnssec
# PASS: AUTHORITY contains NSEC or NSEC3 (+RRSIG) whose bitmap has no DS
# FAIL: only NS/SOA/RRSIG, no NSEC record at all

# 3. confirm end to end through a validating resolver:
$ dig @8.8.8.8 sub.example.com NS +dnssec
# PASS: NOERROR
# FAIL: SERVFAIL with EDE 22 - the delegation is being treated as bogus

Step 2 is the one that isolates responsibility: it queries the provider's authoritative server directly, so caches, forwarders and your own configuration are all out of the picture. Whatever comes back is what the provider generates, full stop. That is also why the same query made the bug report irrefutable.

The broader habit this incident reinforces: providers differ in the corners, and you should test the corner you depend on. Every provider in our matrix handles ordinary signed records flawlessly. The differences - and the one outright bug - lived in a boundary case that marketing pages never mention and most customers never trigger. One dig before migrating is cheaper than a deindexed subtree after.

The same table, two months later

To close the story where it started: the May matrix, re-executed on 10 July 2026 against the same nameservers. Our test zones still answer at every provider except Route 53, where we removed the zone after migrating (its May answer was correct).

Provider DS answer for the insecure delegation (10 July 2026) Verdict
bunny.netNSEC … NS RRSIG NSECfixed - correct minimal proof
AWS Route 53NSEC … NS RRSIG NSECcorrect - the reference answer (May; test zone since removed)
IBM NS1NSEC … NS RRSIG NSECcorrect, unchanged
GcoreNSEC … NS RRSIG NSECcorrect - inflated bitmap gone, now minimal
deSECNSEC3 (real chain, unsalted) … NScorrect, unchanged
Google Cloud DNSNSEC3 (salted) … NScorrect - hosts the production zone

In May this table held one genuine bug and one oddity. In July every nameserver we can still query answers correctly, and even the oddity is gone. That is the outcome worth reporting bugs for: the broken provider fixed its platform for every customer, and a public record of the fix now exists that anyone can re-run with a single dig.

The rest of the zone

This delegation is one corner of a zone built to be verifiable end to end - DNSSEC, DANE pins, ECH, discovery records - all public, all checkable with the same tool.

Anatomy of a Hardened DNS Zone

Related reading: what DNSSEC is and what it signs, DNS record types including NSEC and DS, and the attacks that authenticated denial exists to stop.

Ozy-666 Author · Creator of dnsdoh.art

Ozy-666 builds and operates dnsdoh.art, an encrypted DNS resolver serving DoH, DoH3, DoT and DoQ. This account is first-hand: the delegation, the failing zone, the provider tickets and the migrations were all his, and every dig trace in this article was executed against live infrastructure - the May traces during the incident, the July ones while writing.