❓ What does SERVFAIL mean?
SERVFAILis a resolver saying it could not get you an answer it would stand behind. It is not "this name does not exist", and it is not always the zone's fault. Five things produce it, each one has adigthat tells it apart from the other four, and this page walks them in the order that costs you the least.
SERVFAIL is RCODE 2, and it is about the answer, not the name#
SERVFAIL is response code 2, defined in RFC 1035 section 4.1.1 as "Server failure - The name server was unable to process this query due to a problem with the name server."
That wording was written in 1987, for a server answering out of its own zone file. You almost never meet it there. You meet it on a recursive resolver, and on a recursive resolver "a problem with the name server" means the resolver could not assemble an answer it was willing to hand you: nothing left to ask, nothing that answered, or an answer that failed validation. The resolver itself is usually in perfect health.
The rcode it gets confused with is NXDOMAIN, response code 3, "Name Error" in the same section and named NXDOMAIN by RFC 2308 section 1, "an alternate expression for the Name Error RCODE". That one is a conclusion. SERVFAIL is the absence of one.
| rcode | Number | What the resolver is telling you |
|---|---|---|
NOERROR | 0 | here is the answer, or the name exists with nothing of that type |
SERVFAIL | 2 | I could not produce an answer I trust |
NXDOMAIN | 3 | this name does not exist, and I have that from an authority |
REFUSED | 5 | I will not do this for you, for policy reasons |
Two commands, side by side, so the difference is on your screen rather than in your memory. .invalid is a top-level domain reserved for exactly this (RFC 2606 section 2) and it will never be allocated to anyone, because RFC 6761 section 6.4 forbids registries from granting it, and bogus.probe.isitdns.net is a name in this site's own zone that is deliberately signed wrong:
dig +https @1.1.1.1 nothing.invalid A
dig +https @1.1.1.1 bogus.probe.isitdns.net AThe first returns NXDOMAIN and the second returns SERVFAIL. Same resolver, same second, two completely different statements about the world.
One more property to hold onto before you start retrying things: a SERVFAIL is cached. RFC 9520 section 3.2 requires resolvers to cache resolution failures for at least 1 second and forbids caching them longer than 5 minutes. So a fix can be live and correct while your next three queries still fail.
Step 1: read the EDE line before you guess#
A bare SERVFAIL names no cause. An Extended DNS Error (RFC 8914) is the same failure with a number and a sentence attached, and the big public resolvers send them now.
There is no flag to switch the display on. EDE rides in the EDNS OPT record, dig sends EDNS by default, and dig prints whatever comes back in the OPT pseudosection. +ednsopt is for putting an option into your query, not for reading one out of the response. What you do need is dig from BIND 9.16.4 or newer to see the line at all, and 9.18 or newer for the +https on the command below. Why every pasted command here carries +https is in step 2.
dig +https @1.1.1.1 +dnssec bogus.probe.isitdns.net A;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 22869
;; 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 bogus.probe.isitdns.net.)Four codes cover most of what you will meet on a SERVFAIL:
| EDE | Name | Reads as | Go to |
|---|---|---|---|
| 6 | DNSSEC Bogus | validation ran and failed, no further detail | step 2 |
| 9 | DNSKEY Missing | the parent's DS points at a key the child does not publish | step 2 |
| 22 | No Reachable Authority | none of the zone's servers answered | step 3 |
| 23 | Network Error | the resolver's path upstream broke: reset, timeout, unreachable | step 3 |
How much you get depends on which resolver you asked. Same name, same minute, measured 2026-09-06 over DoH:
1.1.1.1 EDE: 9 (DNSKEY Missing): (no SEP matching the DS found for bogus.probe.isitdns.net.)
8.8.8.8 EDE: 9 (DNSKEY Missing): (No DNSKEY matches DS RRs of bogus.probe.isitdns.net)
9.9.9.9 EDE: 9 (DNSKEY Missing)The code is the part that is on the wire and the part you quote into a ticket. The full registry, codes 0 to 35, and the reasons a failure can arrive with no EDE at all, are on extended-dns-errors.
Step 2: rule DNSSEC in or out with +cd#
+cd sets the Checking Disabled bit: the resolver hands back data whose signatures failed instead of refusing (RFC 4035 section 3.2.2). Run the query twice and let the resolver answer the question for you.
dig +https @1.1.1.1 +dnssec bogus.probe.isitdns.net A
dig +https @1.1.1.1 +dnssec +cd bogus.probe.isitdns.net AIf the second one succeeds, validation was the cause. That is not a resolver quirk, it is required: RFC 4035 section 5.5 says that when a recursive server cannot validate, it "MUST return RCODE 2 to the originating client". There is no soft-fail. The zone is simply gone for every validator on the internet until the signing is fixed.
Here is the +cd half of that pair, real:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14646
;; flags: qr rd ra cd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; ANSWER SECTION:
bogus.probe.isitdns.net. 5 IN A 192.0.2.66
bogus.probe.isitdns.net. 5 IN RRSIG A 13 4 3600 20260915000528 20260906210528 24762 bogus.probe.isitdns.net. XEJol39AUuXC9mo7dtj6Nv2yTr+SlmtYuXceC8RFkAPIJC58Afr1svcY 8ZziYn2nyPakEo8Qcnolrb8Co1KIwQ==The data was there the whole time. 192.0.2.66 is a documentation address (RFC 5737) and routes nowhere; the signature timestamps and the base64 change every time the zone re-signs, so yours will differ.
Measured 2026-09-06 from a single seat over DoH: SERVFAIL from 1.1.1.1, 8.8.8.8 and 9.9.9.9, and NOERROR with 192.0.2.66 from all three once +cd is set. bogus.probe.isitdns.net is kept broken on purpose, so it behaves this way every day. dnssec-failed.org is the long-standing external equivalent and is worth knowing too; both are worked through on dnssec-troubleshooting.
Know your vantage before you trust any of this. Every command on this page that leaves on port 53 can be answered by a box on your own network instead of the server you addressed, and the reply looks entirely normal. On the seat these captures came from,
dig @192.0.2.99 isitdns.net SOAand the same query with+tcpboth return a full SOA, counting down between runs, from an address that hosts nothing and is reserved for documentation (RFC 5737). Both UDP/53 and TCP/53 are being answered locally, and the serial handed back did not match the one the zone was serving at that moment.That is why every command on this page whose output is pasted carries
+https, which sends the query over DoH (RFC 8484) where an interceptor on port 53 cannot reach it. It needsdig9.18 or newer. The commands in steps 3, 4 and 5 cannot use it, which is why their output is described here instead of pasted: they have to reach authoritative servers, or the UDP path is the thing being tested. Those are the ones to run from a seat you have proved clean. See dot-doh and the interception check on the troubleshooting index.
If +cd changes nothing, DNSSEC is not your problem. Keep going.
Step 3: find out whether the zone's servers answer at all#
The other way to fail is to have nothing to validate. If every authoritative server for the zone is unreachable or silent, the resolver runs out of places to ask and returns SERVFAIL, usually with EDE 22 or EDE 23.
Two checks. First, walk it yourself from the root, which stops exactly where the resolver stopped:
dig +trace failing.example.com AThen ask each of the zone's own nameservers directly:
for ns in $(dig +short NS example.com); do
st=$(dig @"$ns" example.com SOA +norec +time=3 +tries=1 | grep -oE 'status: [A-Z]+')
echo "$ns -> ${st#status: }"
doneEach line prints the nameserver, an arrow, and its rcode. A server that times out prints nothing after the arrow, because there was no header to read a status out of. No lines at all, or lines that are plainly not nameserver names, is a different failure: the NS lookup that feeds the loop went through your resolver and failed too, so take the nameserver names out of the +trace output above and put them in by hand.
Read the result three ways. Every server timing out means the zone is off the air and there is nothing you can do from your side except report it. Some answering and some timing out is worse in practice: a resolver that happens to pick the dead one fails, the next query succeeds, and the bug is unreproducible for whoever is watching. Every server answering means reachability is fine, so move on.
Both commands go out over port 53 and both depend on the answers coming from the real servers, so run them from a path you have proved is not intercepted. check-an-auth is the long form of the second one.
Step 4: check the delegation is not lame#
A lame delegation is an NS record pointing at a server that does not serve the zone. RFC 9499 section 7 catalogues the term by quoting RFC 1912 section 2.8, then records that there is no consensus on what it covers today, so name the specific failure rather than the label. It answers, so it is not a timeout. It just has nothing useful to say.
dig @ns1.example.com example.com SOA +norecurseTwo shapes give it away:
status: REFUSED. "That is not my zone." The server is up, fast, and useless for this name.NOERRORwith noaaflag. It replied without claiming ownership, and theaabit is the whole point of the check (RFC 1035 section 4.1.1).
Neither shape breaks the zone outright when the other nameservers are healthy. It breaks a fraction of queries, which is why the report arrives as "it is slow sometimes" rather than "it is down". This query goes to an authoritative server on port 53 and cannot be sent over DoH, so it carries the same warning as step 3. check-a-delegation is the full walkthrough, parent and child compared side by side.
Step 5: squeeze the packet, for EDNS and fragmentation#
DNS over UDP caps at 512 bytes (RFC 1035 section 2.3.4). EDNS(0) lifts that by letting the client advertise a bigger buffer (RFC 6891 section 6.2.3). Most resolvers advertise 1232 today, a number that came out of DNS Flag Day 2020 rather than out of any RFC; RFC 9715 appendix A records that history and then revises the recommendation upward to 1400. A signed answer clears 512 bytes either way.
When something on the path drops the big UDP response or its fragments, the resolver gets nothing back and returns SERVFAIL. Two commands separate that from a real zone fault:
dig @1.1.1.1 +dnssec +bufsize=512 failing.example.com A
dig @1.1.1.1 +dnssec +tcp failing.example.com A+bufsize=512 advertises the small buffer, so the server sets the TC flag when the answer will not fit and dig retries over TCP. +tcp skips UDP altogether. If either of those works while the plain query fails, the zone is fine and the break is in the middle. Neither can go over DoH, because the UDP path is the thing under test, so an intercepting network will make the plain query "work" and walk you straight past a real fragmentation problem. Prove the seat first.
TCP is not optional here: RFC 7766 requires every DNS implementation to support it, so a firewall that permits UDP/53 and blocks TCP/53 is the misconfiguration, not the workload. RFC 6891 section 6.2.6 says the same thing about middleboxes that cannot carry EDNS.
Step 6: ask a different resolver#
The cheapest split in DNS triage. Ask three unrelated resolvers the same question and count the failures:
for r in 1.1.1.1 8.8.8.8 9.9.9.9; do
st=$(dig +https @"$r" bogus.probe.isitdns.net A | grep -oE 'status: [A-Z]+')
echo "$r -> ${st#status: }"
done1.1.1.1 -> SERVFAIL
8.8.8.8 -> SERVFAIL
9.9.9.9 -> SERVFAILThree failures means the zone. Nothing three independent operators share can be a local fault, and the identical EDE 9 from all three in step 1 is the corroboration.
One failure and two answers means that resolver. A forwarder pointed somewhere dead, a filtering policy, a stale failure still inside its five-minute cache window, or validation configured differently from everyone else's. check-a-resolver takes it from there, and if the odd one out is a resolver you run, check-a-forwarder is the next page.
Which side is failing#
| Symptom | The dig that shows it | Whose problem |
|---|---|---|
+cd turns the failure into an answer | dig +cd NAME A | the zone's signer |
EDE 6 or EDE 9 on the failure | dig +dnssec NAME A | the zone's signer |
every nameserver times out, or EDE 22 | the per-NS loop in step 3 | the zone's servers |
one nameserver returns REFUSED or drops aa | dig @NS ZONE SOA +norecurse | the zone's delegation |
EDE 23 on the failure | dig +dnssec NAME A | the resolver's path upstream |
+tcp or +bufsize=512 works, plain UDP does not | dig +dnssec +tcp NAME A | your network path |
| one resolver fails, two answer | the three-resolver loop in step 6 | your resolver |
| a server that cannot exist still answers | dig @192.0.2.99 NAME SOA +time=3 +tries=1 | your network, and every result above is suspect |
Quick reference#
The whole page as one block. Set the name first, then read down the output:
NAME=bogus.probe.isitdns.net
dig +https @1.1.1.1 +dnssec "$NAME" A
dig +https @1.1.1.1 +dnssec +cd "$NAME" A
for r in 1.1.1.1 8.8.8.8 9.9.9.9; do
st=$(dig +https @"$r" "$NAME" A | grep -oE 'status: [A-Z]+')
echo "$r -> ${st#status: }"
done
dig +trace "$NAME" ALine by line: the first query is the failure plus its EDE line, which often ends the investigation on its own. The second is the DNSSEC test, and an answer appearing there points at the signer. The loop says whether the failure is universal or yours alone. The +trace at the end walks from the root and stops at the step that broke, and it is the only line in the block that leaves on port 53, so trust it only from a seat you have checked for interception.
No terminal handy? The in-browser dig tool runs the same queries against every public resolver at once, and its manual covers what it can and cannot see.
See also#
- Extended DNS Errors: every EDE code, how to read one, and why a failure sometimes arrives bare
- DNSSEC troubleshooting: what to do once
+cdhas proved the signer is at fault - check-a-resolver, check-a-forwarder, check-a-delegation, check-an-auth: the four layers, one page each
- The dig phonebook: every command in this wiki, sorted by what you want to see
- dig flags:
+cd,+dnssec,+bufsize,+tcp,+traceand the rest - DoT, DoH and DoQ: the transports an on-path interceptor cannot answer for