DNSSEC SERVFAIL Fix: Stale DS, Broken Chain, Safe Nameserver Move

Updated on Oct 9, 2026
Mila H
20 MINS READ
Table of Contents
How to Fix DNSSEC SERVFAIL

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:

Bash
sudo apt updatesudo apt install bind9-dnsutils bind9-utils -y

Check the versions, time, and date with the commands below:

Bash
dig -vdelv -vtimedatectldate -u

You should see System clock synchronized: yes. If it says no, fix the time before anything else:

Bash
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:

Bash
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:

Bash
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:

Bash
dig @1.1.1.1 "$DOMAIN" A +dnssec +noall +comments +answer

Look at the status: line in each result, which tells you the reason:

Bash
Example Output:;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 41822

Try two more resolvers, because they can behave differently:

Bash
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:

Bash
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:

Bash
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:

Bash
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:

Bash
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
Bash
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:

Bash
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:

Bash
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:

Bash
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:

Bash
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:

Bash
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:

Bash
nano ~/dnssec-incident/dnssec-check.sh

Add the following script to the file:

Bash
#!/usr/bin/env bashset -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:

Bash
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:

Bash
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:

Bash
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:

Bash
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:

  1. Do not type the numbers manually. Create a new DS from your live DNSKEY with dnssec-dsfromkey -2.
  2. Replace the old DS at the registrar with the new one.
  3. 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.

  1. Read the DS TTL from Step 4. Example: 86400 seconds (1 day).
  2. Remove the DS at the registrar.
  3. Keep the zone signed. Do not remove the keys yet. Resolvers that cached the old DS still need valid keys.
  4. Wait at least the DS TTL. A safer rule is 1.5 times the TTL. If the TTL is 24 hours, wait 36 hours.
  5. Confirm the DS is gone on public resolvers.
  6. Only now, turn off signing at your DNS provider or remove dnssec-policy from the BIND zone.
  7. Verify that the zone has no DNSKEY and still resolves.
Bash
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.

  1. Turn on signing at your DNS provider or set a dnssec-policy in BIND. Prefer algorithm 13.
  2. Check that every nameserver returns the DNSKEY and good RRSIG records (Steps 5 and 7).
  3. 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.
  4. Create the DS and add it at the registrar.
  5. Check that the parent now shows the DS (Step 4) and that Step 6 matches.
  6. 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.

  1. Remove the old DS and wait for the DS TTL.
  2. Change the nameservers at the registrar.
  3. Wait for the old NS TTL to end (the parent NS TTL for .com is often 2 days, so check yours with dig).
  4. Enable DNSSEC at the new provider.
  5. 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:

Bash
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:

Bash
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:

Bash
delv @1.1.1.1 "$DOMAIN" A +vtrace

Look for ; fully validated. Then save the result and compare it with your saved files:

Bash
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:

Bash
# BIND resolversudo rndc flushtree example.com # Unboundsudo unbound-control flush_zone example.com # systemd-resolvedsudo resolvectl flush-caches

Finally, check the website and mail:

Bash
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:

Bash
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:

Bash
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:

Bash
sudo nano /var/lib/bind/db.example.com

Add your records. Use your own IPs and names:

Bash
$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:

Bash
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:

Bash
sudo ufw allow 53/udpsudo ufw allow 53/tcp

Reload BIND and check the signing state:

Bash
sudo systemctl reload namedsudo rndc dnssec -status example.comsudo journalctl -u named -n 30 --no-pager

Test the signed zone on the server:

Bash
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:

Bash
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:

Bash
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:

Bash
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.