DNSSEC troubleshooting: keeping out of BOGUS
When DNSSEC works, you notice nothing: answers come back validated and everything is fine. When DNSSEC breaks, every lookup for the affected zone returns
SERVFAILand there is no graceful degradation. This page is the field guide for what breaks, how to tell, and how to stay out of trouble.
Why DNSSEC failures are total#
DNSSEC is intentionally hard-fail. There is no "soft" failure mode: a validating resolver either has a clean signature chain to the root, or it returns SERVFAIL, on modern resolvers with an extended-error code (EDE) indicating why. The design choice: DNSSEC's job is to refuse to lie, and a soft-fail mode would defeat the purpose by letting an attacker who can strip signatures convince validators to fall back to unsigned answers.
So when DNSSEC breaks, your zone goes dark for every validator on the Internet for the duration of their cached SERVFAIL. SERVFAIL caching is controlled by the resolver operator (RFC 9520 requires resolvers to cache resolution failures), not signaled by your zone. Unbound caches DNSSEC-bogus results for 60 seconds by default via val-bogus-ttl (unbound.conf(5)); BIND's servfail-ttl defaults to 1 second (max 30; higher values are silently reduced, per the BIND ARM). You cannot shorten a SERVFAIL storm by editing your SOA. What you can do: fix the chain and wait for cached SERVFAIL responses to expire (up to five minutes per RFC 9520). The SOA minimum field governs NXDOMAIN caching (RFC 2308), not SERVFAIL; lowering it before a migration still helps if you will be pulling names that previously existed, but it does nothing for DNSSEC-failure cache duration.
What "becoming BOGUS" means#
A zone is BOGUS (or "in a bogus state") when a validating resolver cannot verify the chain of signatures from the root down to the answer. The resolver returns SERVFAIL, often with an Extended DNS Error (EDE) code (RFC 8914) that tells you which step failed. See dnssec for how the trust chain is constructed in the first place.
| EDE code | Name | Means |
|---|---|---|
EDE 0 | Other Error | Generic: keep digging |
EDE 1 | Unsupported DNSKEY Algorithm | Resolver doesn't implement the zone's signing algorithm |
EDE 2 | Unsupported DS Digest Type | The DS at the parent uses a digest type the resolver doesn't implement |
EDE 4 | Forged Answer | Resolver deliberately returned a non-authentic answer for policy reasons (e.g. a filtered or blocked name); used only when an answer is still provided rather than a failure rcode, so it is not a validation-failure code |
EDE 5 | DNSSEC Indeterminate | Resolver can't determine validity |
EDE 6 | DNSSEC Bogus | Validation failed |
EDE 7 | Signature Expired | RRSIG past expiration |
EDE 8 | Signature Not Yet Valid | RRSIG inception in the future |
EDE 9 | DNSKEY Missing | Can't find the signing key |
EDE 10 | RRSIGs Missing | Records aren't signed |
EDE 12 | NSEC Missing | Can't prove non-existence |
EDE rides in the EDNS OPT record, so any query that carries EDNS can carry it back: dig sends EDNS by default, and both 1.1.1.1 and 8.8.8.8 attach the code without needing +dnssec. You need dig from BIND 9.16.4 or newer to see it printed (as an EDE: line in the OPT pseudosection; change 5408 added it). Cloudflare even keeps the EDE on +cd responses, right alongside the unvalidated answer. The full IANA registry of EDE values lives at iana.org/assignments/dns-parameters.
A permanent BOGUS canary: dnssec-failed.org#
This zone exists for one purpose: it is deliberately broken. The operators publish DNSKEY records whose hashes do not match the DS record at the parent, so any validating resolver should refuse to answer. The expected response is SERVFAIL with EDE 9 (DNSKEY Missing): the resolver found the DS at the parent but could not find a matching DNSKEY in the zone.
The SERVFAIL arrives with or without the +dnssec flag from a validating resolver. An actual address means the validator is not in your path.
dig @1.1.1.1 +dnssec dnssec-failed.org A;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 58290
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
; EDE: 9 (DNSKEY Missing): (no SEP matching the DS found for dnssec-failed.org.)nslookup can't show EDE codes or the DO bit, but it does surface the SERVFAIL clearly:
nslookup dnssec-failed.org 1.1.1.1Server: 1.1.1.1
Address: 1.1.1.1#53
** server can't find dnssec-failed.org: SERVFAILConfirm the EDE reason code via DoH (no local resolver interference):
curl -s -H 'accept: application/dns-json' \
'https://cloudflare-dns.com/dns-query?name=dnssec-failed.org&type=A'{
"Status": 2,
"AD": false,
"Comment": ["EDE(9): DNSKEY Missing no SEP matching the DS found for dnssec-failed.org."]
}To see what the zone actually contains, bypassing validation with +cd (Checking Disabled):
dig @1.1.1.1 +dnssec dnssec-failed.org A +cd;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 63696
;; flags: qr rd ra cd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
; EDE: 9 (DNSKEY Missing): (no SEP matching the DS found for dnssec-failed.org.)
;; ANSWER SECTION:
dnssec-failed.org. 300 IN A 96.99.227.255
dnssec-failed.org. 300 IN RRSIG A 13 2 300 20260723015706 20260715225206 32784 dnssec-failed.org. zYXpmdcfbSRa7KYCaHbxSSnurpYEHZjGS0/NuZ8Q0EeVtD0nicxi/EIK e6jKlyJmdWOQs5BPYQit+hjEMPJskA==The RRSIG inception/expiration timestamps and the signature itself change every time the zone re-signs; expect different values on your run. Note that Cloudflare still attaches the EDE 9 diagnostic even though +cd let the answer through.
nslookup cannot set the CD (Checking Disabled) bit. Use dig for this test.
If your resolver returns NOERROR on the first query (without +cd), DNSSEC validation is off or broken on the resolver. That is the test. Some operators keep a continuous external probe pointed at dnssec-failed.org: if the canary ever returns NOERROR through their validator, an alert fires before users notice.
Other handy canaries:
A validating resolver sets the
adbit on answers you queried with+dnssec. Its absence is not proof a zone is unsigned: on-path interceptors and some routers stripadbefore it reaches a stub.delvand RRSIG presence are the path-independent checks.
| Zone | Expected behavior |
|---|---|
dnssec-failed.org | SERVFAIL with EDE 9 (DNSKEY Missing, by design); delv reports resolution failure |
cloudflare.com | NOERROR; delv reports "fully validated" |
ietf.org | NOERROR; delv reports "fully validated" |
iana.org | NOERROR; delv reports "fully validated" |
isitdns.net | NOERROR with AD=true; delv reports "fully validated" (DS keytag 2371 at .net) |
How a zone becomes bogus#
The most common ways, in roughly the order you'll encounter them:
1. Signatures expired#
RRSIG records have an explicit expiration (typically 30 days from issuance). Authoritative servers resign before expiry. If signing stops (a forgotten cron, a permissions break, an HSM offline), signatures expire and the zone goes bogus.
Tell: EDE 7 (Signature Expired). Fix: re-sign immediately.
2. Key rollover without parent DS update#
A "KSK rollover" replaces the zone's Key Signing Key. The new KSK's hash must be published at the parent zone as a DS record (see dnssec for how DS records cross zone boundaries). If the rollover happens but the DS is not updated, validators chase the chain to an unknown key and refuse.
Tell: EDE 9 (DNSKEY Missing) or EDE 5 (Indeterminate). Fix: publish the new DS at the parent and wait for TTL.
3. Zone moved between providers with DNSSEC active#
Migrating a zone from one provider to another while DNSSEC is on at both ends is a guaranteed outage. The DS at the registrar still hashes the old KSK while the new provider's KSK has different bits.
Always disable DNSSEC at the old provider before changing nameservers, then re-enable at the new provider once the new DS is published. This is the #1 way people break DNSSEC.
4. Algorithm mismatch#
You roll a key to a new algorithm (RSA → ECDSA) but the DS at the parent still references the old algorithm. Validators see a chain that doesn't line up.
Tell: EDE 6 (Bogus) or EDE 1 (Unsupported DNSKEY Algorithm).
5. NSEC / NSEC3 inconsistency#
NSEC records prove non-existence. If a zone has DNSSEC but the NSEC chain is incomplete or inconsistent (a record was added without re-signing the NSEC), validators reject the response.
Tell: EDE 10 (RRSIGs Missing) or EDE 12 (NSEC Missing). Fix: re-sign.
6. Clock skew on the resolver or authoritative#
RRSIG validity is bounded by absolute timestamps. If the resolver's clock is wildly off, signatures look "not yet valid" or "expired" even when they aren't.
Tell: EDE 7 or EDE 8. Fix: ensure NTP is running on every resolver and every authoritative server in the chain. Drift on either end produces the same symptom.
Triage flow#
Symptom: dig @1.1.1.1 <name> → SERVFAIL
│
├──▶ Try with +cd → does it succeed?
│ │
│ ├── YES → DNSSEC is the cause. Continue ↓
│ └── NO → Not DNSSEC. Look at recursion/forwarders.
│
├──▶ dig @1.1.1.1 +dnssec <name> → look at EDE in the response
│ │ (needs dig from BIND 9.16.4+; curl DoH always works)
│ ├── EDE 7 → expired RRSIG. Re-sign.
│ ├── EDE 9 → missing DNSKEY. Check zone + parent DS.
│ ├── EDE 10 → missing RRSIGs. Zone has DS but is not actually signed.
│ └── EDE 6 → DNSSEC Bogus. Run DNSViz.
│
├──▶ Visualize the whole chain in DNSViz
│
└──▶ Compare against a known-signed zone with delv:
delv @<your-resolver> ietf.org A
"fully validated" → problem is zone-specific
"failure" → resolver DNSSEC pipeline has a broader problemThe DNSViz walkthrough lives on its own page: DNSViz: visualize the whole signature chain.
For the first step, nslookup gives you the same yes/no on SERVFAIL, just without the EDE code:
nslookup <name> 1.1.1.1** server can't find <name>: SERVFAILWhen you need the EDE code and dig is too old to show it, use curl DoH:
curl -s -H 'accept: application/dns-json' \
'https://cloudflare-dns.com/dns-query?name=<name>&type=A'The Comment field in the JSON response carries the EDE string verbatim.
Quick delv validation#
delv ships with the BIND utilities package. Unlike dig +dnssec, which shows raw DNSSEC records and lets you read them yourself, delv walks the chain on your behalf, validates each link against its own embedded trust anchor for the root, and prints a single-line verdict:
macOS note: the
delvbundled with macOS (BIND 9.10.6) was compiled without crypto support and printsno crypto supportinstead of validation results. Install a current version withbrew install bindto get a workingdelv.
nslookup cannot perform DNSSEC chain validation. Use
delvfor these checks.
delv @1.1.1.1 cloudflare.com A; fully validated
cloudflare.com. 300 IN A 104.16.132.229
cloudflare.com. 300 IN A 104.16.133.229
cloudflare.com. 300 IN RRSIG A 13 2 300 ...delv @1.1.1.1 +rtrace cloudflare.com A;; fetch: cloudflare.com/A
;; fetch: cloudflare.com/DNSKEY
;; fetch: cloudflare.com/DS
;; fetch: com/DNSKEY
;; fetch: com/DS
;; fetch: ./DNSKEY
; fully validated
...The +rtrace flag logs every fetch delv makes internally: DNSKEY lookups, DS checks, and the final record. Useful when you need to see exactly where the chain diverges. Two related flags per the delv manual: +vtrace shows the validator's step-by-step reasoning, and +mtrace dumps the full response messages.
delv @1.1.1.1 dnssec-failed.org A;; resolution failed: failuredelv @1.1.1.1 neverssl.com A; unsigned answer
neverssl.com. 60 IN A 34.223.124.45Because delv carries its own trust anchor, it tells you whether a correctly behaving validating resolver would accept the chain, even if the resolver you are testing through is itself misconfigured.
Confirm isitdns.net as a live working example of a fully anchored zone:
delv @1.1.1.1 isitdns.net A; fully validated
isitdns.net. 300 IN A 104.21.56.211
isitdns.net. 300 IN A 172.67.155.242
isitdns.net. 300 IN RRSIG A 13 2 300 ...Staying out of BOGUS: operational checklist#
- Automate signing. Whoever signs your zone needs a process that will not quietly stop. A managed DNS provider takes this off your plate. If you sign yourself, the signing job has to be a monitored scheduled task, not "I remember to re-sign before the 30-day window closes."
- Audit that the DS at the registrar matches your zone's KSK. Query the parent zone directly:
dig @a.gtld-servers.net isitdns.net DS ;; ANSWER SECTION:
isitdns.net. 86400 IN DS 2371 13 2 2DF8E1D4E0855DF60F2A8B51C914F2F2866ED1E3344F74F9682DAC4D42D18802 Then confirm the same keytag appears in your zone's DNSKEY (+multiline makes dig compute and print the key id):
dig @1.1.1.1 isitdns.net DNSKEY +multiline isitdns.net. 3600 IN DNSKEY 257 3 13 (
...
) ; KSK; alg = ECDSAP256SHA256 ; key id = 2371 Run this after any provider change, after any rollover, and on a regular schedule. If the keytag in the DS does not match any DNSKEY in the zone, validators will return SERVFAIL with EDE 9.
- NTP everywhere. Clock skew breaks RRSIG validity windows. Run NTP on every resolver and every authoritative server in the chain; drift on either end produces the same symptom.
- Keep a canary probe running.
dnssec-failed.orgreturningSERVFAILis a continuous "validation is on" heartbeat. If your monitoring ever seesNOERRORfrom that zone via your validator, validation has silently broken somewhere.
- Test before migrating. Any DNSSEC-relevant change (key rollover, algorithm change, provider migration) should be probed against a side zone first. Do not experiment on the production zone.
- Lower the negative-cache TTL before a migration. Only useful if you will be removing previously-existing names: the SOA
minimumfield controlsNXDOMAINcaching (RFC 2308), notSERVFAIL.SERVFAILcaching is configured on the resolver (capped at five minutes per RFC 9520) and you cannot shorten it from your zone.
See also#
- DNSSEC, signed and validated: the foundational page on how the trust chain works, and how DS records cross zone boundaries to anchor it at each delegation
- DNSViz: visualize the whole signature chain: what to reach for when EDE 6 is not enough
- SOA records: the
minimumTTL field and negative caching - NTP and DNS: why clock sync is a DNSSEC dependency
- nslookup and dig: tool reference for the commands on this page
- CAA records and TLSA records: both depend on DNSSEC being healthy