Your domain suddenly stops opening, and dig shows SERVFAIL. In many cases, DNSSEC is the cause. The DS record at your registrar no longer matches the keys on your nameservers.
This guide is a simple runbook for DNSSEC SERVFAIL stale DS troubleshooting. You will find the broken link, fix it, change DNSSEC or nameservers in the safe order, and wait the right TTL time.
Prepare the Tools and Work Folder
In this guide, example.com is the broken domain. Replace it with your own domain. We assume you have a running Ubuntu 26.04 or Ubuntu 24.04.
First, you must install the DNS tools, including dig and delv, which are in bind9-dnsutils, and dnssec-dsfromkey is in bind9-utils:
sudo apt updatesudo apt install bind9-dnsutils bind9-utils -y
Check the versions, time, and date with the commands below:
dig -vdelv -vtimedatectldate -u
You should see System clock synchronized: yes. If it says no, fix the time before anything else:
sudo timedatectl set-ntp true
Create a work folder and set variables. Keep every output in this folder, so you can compare before and after:
mkdir -p ~/dnssec-incident/{before,after}cd ~/dnssec-incidentexport DOMAIN="example.com"export PARENT="com"
PARENT is the zone that holds your DS record. For example.com it is com. For example.co.uk it is co.uk.
Then, save the current state. This is your evidence and your rollback data:
dig @1.1.1.1 "$DOMAIN" A +dnssec +comments | tee before/resolver-1.1.1.1.txtdig "$DOMAIN" NS +noall +answer | tee before/ns.txtdig "$DOMAIN" DS +noall +answer | tee before/ds.txt
DNSSEC SERVFAIL Stale DS Troubleshooting: Step-by-Step Runbook
Follow these steps in order. Each step checks one link in the DNSSEC chain, so you can find the exact break. Save every output in your work folder so you can compare it after the fix.
Step 1: Confirm the SERVFAIL
First, make sure the domain really returns SERVFAIL. Ask a few public resolvers that check DNSSEC, because they can give different answers:
dig @1.1.1.1 "$DOMAIN" A +dnssec +noall +comments +answer
Look at the status: line in each result, which tells you the reason:
Example Output:;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 41822
Try two more resolvers, because they can behave differently:
dig @8.8.8.8 "$DOMAIN" A +dnssec +noall +comments +answerdig @9.9.9.9 "$DOMAIN" A +dnssec +noall +comments +answer
Newer dig versions print an Extended DNS Error (EDE) in the OPT PSEUDOSECTION. It gives a reason. These are the common ones:
| EDE code |
Name |
What it means |
| 1 |
Unsupported DNSKEY Algorithm |
The resolver does not support your key algorithm |
| 2 |
Unsupported DS Digest Type |
The resolver does not support the DS digest type |
| 6 |
DNSSEC Bogus |
The validation failed |
| 7 |
Signature Expired |
The RRSIG end time is in the past |
| 8 |
Signature Not Yet Valid |
The RRSIG start time is in the future |
| 9 |
DNSKEY Missing |
The parent has a DS, but the zone has no matching key |
| 10 |
RRSIGs Missing |
The answer has no signatures |
| 12 |
NSEC Missing |
The proof of non-existence is missing |
Not every resolver returns an EDE. If you do not see one, continue to the next step.
Step 2: Test with Checking Disabled (+cd)
Now check if DNSSEC is the cause. The +cd option (checking disabled) tells the resolver to skip the DNSSEC check:
dig @1.1.1.1 "$DOMAIN" A +cd +noall +comments +answer
Compare the results with the normal query:
| Normal query |
Query with +cd |
Meaning |
| SERVFAIL |
NOERROR with an answer |
DNSSEC is the problem. Go to Step 3. |
| SERVFAIL |
SERVFAIL |
Not DNSSEC. Check if the nameservers are down, lame, or blocked by a firewall. |
| NOERROR |
NOERROR |
The domain works. Check other resolvers or wait for the cache. |
This one test is the base of DNSSEC SERVFAIL stale DS troubleshooting. It tells you if you are fixing the right problem.
Also, you can use delv. It validates DNSSEC by itself and gives the reason:
delv @1.1.1.1 "$DOMAIN" A +vtrace
If it works, you see ; fully validated. If it fails, you see resolution failed and the reason.
Step 3: Trace the Path with +trace
Next, follow the full DNS path. The +trace option starts at the root servers and goes down to your nameservers:
dig +trace +dnssec "$DOMAIN" A | tee before/trace.txt
- The
.com servers point to your nameservers. Check that they are the ones you expect. If not, the registrar has old or wrong NS records.
- If you see a DS line and its RRSIG, the parent says your domain is signed.
- Your nameservers answer last. If they time out or refuse, the problem is not DNSSEC.
Note: +trace does not validate anything. It only shows you the path and the data. Use it to look, not to decide.
Step 4: Check the DS Record at the Parent
Now check what the parent zone says about your domain. The DS record is stored at the parent, for example, in .com, and it points to your key. Ask a parent server directly, so you see the real DS and not a cached copy:
export PARENT_NS=$(dig +short NS "$PARENT" | head -n1)echo "$PARENT_NS"dig @"$PARENT_NS" "$DOMAIN" DS +norecurse +noall +answer | tee before/parent-ds.txtdig @"$PARENT_NS" "$DOMAIN" NS +norecurse +noall +authority | tee before/parent-ns.txt
Example Output:example.com. 86400 IN DS 2371 13 2 1F8A...C0DE
Read it from left to right:
- 86400 is the TTL in seconds. It tells you how long resolvers can keep the DS.
- 2371 is the key tag. It points to one DNSKEY.
- 13 is the key algorithm.
- 2 is the digest type. 2 is SHA-256.
- The last value is the digest, the fingerprint of the key.
Write down the TTL. You will need it later.
If this command shows no DS, the parent does not expect DNSSEC. Then a DNSSEC chain error is not the cause, and the problem is elsewhere, or only a cached DS is left.
Step 5: Check the DNSKEY on Every Nameserver
Now check the keys on your nameservers. Ask each nameserver for its DNSKEY records, one by one:
export NS_LIST=$(dig @"$PARENT_NS" "$DOMAIN" NS +norecurse +noall +authority | awk '$4=="NS"{print $5}')echo "$NS_LIST" for ns in $NS_LIST; do echo "--- $ns ---" dig @"$ns" "$DOMAIN" DNSKEY +norecurse +multiline +noall +answerdone | tee before/dnskey.txt
All of them must return the same keys, and one key must match the DS you found in Step 4. If the keys are different or missing on one server, resolvers can fail on some tries only.
If DNSKEY queries time out, but normal A queries work, a firewall may block large UDP answers or TCP port 53. Test TCP:
for ns in $NS_LIST; do echo "--- $ns ---" dig @"$ns" "$DOMAIN" DNSKEY +dnssec +tcp +norecurse +noall +comments | head -n 3done
Step 6: Match the DS with the DNSKEY
At this point, you should compare the DS at the parent with your real keys. Create a DS from the DNSKEY that your nameservers serve, then check it with the DS from Step 4:
for ns in $NS_LIST; do echo "--- $ns ---" dig @"$ns" "$DOMAIN" DNSKEY +norecurse +noall +answer | dnssec-dsfromkey -2 -f - "$DOMAIN"done | tee before/computed-ds.txt
If the command prints nothing, your zone has no key with flag 257 (KSK). If your zone only uses a key with flag 256, add -A to the command.
Now compare the four values: key tag, algorithm, digest type, and digest.
| Result |
What it means |
| Parent DS and computed DS are the same |
The DS is good. Go to Step 7. |
| No key tag matches |
Stale DS. The DS points to a key that your nameservers do not serve. |
| Key tag matches, algorithm is different |
Algorithm mismatch. |
| Key tag and algorithm match, digest is different |
Digest mismatch. The wrong digest or digest type was entered. |
| Parent DS exists, zone has no DNSKEY |
The zone is unsigned, but the parent still expects DNSSEC. |
Step 7: Check the RRSIG Dates
Even with a good DS, expired signatures give SERVFAIL. Ask for a signed record:
dig @"$(echo $NS_LIST | awk '{print $1}')" "$DOMAIN" SOA +dnssec +norecurse +noall +answer
The RRSIG line has the format type algorithm labels original-TTL expiration inception key-tag signer signature. The dates are in UTC as YYYYMMDDHHMMSS. Check them with the current time:
NOW=$(date -u +%Y%m%d%H%M%S)echo "Now: $NOW"for ns in $NS_LIST; do echo "--- $ns ---" dig @"$ns" "$DOMAIN" SOA +dnssec +norecurse +noall +answer | awk -v now="$NOW" '$4=="RRSIG"{ s = ($9 > now && $10 < now) ? "OK" : "BAD"; print s, "expires=" $9, "starts=" $10, "keytag=" $11 }'done
The result must be OK. This means the signature has started and has not expired yet. If it says BAD, your signer stopped re-signing the zone. Fix the signer (see the BIND section below) or contact your DNS provider.
Also, check that the keytag= value matches one of the keys in your DNSKEY set.
Step 8: Use a Visual Checker
Finally, check the domain with a visual tool, like DNSViz or the Verisign DNSSEC Debugger. These tools draw the DNSSEC chain and mark the broken link in red. Use them to confirm what you found with dig.
DNSSEC Diagnostic Script for Faster Troubleshooting
You can put all the checks above in one script. It tests the public resolvers, reads the DS at the parent, and checks the keys and signatures on every nameserver. Run it any time a domain returns SERVFAIL, and you get the main facts in a few seconds. Create the file with:
nano ~/dnssec-incident/dnssec-check.sh
Add the following script to the file:
set -uDOMAIN="${1:?Usage: $0 example.com [parent-zone]}"PARENT="${2:-${DOMAIN#*.}}" echo "== Resolver tests =="for r in 1.1.1.1 8.8.8.8 9.9.9.9; do n=$(dig @"$r" "$DOMAIN" A +dnssec +time=5 +tries=1 | awk '/status:/{gsub(",","",$6); print $6}') c=$(dig @"$r" "$DOMAIN" A +cd +time=5 +tries=1 | awk '/status:/{gsub(",","",$6); print $6}') echo "$r normal=$n checking-disabled=$c"done echo; echo "== DS at parent =="PNS=$(dig +short NS "$PARENT" | head -n1)dig @"$PNS" "$DOMAIN" DS +norecurse +noall +answer echo; echo "== Delegated nameservers =="NSLIST=$(dig @"$PNS" "$DOMAIN" NS +norecurse +noall +authority | awk '$4=="NS"{print $5}')echo "$NSLIST" NOW=$(date -u +%Y%m%d%H%M%S)for ns in $NSLIST; do echo; echo "== $ns ==" dig @"$ns" "$DOMAIN" DNSKEY +norecurse +noall +answer | awk '$4=="DNSKEY"{print "DNSKEY flags=" $5 " algorithm=" $7}' echo "Computed DS from this server:" dig @"$ns" "$DOMAIN" DNSKEY +norecurse +noall +answer | dnssec-dsfromkey -2 -f - "$DOMAIN" 2>/dev/null echo "Signature dates:" dig @"$ns" "$DOMAIN" SOA +dnssec +norecurse +noall +answer | awk -v now="$NOW" '$4=="RRSIG"{ s = ($9 > now && $10 < now) ? "OK" : "BAD"; print s, "expires=" $9, "starts=" $10, "keytag=" $11 }'done
Save the file, make it executable, and run it:
chmod +x ~/dnssec-incident/dnssec-check.sh~/dnssec-incident/dnssec-check.sh example.com com
Common DNSSEC Causes and Fixes
Most DNSSEC failures come from a small set of causes. Find your symptom in the table below to see the cause and the fix. Use the results from the steps above to confirm it before you change anything.
| Symptom |
Cause |
Fix |
| SERVFAIL after a nameserver change |
Stale DS at the registrar |
Replace or remove the DS (see below) |
| DS key tag not in DNSKEY set |
Keys were changed, or the zone was moved |
Replace the DS with one from the current key |
| Key tag matches, algorithm differs |
Wrong algorithm number in the DS |
Re-create the DS from the current DNSKEY |
| Key tag and algorithm match, digest differs |
Wrong digest, copy mistake, or other digest type |
Re-create the DS with digest type 2 |
| RRSIG expired |
The signer stopped signing |
Fix the signer and reload the zone |
| Works on some tries only |
Nameservers serve different data |
Make all nameservers serve the same signed zone |
| DNSKEY query times out |
Firewall blocks TCP 53 or large UDP |
Open UDP and TCP port 53 |
| Parent has DS, zone is unsigned |
DNSSEC was turned off in the wrong order |
Remove the DS, or sign the zone again |
Fix a Stale DS After Changing Nameservers
Stale DS is the top cause in DNSSEC SERVFAIL stale DS troubleshooting. It happens like this:
- The domain was signed at the old DNS provider. The registrar holds a DS for the old key.
- You changed the nameservers to a new provider.
- The new provider has different keys, or no keys at all.
- Validating resolvers still see the old DS. It does not match. They return SERVFAIL.
Pick one of the three fixes below.
Fix A: Remove the DS
It is the fastest way to restore the domain. Log in to your registrar. If you use PerLod, open your domain in the client area and go to the DNSSEC settings.
Delete the DS record and check that the parent no longer has it:
dig @"$PARENT_NS" "$DOMAIN" DS +norecurse +noall +answer
No output means the DS is gone at the parent. Resolvers may keep the old DS in cache until its TTL ends. Check the real status on public resolvers:
for r in 1.1.1.1 8.8.8.8 9.9.9.9; do echo "--- $r ---" dig @"$r" "$DOMAIN" A +noall +comments +answer | grep -E "status|^$DOMAIN"done
You can clear the cache of the big public resolvers: Google Public DNS cache flush and Cloudflare cache purge.
The domain works again, but it is now unsigned. You can turn DNSSEC on later, in the safe order below.
Fix B: Put the New DS at the Registrar
Use this if the new provider already signs your zone.
First, check that all new nameservers serve a DNSKEY and good signatures (Steps 5 and 7).
Then, get the new DS from your new DNS provider, or create it yourself:
dig @ns1.new-provider.example "$DOMAIN" DNSKEY +noall +answer | dnssec-dsfromkey -2 -f - "$DOMAIN"
At the registrar, delete the old DS and add the new one. Enter the four values: key tag, algorithm, digest type, and digest. Some registrars ask for the DNSKEY public key instead. Then, they build the DS for you.
Finally, run Step 4 and Step 6 again. The parent DS and the computed DS must be the same.
Fix C: Go Back to the Old Nameservers
If the old provider still has the signed zone and the same keys, set the old nameservers again at the registrar. The old DS then matches again.
Use this only when the old zone is still correct and signed. For more on rollback and zone copy checks, read the guide on changing nameservers without downtime.
Fix Algorithm and Digest Mismatches
Algorithm and digest errors are the second group in DNSSEC SERVFAIL stale DS troubleshooting. The DS has the right key tag but wrong numbers. A resolver needs all four values to match.
These are the common algorithm numbers:
| Number |
Name |
Use it? |
| 5 |
RSASHA1 |
No. It is old. |
| 7 |
RSASHA1-NSEC3-SHA1 |
No. It is old. |
| 8 |
RSASHA256 |
Yes, it is fine |
| 13 |
ECDSAP256SHA256 |
Yes. Best default choice. |
| 14 |
ECDSAP384SHA384 |
Yes |
| 15 |
ED25519 |
Yes, if your provider and registrar support it |
These are the digest types you will see most often:
| Number |
Name |
Use it? |
| 1 |
SHA-1 |
No |
| 2 |
SHA-256 |
Yes. Use this. |
| 4 |
SHA-384 |
Yes, if your registrar accepts it |
Fix a mismatch like this:
- Do not type the numbers manually. Create a new DS from your live DNSKEY with
dnssec-dsfromkey -2.
- Replace the old DS at the registrar with the new one.
- If the registrar does not accept your algorithm, switch the zone to an algorithm it supports. Follow the disable and re-enable steps in the next section, because changing the algorithm while a DS is live is risky.
One wrong character in the digest breaks the chain. Always copy and paste the DS.
Disable and Re-Enable DNSSEC in the Safe Order
Order is the most important rule in DNSSEC SERVFAIL stale DS troubleshooting. The parent (DS) and the child (DNSKEY) must never disagree for longer than the cache time.
The simple rule: the DS goes last when you turn DNSSEC on, and first when you turn it off.
Safe Disable Order
Use this order when you want to disable DNSSEC or when you switch to a new DNS provider. The DS record must go first, and the keys must go last. If you do it the other way, the DS has no key to match, and the domain returns SERVFAIL.
- Read the DS TTL from Step 4. Example: 86400 seconds (1 day).
- Remove the DS at the registrar.
- Keep the zone signed. Do not remove the keys yet. Resolvers that cached the old DS still need valid keys.
- Wait at least the DS TTL. A safer rule is 1.5 times the TTL. If the TTL is 24 hours, wait 36 hours.
- Confirm the DS is gone on public resolvers.
- Only now, turn off signing at your DNS provider or remove
dnssec-policy from the BIND zone.
- Verify that the zone has no DNSKEY and still resolves.
dig @"$PARENT_NS" "$DOMAIN" DS +norecurse +noall +answerdig @1.1.1.1 "$DOMAIN" DS +noall +answerdig @1.1.1.1 "$DOMAIN" A +dnssec +noall +comments +answer
Safe Re-Enable Order
Use this order when you turn DNSSEC on again. Sign the zone first, check that every nameserver serves the keys, and add the DS record last. This way, the DS always has a key to match when resolvers check it.
- Turn on signing at your DNS provider or set a
dnssec-policy in BIND. Prefer algorithm 13.
- Check that every nameserver returns the DNSKEY and good RRSIG records (Steps 5 and 7).
- Wait a short time so all nameservers and caches are in sync. If you use low TTLs (300 seconds), 10 to 15 minutes is enough.
- Create the DS and add it at the registrar.
- Check that the parent now shows the DS (Step 4) and that Step 6 matches.
- Check that resolvers validate the domain (see the next section).
Do Not Mix Nameserver and DNSSEC Changes
Do not change your nameservers and your DNSSEC settings at the same time. Each change has its own cache time, so the old and new data can overlap and break the chain. Make one change, wait for the TTL, check the result, and only then make the next one.
- Remove the old DS and wait for the DS TTL.
- Change the nameservers at the registrar.
- Wait for the old NS TTL to end (the parent NS TTL for
.com is often 2 days, so check yours with dig).
- Enable DNSSEC at the new provider.
- Add the new DS at the registrar.
How Long to Wait After DNSSEC Changes
TTL is the time, in seconds, that resolvers keep a record in their cache. After you change a DS, a nameserver, or a key, some resolvers still show the old data until the TTL ends. The table below shows what to wait for after each change, so you do not make the next step too early.
| What you changed |
What to wait for |
How long |
| Removed the DS |
DS TTL at the parent |
At least 1 DS TTL, safer 1.5 times |
| Changed nameservers |
Old NS TTL at the parent |
At least 1 NS TTL |
| Added signing |
Zone is the same on all nameservers |
Until all nameservers match, then add the DS |
| Added the DS |
DS TTL for the new DS to spread |
Check after 5 to 15 minutes; full result after the TTL |
| Replaced a bad DS |
Old DS TTL |
Up to the old DS TTL |
Read the real TTL values from your own dig output. They are the second number on each line:
dig @"$PARENT_NS" "$DOMAIN" DS +norecurse +noall +answerdig @"$PARENT_NS" "$DOMAIN" NS +norecurse +noall +authoritydig "$DOMAIN" DNSKEY +noall +answer
You can only change the TTL of your own zone records. The DS and NS TTL at the parent are set by the registry, and you cannot lower them.
Verify DNSSEC After the Fix
After you fix the problem, do not trust only your own computer. Your local resolver may still show old cached data. Check the domain on several public resolvers, confirm that DNSSEC validation passes, and compare the result with your previous files.
Check from the parent, from your nameservers, and from public resolvers:
dig @1.1.1.1 "$DOMAIN" A +dnssec +noall +comments +answerdig @8.8.8.8 "$DOMAIN" A +dnssec +noall +comments +answerdig @9.9.9.9 "$DOMAIN" A +dnssec +noall +comments +answer
A good result shows status: NOERROR. If the domain is signed, the flags: line also has ad (Authenticated Data), for example flags: qr rd ra ad. If the domain is unsigned on purpose, there is no ad flag. This is normal.
You can run delv for a clear answer:
delv @1.1.1.1 "$DOMAIN" A +vtrace
Look for ; fully validated. Then save the result and compare it with your saved files:
dig @1.1.1.1 "$DOMAIN" A +dnssec +comments | tee after/resolver-1.1.1.1.txtdiff before/resolver-1.1.1.1.txt after/resolver-1.1.1.1.txt
If you run your own resolver, clear its cache:
sudo rndc flushtree example.com sudo unbound-control flush_zone example.com sudo resolvectl flush-caches
Finally, check the website and mail:
curl -I --max-time 10 "https://$DOMAIN"dig "$DOMAIN" MX +short
Sign Your Own Zone with BIND 9.20
Skip this section if your DNS provider signs the zone for you. If you run your own authoritative server, BIND 9.20 can sign the zone and re-sign it automatically with dnssec-policy. This prevents expired signatures.
Install BIND and open the zone config with the commands below:
sudo apt updatesudo apt install bind9 bind9-utils bind9-dnsutils -ysudo nano /etc/bind/named.conf.local
Add the zone. Replace example.com with your domain:
zone "example.com" { type primary; file "/var/lib/bind/db.example.com"; dnssec-policy default; inline-signing yes;};
The folder /var/lib/bind/ is writable by BIND on Ubuntu. BIND keeps the signed copy of the zone there, and the keys are stored in /var/cache/bind/. Create the zone file:
sudo nano /var/lib/bind/db.example.com
Add your records. Use your own IPs and names:
$TTL 300@ IN SOA ns1.example.com. admin.example.com. ( 2026100601 ; serial (YYYYMMDDnn) 3600 ; refresh 600 ; retry 1209600 ; expire 300 ) ; negative cache TTL IN NS ns1.example.com. IN NS ns2.example.com.@ IN A 203.0.113.10www IN A 203.0.113.10ns1 IN A 203.0.113.11ns2 IN A 203.0.113.12@ IN MX 10 mail.example.com.mail IN A 203.0.113.20
Fix the file owner, then check the config and the zone:
sudo chown bind:bind /var/lib/bind/db.example.comsudo named-checkconfsudo named-checkzone example.com /var/lib/bind/db.example.com
Allow DNS through the firewall. Use both UDP and TCP, because signed answers are large:
sudo ufw allow 53/udpsudo ufw allow 53/tcp
Reload BIND and check the signing state:
sudo systemctl reload namedsudo rndc dnssec -status example.comsudo journalctl -u named -n 30 --no-pager
Test the signed zone on the server:
dig @127.0.0.1 example.com DNSKEY +multiline +norecurse +noall +answerdig @127.0.0.1 example.com A +dnssec +norecurse +noall +answer
Create the DS to give to your registrar:
dig @127.0.0.1 example.com DNSKEY +noall +answer | dnssec-dsfromkey -2 -f - example.com
Add the DS at the registrar. Then tell BIND that the DS is published, so it can continue its key plan:
sudo rndc dnssec -checkds published example.com
Every time you edit the zone file, raise the serial number first, check it with named-checkzone, and then reload:
sudo named-checkzone example.com /var/lib/bind/db.example.comsudo rndc reload example.com
Conclusion
A domain that returns SERVFAIL after a DNS change is usually a broken DNSSEC chain. Use +cd to prove it, then check the parent DS, the DNSKEY on every nameserver, and the RRSIG dates. A stale DS or a mismatched algorithm or digest is the most common cause.
The fix is almost always about order and waiting. Remove the DS first and wait for its TTL before you turn off signing. Sign first and add the DS last when you turn DNSSEC on. Never change nameservers and DNSSEC in the same minute. Keep this page as your DNSSEC SERVFAIL stale DS troubleshooting checklist.
When your domain is working again, read the DNSSEC and WAF configuration guide to set up DNSSEC and protect your server in the right way.
To keep your domain and DNS settings in one place, register and manage your domain through PerLod. Then use this guide whenever you change authoritative DNS or DNSSEC settings.