Bali, Indonesia
Insights

Thinking DNSSEC Re-Signs Itself After Moving DNS to BIND, Until RRSIGs Expire

October 30, 2026
0:00
A common assumption during DNS migrations goes like this: the zone is already signed, so if you export it with all its records and load it into your own BIND server, DNSSEC keeps working. And it does, for a while. Then one day validating resolvers start returning SERVFAIL for the domain, and nobody connects it to a migration that happened weeks earlier. The flaw in the assumption is treating DNSSEC as a one-time property of the zone. It is closer to a subscription that has to be renewed constantly. Every signature has an expiry date, and something has to keep producing new ones.
When a zone is signed, each set of records (all the A records for a name, the MX set, the SOA, and so on) gets an RRSIG record. An RRSIG is the cryptographic signature over that record set, made with the zone's private key. Resolvers that validate DNSSEC check the signature against the public key published in the DNSKEY record. That key is in turn vouched for by the DS record at the parent zone, which your registrar submits to the TLD registry. Every RRSIG carries two timestamps: an inception time and an expiration time. A validating resolver rejects a signature outside that window, even if the cryptography is perfect. Expiry is deliberate. It limits how long an attacker could replay old, captured answers. So a signing system does two jobs. It signs the zone, and then it keeps re-signing before the old signatures run out. Your previous DNS provider was doing that second job in the background, and you never saw it.
The migration usually goes one of two ways. Either someone does a zone transfer or export from the old provider and gets a file full of RRSIG, DNSKEY, and NSEC or NSEC3 records alongside the normal ones. Or the export already includes the signed data because that is what the provider serves. That file gets loaded into BIND as a regular primary zone. BIND treats those DNSSEC records as plain data. It serves them exactly as they are. It does not re-sign them because nobody told it to, and in most cases it could not anyway. The private keys that made those signatures usually stay with the old provider, and you only exported public records. That is why the failure looks disconnected from the migration. The imported signatures were still inside their validity window, so validation kept passing. How long that lasts depends entirely on how the old signer set its validity period, which differs between providers. When the signatures expire, validating resolvers such as Google's 8.8.8.8 or Cloudflare's 1.1.1.1 start returning SERVFAIL. Non-validating resolvers and clients with cached answers may still reach the site. That mix is what makes the reports so confusing. Some users say the site is down, others say it is fine, and the web server looks healthy the whole time. When this lands at the support desk, the first instinct is often to check the web server, the firewall, or the SSL certificate. A faster first check is to ask a validating resolver directly, then repeat the same query with validation turned off.
The +cd flag (checking disabled) tells the resolver to skip DNSSEC validation. If the normal query returns SERVFAIL and the +cd query returns the right answer, the problem is DNSSEC, not your records. The third command asks your own authoritative server for the SOA record with DNSSEC data included, so you can see the signatures it is actually serving.
BASH
# Validating resolver: SERVFAIL here points at a validation problem
dig example.com A @8.8.8.8

# Same query with validation disabled: an answer here confirms it is DNSSEC
dig example.com A @8.8.8.8 +cd

