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 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 (white lie) … 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. Minimal NSEC (Route 53, NS1, bunny.net post-fix). The bitmap lists exactly the types present at a delegation point and nothing more. The \000 next-name is the tightest possible span, disclosing no real neighbouring names:

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

2. White-lie NSEC (RFC 4470 family). The server synthesizes an NSEC on the fly covering only the queried name, to prevent zone walking. Gcore's May answer was this dialect with an over-generous bitmap; the technique itself is legitimate - what matters for the delegation is solely that DS is absent from the bitmap.

3. NSEC3 (deSEC, Google Cloud DNS). The name is hashed, so the proof leaks nothing about other names in the zone. Two live examples - deSEC's unsalted white-lie variant and Google Cloud DNS's salted form, which is what production answers today:

10 July 2026
# deSEC - NSEC3, no salt, bitmap "NS" only
$ 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 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 privacy trade-offs, identical cryptographic effect: 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 (white lie) … 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.