# Ask your authoritative server directly and show the signatures it serves
dig example.com SOA @ns1.example.com +dnssec +noall +answer
In the last command's output, the RRSIG line follows this layout: owner name, TTL, class, the word RRSIG, the record type it covers, the algorithm number, the label count, the original TTL, the expiration time, the inception time, the key tag, the signer name, and then the signature itself. The two timestamps are in YYYYMMDDHHMMSS format and always in UTC. An illustrative line would begin like example.com. 3600 IN RRSIG SOA 13 2 3600 20261101000000 20261018000000 12345 example.com. .... Here the expiration is the first timestamp, and in this illustrative example it is 1 November. If the expiration date is in the past, you have found the problem. If it is a few days away and nothing on your server is configured to sign, you have found it early. Check more than one record type. The SOA and the DNSKEY set are good indicators, but they can be signed with different keys and on different schedules. Check every authoritative nameserver too, not only the primary. A secondary that stopped receiving transfers can keep serving stale signatures after the primary is fixed.
The real fix is to decide, before the cutover, which of two states the domain will be in afterwards. Copying signed records into a static zone is not one of them. The first option is to let BIND do the signing. On current BIND versions this is done with dnssec-policy, which generates keys, signs the zone, and re-signs automatically before signatures expire. The older auto-dnssec option was deprecated in favor of it. A minimal zone definition looks like this. Whether inline-signing must be stated explicitly depends on your BIND version, so check the documentation for the release you run.
TEXT
zone 'example.com' {
    type primary;
    file '/var/named/example.com.db';
    // Generate keys, sign, and keep re-signing using BIND's built-in policy
    dnssec-policy default;
    // Keep the unsigned file untouched; BIND maintains a signed copy
    inline-signing yes;
};
BIND's named.conf uses double quotes around names and paths. They are shown as single quotes here only for display, so use double quotes in your real config. The zone file should contain only your normal records. Strip out the imported RRSIG, DNSKEY, NSEC, and NSEC3 records so BIND is not serving someone else's signatures next to its own. There is a catch. BIND's new keys do not match the DS record currently at the registrar, which still points to the old provider's key. Until the DS is updated to match the new key, validators see a broken chain of trust and return SERVFAIL anyway. A mismatched DS after a provider change is its own well-known failure, covered in Kenapa Domain SERVFAIL Setelah Pindah DNS Provider yang Pakai DNSSEC. Doing this without any gap requires either a multi-signer setup where both providers publish each other's keys, or the second option below. The second option is to go unsigned for the move. Remove the DS record at the registrar first. Wait at least the DS record's TTL, plus some margin, so resolvers forget it. Then move the nameservers to BIND with a plain, unsigned zone. Once the zone is stable on BIND, enable dnssec-policy, let it sign, and publish the new DS. The domain is unprotected by DNSSEC for a short period, but it never goes dark. For many smaller setups this is the more predictable path. On a BIND server that is signing, rndc dnssec -status example.com shows the policy and key states in recent versions. That confirms BIND believes it is signing. It does not prove resolvers accept the result, so the external check below still matters.
Even with automatic signing, signing can stop quietly. Keys can become unreadable after a permissions change, the signed copy can fail to write because the disk is full, or a secondary can stop receiving transfers. A small daily check that reads the expiration directly from each authoritative server is cheap insurance.
BASH
#!/usr/bin/env bash
# Warn when RRSIGs served by an authoritative server are close to expiry.
# Usage: ./rrsig-check.sh example.com ns1.example.com 5
# Requires GNU date (the -d option).
zone="1"; ns="2"; warn_days="${3:-5}"
now=$(date -u +%s)
status=0

for type in SOA DNSKEY; do
  # Field 9 of an RRSIG answer line is the expiration time (YYYYMMDDHHMMSS, UTC)
  exps=(dig @"ns" "zone" "type" +dnssec +noall +answer | awk '4 == "RRSIG" {print9}')
  if [ -z "$exps" ]; then
    echo "CRITICAL: no RRSIG for zonetype on $ns"
    status=2
    continue
  fi
  for exp in $exps; do
    exp_s=(date -u -d "{exp:0:8} {exp:8:2}:{exp:10:2}:${exp:12:2}" +%s)
    days=$(( (exp_s - now) / 86400 ))
    if [ "days" -lt "warn_days" ]; then
      echo "WARNING: zonetype RRSIG expires in days day(s) (exp UTC)"
      [ "$status" -eq 0 ] && status=1
    fi
  done
done
exit $status
Run it from cron once a day against every authoritative nameserver for the zone, and route any non-zero exit to wherever your team actually reads alerts. Choose the warning threshold relative to your signer's refresh schedule. A healthy signer replaces signatures well before expiry, so expiry getting close usually means re-signing has already stopped. Pair the script with a plain dig example.com @8.8.8.8 status check. The script catches problems days in advance, and the resolver check confirms what real users see. If a domain also receives mail, an expired signature affects more than the website. Sending servers that validate DNSSEC defer or bounce mail just as browsers fail to resolve the site. That pattern is described in Why Inbound Mail Bounces After a DNSSEC Key Rollover Even With a Healthy Server. The practical takeaway is simple. When a signed zone moves, the signing job has to move with it, or be shut down cleanly by removing the DS first. Before any cutover, look at the RRSIG expiration dates, decide who signs afterwards, and set up a daily check that reads those dates from your own nameservers. Then the expiry date becomes a calendar item instead of an outage.