In a nutshell
Imagine every answer the DNS system hands you arrives in an envelope. Ordinary DNS envelopes have no seal: whoever passes you the envelope could have steamed it open, swapped the address inside, resealed it, and you would never know. DNSSEC adds a tamper-evident wax seal to every DNS answer. Each zone presses its own seal onto the records it owns, and a chain of seals — your zone’s seal vouched for by its parent, the parent’s by its parent, all the way up to the internet root — lets a resolver prove the answer in front of it is the genuine one the zone owner stamped, not a forgery slipped in along the way.
Two things follow from the seal metaphor, and both trip people up. First, DNSSEC seals the envelope; it does not hide what is inside. It gives you authenticity and integrity, never confidentiality — anyone can still read the address, they just cannot change it without cracking the seal (that is what DNS-over-HTTPS/TLS is for, and the two compose). Second, DNSSEC is fail-closed: if any seal in the chain is missing, broken, or expired, a validating resolver refuses the answer outright and returns SERVFAIL rather than risk handing you a forgery. That refusal is the entire security value — and the operational catch you will spend most of this lesson learning to respect, because a lapsed seal takes the whole zone down for everyone who validates.
The work has two halves that are easy to confuse. Signing protects the people who look up your zone. Validating protects your apps when they look up everyone else’s zones. You need both, and most teams only do the first.
Read it left to right: the parent zone publishes a DS fingerprint of your zone’s Key Signing Key, your keys sign the records, a validating resolver walks that chain of trust down from the root anchor, and the client gets either an AD-flagged (authenticated) answer or a fail-closed SERVFAIL.
Level: Advanced · Time: ~36 min
Prerequisites
- How recursive DNS resolution works (root → TLD → authoritative), the common record types (A, NS, SOA), and what a TTL does.
- Comfort with
digand reading a DNS response header — the flags line and the answer/authority sections. - A little public-key intuition: a private key signs, the matching public key verifies; a hash is a one-way fingerprint.
- Helpful siblings: Azure Private DNS Resolver & hybrid conditional forwarding and Private Endpoints & Private DNS at scale.
After this lesson you can
- Explain precisely what DNSSEC protects (integrity + origin authenticity) and what it does not (confidentiality).
- Sign a public zone with a KSK/ZSK split, choose the algorithm deliberately, and configure NSEC3 to current RFC guidance.
- Publish the
DSrecord at the parent and verify the live chain top-to-bottom withdelvanddnssec-verify. - Turn on validating resolution on Route 53, Azure DNS, and on-prem BIND/Windows, and prove it with the
ADflag and a known-broken test domain. - Roll ZSKs (pre-publish) and KSKs (double-DS) with zero validation gap.
- Recognise the SERVFAIL failure modes — broken DS, expired RRSIG, DNSSEC-stripping middlebox — and triage them in seconds.
DNSSEC is the only widely deployed mechanism that lets a resolver prove a DNS answer came from the zone’s owner and was not tampered with in flight. Without it, a recursive resolver takes whatever the network hands it: an on-path attacker, a poisoned cache, or a misconfigured forwarder can substitute an A record, and your TLS client connects to the wrong IP before the certificate is ever checked. Signing covers only half the problem; a signed zone that nobody validates is decoration. This guide does both halves: build the key hierarchy and signatures on the authority side, publish the chain of trust up to the registrar, then enable validating resolution on cloud and on-prem resolvers so forged answers are dropped before an app sees them.
The hard parts of DNSSEC are not the signing commands. They are the rollovers and the failure modes. A lapsed signature or a stale DS record does not degrade gracefully; it returns SERVFAIL to every validating client and blackholes the zone. So we spend real time on rollover choreography and failure drills.
1. How a resolver validates: the chain of trust
Validation walks a chain of cryptographic delegations from the root down to the record you asked for. Four record types carry it:
| Record | Lives in | Role |
|---|---|---|
DNSKEY |
The zone | Public keys. A ZSK (Zone Signing Key) and a KSK (Key Signing Key). |
RRSIG |
The zone | A signature over an RRset, made with a private key. Carries inception/expiration. |
DS |
The parent zone | A hash of the child’s KSK DNSKEY. This is the link across the delegation. |
NSEC / NSEC3 |
The zone | Authenticated denial of existence - proves a name does not exist. |
The walk for www.example.com:
. DNSKEY (trust anchor, baked into the resolver)
-> DS for com (signed by root's ZSK, verified by root RRSIG)
com DNSKEY
-> DS for example.com (signed by com's ZSK)
example.com DNSKEY (KSK signs the DNSKEY set; ZSK signs everything else)
-> RRSIG over the www A record (made by the ZSK)
Each step verifies the next link’s signature using a key whose hash the parent vouched for. The root key is the anchor every validating resolver ships with. Break any link - a DS that does not match the published KSK, an RRSIG past its expiration - and the resolver returns SERVFAIL, not a forged answer and not the real one. That fail-closed behavior is the entire security value, and also the operational risk.
NSEC and NSEC3 handle the “this name does not exist” case. You cannot sign a record that is not there, so the zone proves a gap. NSEC lists the next existing name in canonical order, which lets anyone walk the zone to enumerate every name; NSEC3 hashes names so the zone is not trivially enumerable.
2. Sign a public zone: KSK vs ZSK, algorithm, NSEC3
Split your keys. The KSK signs only the DNSKEY RRset and is the key the parent’s DS points at. The ZSK signs every other RRset. The split exists so you can roll the workhorse ZSK frequently without touching the registrar, and roll the KSK rarely - the KSK roll being the one that requires a parent DS update.
Pick the algorithm deliberately. ECDSA P-256 (algorithm 13) is the modern default: 256-bit keys and far smaller signatures than RSA, which keeps responses under the UDP fragmentation pain point and shrinks amplification. RSASHA256 (algorithm 8) is the conservative interoperable fallback; use it only if a resolver in your path genuinely lacks ECDSA support (rare in 2026). Do not use algorithm 7 (NSEC3RSASHA1) - SHA-1 is deprecated for DNSSEC.
If you run a hosted zone on Route 53 or Azure DNS, the provider manages keys and signing for you - you enable DNSSEC and it generates a KSK (and handles ZSK internally), so the manual
dnssec-keygenflow below is for self-hosted BIND. Route 53 uses ECDSA P-256; you still own the DS publication step at the registrar either way.
For a self-hosted BIND zone, generate the keys with ECDSA P-256:
# ZSK (zone signing key) - ZONE flag, signs the data
dnssec-keygen -a ECDSAP256SHA256 -n ZONE example.com
# KSK (key signing key) - KSK flag, signs the DNSKEY set, parent points here
dnssec-keygen -a ECDSAP256SHA256 -f KSK -n ZONE example.com
Modern BIND (9.16+) makes manual signing largely obsolete with dnssec-policy, which automates signing and rollovers inside named. The recommended path is to let BIND maintain it:
# named.conf
dnssec-policy "ecdsa-default" {
keys {
ksk lifetime P365D algorithm ecdsap256sha256;
zsk lifetime P90D algorithm ecdsap256sha256;
};
nsec3param iterations 0 optout no salt-length 0;
};
zone "example.com" {
type primary;
file "/var/named/example.com.zone";
dnssec-policy "ecdsa-default";
inline-signing yes;
};
A few non-obvious choices in that policy:
iterations 0for NSEC3. RFC 9276 is explicit: zero extra iterations. More iterations add resolver CPU cost for negligible security benefit and invite DoS. Ignore the old “10 iterations” advice.salt-length 0. The NSEC3 salt provides no meaningful protection against precomputation for a public zone and complicates rollovers. Empty salt is the current guidance.opt-out no. Opt-out lets you skip signing insecure delegations - only useful at TLD scale with many unsigned children. For a normal zone it weakens denial-of-existence proofs; leave it off.
If you must sign offline/manually instead, the signer is dnssec-signzone:
dnssec-signzone -A -3 - -N INCREMENT -o example.com -t example.com.zone
# -3 - : NSEC3 with empty salt; -N INCREMENT : bump the SOA serial
3. Publish the DS record and prove the chain
After the zone is signed, the parent (your registrar / TLD) must publish a DS record that hashes your KSK. Until that DS exists and matches, validating resolvers treat the zone as unsigned (insecure), not broken - so signing without publishing the DS gives you zero protection. Generate the DS from the KSK:
# From the signed zone, emit the DS for the active KSK (SHA-256, digest type 2)
dnssec-dsfromkey -2 Kexample.com.+013+12345.key
That prints a line like:
example.com. IN DS 12345 13 2 49FD9... (hex digest)
Hand that DS to the registrar. Most have a “DNSSEC / DS records” panel; some accept the full DNSKEY and compute the DS themselves. Critical: publish the DS only after the matching DNSKEY is live and propagated, and never remove a DS while clients may still be validating against that key. A DS that points at a key the zone no longer serves equals instant SERVFAIL.
Verify the live chain top to bottom with delv (the validating lookup tool that ships with BIND). Unlike dig +dnssec, delv actually performs validation against the root anchor and tells you the verdict:
delv @8.8.8.8 www.example.com A +rtrace
# Look for the verdict line:
# ; fully validated
; fully validated means the whole chain checked out. ; unsigned answer means no secure delegation exists (DS missing or insecure). Anything else is a break to chase. To inspect the zone’s own consistency before it goes live, use dnssec-verify:
dnssec-verify -o example.com example.com.zone.signed
It confirms every RRset has a valid RRSIG, the NSEC3 chain is complete, and signatures are within their validity window.
4. Automate rollovers without a validation gap
Keys must rotate. The danger is a window where a resolver has cached old material but the zone now serves only new - a self-inflicted SERVFAIL. The two roll types use different choreography.
ZSK rollover - pre-publish. The ZSK is not referenced by the parent DS, so you never touch the registrar:
- Publish the new ZSK in the DNSKEY set alongside the old one. Sign nothing with it yet.
- Wait at least the DNSKEY TTL so every resolver has both keys cached.
- Switch signing to the new ZSK (re-sign the zone). Resolvers already hold the key, so existing cached signatures still validate against the old key until they expire.
- After the max RRSIG TTL has passed, remove the old ZSK from the DNSKEY set.
KSK rollover - double-DS (double-signature at the parent). The KSK is what the DS points at, so the parent must learn the new key before you retire the old one:
- Generate the new KSK and add its DNSKEY to the zone. Both KSKs now sign the DNSKEY set.
- Publish the new KSK’s DS at the registrar so the parent now has two DS records. Either chain validates.
- Wait for the old DS’s TTL to expire from caches so no resolver is pinned to only the old DS.
- Remove the old DS at the registrar, then remove the old KSK from the zone.
With dnssec-policy, named runs both rolls automatically from the lifetime values - but it cannot publish the KSK’s DS at the registrar. That handoff is the one manual step (or an API call). BIND signals readiness; you watch key state and act on the parent:
# Show every key's rollover state and the timing of each phase
rndc dnssec -status example.com
# When BIND reports the new KSK needs its DS submitted, emit the new DS:
dnssec-dsfromkey -2 /var/named/keys/Kexample.com.+013+54321.key
# ...publish it at the registrar, then tell BIND the parent has it:
rndc dnssec -checkds -key 54321 published example.com
# and once the old DS is gone from the parent:
rndc dnssec -checkds -key 12345 withdrawn example.com
The single most common DNSSEC outage is an automated CDS/CDNSKEY pipeline (or a registrar’s auto-DNSSEC) rotating a key while the registrar’s DS update lags or silently fails. Treat the DS-at-parent step as a monitored, alerting workflow - not fire-and-forget. RFC 7344 CDS/CDNSKEY records let the parent poll for changes, but only some registrars honor them; verify yours does before relying on it.
5. Enable validating resolution: Route 53, Azure, on-prem
Signing protects clients of other resolvers. To protect your apps, your resolvers must validate. This is the half most teams skip.
Amazon Route 53 Resolver. Validation is configured at the resolver level via a Resolver DNSSEC validation config, attached to a VPC. With the AWS CLI:
aws route53resolver update-resolver-dnssec-config \
--resource-id vpc-0abc123 \
--validation ENABLE
With this enabled, the Route 53 Resolver validates DNSSEC for queries from that VPC and returns SERVFAIL for responses that fail validation. (To sign a Route 53 hosted zone, that is a separate action - aws route53 enable-hosted-zone-dnssec plus a KMS-backed KSK - covered in step 2.)
Azure DNS Private Resolver. It sits in front of Azure-provided DNS and validates on its recursive path - there is no per-zone toggle; validation applies to forwarded/public names it resolves. To sign an Azure Public DNS zone, enable DNSSEC on the zone (a managed capability) and publish the DS at your registrar as in step 3. Confirm from a workload behind the inbound endpoint:
# From a VM whose VNet uses the Private Resolver inbound endpoint:
dig +dnssec @<inbound-endpoint-ip> www.cloudflare.com A
# AD flag set in the header == the resolver validated the answer
The AD (Authenticated Data) flag in the response header is the wire-level proof of validation. No AD on a signed name means your resolver is not validating, even if upstream is signed.
On-prem BIND. Validation is on by default in modern BIND, but make it explicit:
# named.conf - recursive resolver
options {
recursion yes;
dnssec-validation auto; # built-in, RFC 5011-maintained root trust anchor
};
dnssec-validation auto tracks root KSK rollovers automatically. Avoid dnssec-validation yes with a hand-pasted static anchor unless you have a process to update it - a stale root anchor is a zone-wide outage waiting for the next root roll.
On-prem Windows Server DNS. Install the root trust anchor so the service can validate the chain, then require DNSSEC per namespace via a Name Resolution Policy Table (NRPT) rule:
# Install the root zone trust anchor (root KSK) on the DNS server:
Add-DnsServerTrustAnchor -Root
The validation requirement is pushed as a Group Policy NRPT rule (“Require DNSSEC validation in name and address data”) for the namespaces you care about; the trust anchor above is what lets the DNS service actually verify the signatures.
6. Negative caching, NSEC3 walking, and serve-stale
Three operational behaviors interact with DNSSEC in ways that bite later.
Negative-answer caching. Signed “does not exist” (NSEC3) proofs are cached like positive answers, bounded by the SOA minimum TTL (the last SOA field, RFC 2308). Too high and a newly added name is invisible for hours; too low and you lose the caching benefit. A 300-900s minimum is a sane public-zone default.
NSEC3 walking. NSEC3 hashes names, but offline dictionary attacks (nsec3walker/nsec3map-class tools) still recover many names. It raises the cost of enumeration; it does not make a zone secret. Do not put confidential names in a public signed zone assuming NSEC3 hides them.
Serve-stale. Returning expired cached data when the authority is unreachable can also mean serving an expired RRSIG, which a strict validator rejects. Keep the stale window short and lean on signature-expiry monitoring (step 8) rather than stale data to mask a signing lapse.
7. Failure drills
DNSSEC fails closed. Rehearse the three failures so the on-call recognizes them instantly.
Broken DS (parent points at the wrong key). The most common outage. The zone is signed and internally valid, but the registrar’s DS hashes a KSK the zone no longer serves. Every validating resolver SERVFAILs; non-validating ones are fine - hence the classic “works from my phone, not from the office.”
# Validators fail:
delv @1.1.1.1 www.example.com A # -> resolution failed: SERVFAIL / no valid signature
# The actual answer is fine to a non-validator:
dig +cd @1.1.1.1 www.example.com A # +cd = checking disabled -> returns the record
+cd returning the record while normal lookups SERVFAIL is the fingerprint of a DNSSEC break, not a server-down event.
Expired RRSIG. If re-signing stalls (a crashed cron, a dnssec-policy that lost access to its keys) the RRSIGs lapse and every validator SERVFAILs at expiry - a delayed-action outage that fires when the last signature ages out, often hours after the real failure.
# Show the RRSIG expiration so you can see how close you are:
dig +dnssec www.example.com A | grep RRSIG
# RRSIG fields 5/6 are expiration/inception as YYYYMMDDHHMMSS (UTC)
SERVFAIL as the client sees it. Applications get no “DNSSEC error” - just a generic resolution failure: getaddrinfo returns EAI_AGAIN/EAI_FAIL, browsers show DNS_PROBE_FINISHED_NXDOMAIN-style errors, libraries time out. The cause is invisible above the resolver, which is why the +cd test is what separates a validation failure from an ordinary outage in seconds.
Verify
Run these against a freshly signed zone before you call it done:
# 1. Zone signs cleanly and the NSEC3 chain is complete:
dnssec-verify -o example.com example.com.zone.signed
# 2. The full chain validates from the root anchor:
delv www.example.com A +rtrace # expect: ; fully validated
# 3. The parent DS matches the live KSK (no stale DS):
dig +dnssec example.com DS @<parent-ns> # DS at parent
dnssec-dsfromkey -2 Kexample.com.+013+*.key # compute from live KSK; digests must match
# 4. Your own resolvers actually validate (AD flag present):
dig +dnssec www.cloudflare.com A | grep -E 'flags:.* ad' # 'ad' in flags == validated
# 5. A known-broken signed test domain SERVFAILs (proves validation is on, not bypassed):
delv dnssec-failed.org A # expect: resolution failed (validation working)
Item 5 is the one teams forget: a resolver that is not validating will happily return the deliberately broken dnssec-failed.org record. If that lookup succeeds, your validation is off.
8. Monitor expirations and key state
The worst failure mode is silent: a signature lapse or DS mismatch nobody catches until clients SERVFAIL. Alert on time-to-expiry, not on the outage. Export RRSIG expiry to Prometheus (blackbox_exporter and dnssec-checks-style exporters surface dnssec_zone_rrsig_expiry_timestamp_seconds) and alert when the soonest expiry is inside a comfortable re-sign margin:
# Prometheus alert: fire well before any signature actually expires
groups:
- name: dnssec
rules:
- alert: DnssecRrsigExpiringSoon
# alert when the earliest RRSIG in the zone expires within 3 days
expr: (min by (zone) (dnssec_zone_rrsig_expiry_timestamp_seconds) - time()) < 259200
for: 15m
labels: { severity: critical }
annotations:
summary: "RRSIG for {{ $labels.zone }} expires in under 3 days"
If your validating resolvers are in Azure and stream query logs to Log Analytics, watch SERVFAIL rate - a validation failure shows up as a spike for an otherwise healthy name:
// Azure DNS Private Resolver query logs: SERVFAIL rate by queried name
DnsResolverQueryLogs
| where TimeGenerated > ago(1h)
| where ResponseCode == "SERVFAIL"
| summarize servfail = count() by QueryName = tostring(QueryName), bin(TimeGenerated, 5m)
| where servfail > 10
| order by TimeGenerated desc
And track DS-at-parent agreement on a schedule: compute the DS from your live KSK and compare it to what the parent publishes. Any drift is a pending outage.
Enterprise scenario
A platform team running a public .com zone on self-hosted BIND for a regulated SaaS turned on DNSSEC and validation across their estate. Months later, every internal service that resolved through the on-prem corporate resolvers went dark for one of their own signed subdomains - but only internally; external customers were unaffected. The constraint that caused it: their corporate resolvers were configured with conditional forwarders that pointed at an internal DNS appliance which stripped DNSSEC records (RRSIG/DNSKEY) from responses to “simplify” answers. To the validating BIND resolvers downstream, a signed zone arriving with no RRSIGs is indistinguishable from tampering, so they SERVFAILed - fail-closed, exactly as designed. Customers used public validating resolvers (which got intact records) and saw nothing.
The root cause was a middlebox that did not preserve the DNSSEC RRsets or handle the DO bit / large EDNS correctly. The fix had two parts: make the forwarding path DNSSEC-transparent, and validate in one place rather than at every hop. They pointed the corporate resolvers straight at a DNSSEC-preserving upstream:
# Corporate BIND resolver: forward to a DNSSEC-transparent upstream, keep validating locally
options {
dnssec-validation auto; # validate here, once
};
zone "internal.example.com" {
type forward;
forward only;
forwarders { 10.20.0.53; }; # upstream that preserves RRSIG/DNSKEY (DO bit honored)
};
The durable lesson: anything on the resolution path that does not preserve DNSSEC records breaks validation downstream. Old DNS proxies, some load balancers’ DNS modules, and “DNS firewall” appliances that filter record types are the usual suspects. Validate in as few places as possible, and make every forwarder in front of those validators DNSSEC-transparent with a large enough EDNS buffer to carry the signatures.
Going deeper
The attack DNSSEC actually stops (and the one it doesn’t)
DNSSEC exists to defeat answer forgery: cache poisoning and on-path spoofing. The canonical example is the Kaminsky attack (2008). A recursive resolver that asks for random123.example.com can be raced by an attacker who floods forged replies guessing the query’s transaction ID; a lucky guess plants not just that name but poisoned NS/glue for the whole example.com zone in the cache, redirecting every later lookup. Source-port randomisation and 0x20 case-mixing raised the number of bits an attacker must guess, but they are statistical speed bumps — with enough packets the race is still winnable. Only a cryptographic signature over the RRset closes it for good: a forged answer has no valid RRSIG chaining to the root, so a validator drops it.
What DNSSEC deliberately does not do is hide the query or the answer. The name you look up and the address you get back travel in cleartext; a passive observer sees everything. Confidentiality is a different layer — DNS-over-TLS (DoT), DNS-over-HTTPS (DoH), and DNS-over-QUIC encrypt the client-to-resolver hop. The two are complementary, not alternatives: DoH stops your ISP from reading your lookups; DNSSEC stops anyone from altering the answer. A privacy-conscious design uses both — encrypted transport to a resolver that validates.
Enabling DNSSEC on Azure Public DNS, end to end
Azure Public DNS zone signing is generally available. It is a fully managed, “online-signing” model: Azure owns and rotates both the KSK and ZSK for you (ECDSA P-256 / algorithm 13), signs with NSEC3, and maintains the RRSIGs — you never run dnssec-keygen or a cron. Your job shrinks to two steps: turn it on, and publish the DS at the parent.
# Enable DNSSEC signing on an existing Azure Public DNS zone (Azure manages KSK + ZSK):
az network dns dnssec-config create \
--resource-group rg-dns \
--zone-name example.com
# Confirm the signing state and read back the signing-key material:
az network dns dnssec-config show \
--resource-group rg-dns \
--zone-name example.com
Once signing is enabled, Azure serves the DNSKEY set at the zone apex. You now need the DS record to hand to whoever runs the parent zone (your registrar for a .com, or the parent Azure zone if this is a delegated subdomain). The portal’s DNSSEC blade prints a copy-paste-ready DS line; from the wire you can read the published KSK and derive it:
# Read the live DNSKEY set Azure now publishes, then derive the DS (digest type 2 / SHA-256):
dig +short DNSKEY example.com @ns1-01.azure-dns.com
# Feed the KSK (flags 257) into dnssec-dsfromkey to get the DS, or copy the DS from the portal blade.
Paste that DS into the registrar’s DNSSEC panel. Until it lands and propagates, validating resolvers see the zone as insecure (unsigned), so you have signing with no protection yet — the DS is the switch that arms the chain. To turn signing off safely, reverse the order: remove the DS at the registrar first, wait for its TTL to drain from caches, and only then az network dns dnssec-config delete. Delete the config while a DS still points at the zone and every validator SERVFAILs instantly — the same fail-closed cliff as a botched KSK roll.
Cross-provider chains and the hybrid angle
The chain of trust does not care who runs which zone — only that each parent holds a DS matching its child’s KSK. That is what makes hybrid and multi-provider setups work and what makes them fragile. A subdomain internal.example.com delegated to Azure while example.com is signed on Route 53 is fine: the parent (Route 53) simply publishes a DS for the Azure-signed child. You cannot “half-sign,” though — an unsigned link anywhere breaks secure validation for everything below it (the zone falls back to insecure, not broken, but you lose protection).
| Aspect | Self-hosted BIND | Azure Public DNS | Amazon Route 53 |
|---|---|---|---|
| Key ownership | You own KSK + ZSK | Azure-managed (KSK + ZSK) | You create the KSK (KMS ECC_NIST_P256); AWS manages the ZSK |
| Algorithm | Your choice (ECDSA P-256 recommended) | ECDSA P-256 (alg 13) | ECDSA P-256 (alg 13) |
| Turn on signing | dnssec-policy in named.conf |
az network dns dnssec-config create |
create-key-signing-key + enable-hosted-zone-dnssec |
| DS publication | You, at the registrar | You, at the registrar (portal shows the DS) | You, at the registrar |
| ZSK rollover | Automatic (dnssec-policy) |
Automatic (managed) | Automatic (managed) |
| KSK rollover | Automatic + manual DS handoff | Managed; DS handoff still yours | Manual KSK roll + DS handoff |
| CDS/CDNSKEY automation | Emitted by BIND | Verify current support before relying on it | Verify current support before relying on it |
The recurring hybrid failure is not the signing — it is a resolution path that is not DNSSEC-transparent (the enterprise scenario above). When on-prem resolvers forward to a cloud resolver, or a cloud resolver conditional-forwards to on-prem, every hop must set the DO bit, carry RRSIG/DNSKEY intact, and negotiate a large enough EDNS buffer (or fall back to TCP). One record-stripping middlebox anywhere in that chain turns a signed answer into apparent tampering downstream.
Algorithm rollover is harder than key rollover
Rolling a key keeps the same algorithm; rolling the algorithm (say ECDSA P-256 → P-384, or off legacy RSA) is a stricter dance because a validator must be able to verify every RRset with an algorithm it recognises. RFC 6781’s conservative approach: introduce the new algorithm’s DNSKEY and pre-generate RRSIGs for the new algorithm over the whole zone before any resolver could see only-new material; keep both algorithms fully signing in parallel; swap the parent DS to the new KSK; drain TTLs; then withdraw the old algorithm’s signatures and DNSKEY. Skip the parallel-signing window and a resolver that fetched the new DNSKEY but a cached old-algorithm RRSIG (or vice versa) fails validation. Managed providers hide this, which is a good reason to let Azure or Route 53 own the keys unless you have a hard requirement to self-host.
Automating the DS handoff: CDS/CDNSKEY and RFC 5011
The one step no signer can do for you is updating the DS in a zone you do not control. Two mechanisms narrow the gap. CDS/CDNSKEY (RFC 7344, RFC 8078) let the child publish its desired DS as a signed record in its own zone; a cooperating parent or registrar polls for it and updates the real DS automatically — turning a manual registrar click into a monitored pipeline. Support is uneven, so verify your registrar honours it before trusting it. For the root trust anchor, resolvers use RFC 5011 automated updates: dnssec-validation auto watches the root KSK’s own rollover signalling and adopts the new anchor without a human editing a file — which is exactly why you prefer auto over a hand-pasted static anchor that goes stale at the next root roll.
NSEC vs NSEC3, zone-walking, and aggressive caching
Authenticated denial is subtler than it looks. NSEC proves “no name exists between a.example.com and c.example.com,” which is trivially walkable — request names in order and enumerate the entire zone. NSEC3 hashes the names before ordering them, raising enumeration cost, but offline GPU dictionary attacks still recover common labels, so treat it as obfuscation, not secrecy. Some large providers avoid the problem entirely with minimally-covering NSEC / “compact denial” (white/black lies, RFC 4470-style): they synthesise a tiny signed proof on the fly for each negative answer instead of pre-computing an NSEC(3) chain, so there is nothing to walk. A related win is aggressive NSEC caching (RFC 8198): a validating resolver that has a signed NSEC/NSEC3 span can answer other non-existent names in that range straight from cache, cutting queries to the authority and shrinking the attack surface for random-subdomain floods.
The validation path on the wire: DO, CD, AD, and buffer sizing
Three header bits carry DNSSEC. A client/resolver sets the DO (DNSSEC OK) bit in the EDNS0 record to say “send me the RRSIGs.” The resolver returns AD (Authenticated Data) when it validated the chain, and honours CD (Checking Disabled) from a client that wants the raw answer without validation (dig +cd) — the exact switch that distinguishes a DNSSEC break from a server outage. Because signatures inflate responses, EDNS buffer sizing matters: too large and fragmented UDP gets dropped by some firewalls; too small and you force TCP fallback on every signed answer. Modern guidance is a moderate advertised buffer (commonly 1232 bytes) with clean TCP fallback — which every forwarder in the path must also permit. When a resolver hits a genuinely broken but important zone, operators can apply a negative trust anchor (RFC 7646) to temporarily treat that one zone as insecure until the owner fixes their chain — an escape hatch, never a default.
Where the money and the risk actually sit
Signing itself is nearly free on a managed provider (Azure and Route 53 bill DNSSEC-signed zones the same as unsigned, plus normal query volume; a KMS-backed KSK on Route 53 adds a small per-key KMS charge). The real cost is operational: the on-call burden of a fail-closed system, the monitoring you must build (RRSIG time-to-expiry, DS-vs-live-KSK drift, SERVFAIL rate), and the blast radius of a mistake — a single lapsed signature or stale DS takes down every validating client for the whole zone at once. That asymmetry is why the discipline in sections 4, 7 and 8 matters more than the signing commands, and why “let the managed provider own the keys and rollovers” is the right default for most teams.
Practice challenges
Work these in order — they escalate from reading a header to designing a gap-free rollover. Each has a solution with the reasoning.
<details> <summary>1. (Beginner) Prove that a public zone is signed and that your resolver validated it.</summary>
Pick a known-signed zone (cloudflare.com, ietf.org, iana.org) and show both the signature and the validation verdict.
dig +dnssec cloudflare.com A # RRSIG lines present == the zone is signed
dig +dnssec cloudflare.com A | grep -E 'flags:.* ad' # 'ad' in flags == YOUR resolver validated
delv cloudflare.com A # expect: ; fully validated
Why: the RRSIG proves the zone signed; the AD flag and delv’s “fully validated” prove your resolver actually walked the chain — the two halves you must never conflate. If there is no ad flag, your resolver is not validating even though the zone is signed.
</details>
<details> <summary>2. (Beginner) Distinguish a signed zone from an unsigned one, and show that DNSSEC is not encryption.</summary>
dig +dnssec cloudflare.com A | grep RRSIG # signed: RRSIG present
dig +dnssec example.org A | grep RRSIG # try an unsigned zone: no RRSIG line
Why: the presence/absence of RRSIG is the on-the-wire tell of whether a zone is signed. Note that in both cases the A record itself is plainly visible — DNSSEC added a signature, it did not hide the answer. Confidentiality would need DoT/DoH, a separate layer. </details>
<details> <summary>3. (Intermediate) Turn on DNSSEC for an Azure Public DNS zone and produce the DS for the registrar.</summary>
az network dns dnssec-config create \
--resource-group rg-dns --zone-name example.com
az network dns dnssec-config show \
--resource-group rg-dns --zone-name example.com # confirm signing state
# Get the DS: copy it from the portal DNSSEC blade, or derive it from the live KSK:
dig +short DNSKEY example.com @ns1-01.azure-dns.com # then dnssec-dsfromkey on the flags-257 key
Why: Azure manages the KSK/ZSK, so enablement is one command — but the chain is not armed until you paste the DS at the parent/registrar. Signing without the DS is zero protection; that handoff stays yours on every provider. </details>
<details> <summary>4. (Intermediate) A name SERVFAILs. Prove whether it is a DNSSEC break or an ordinary outage.</summary>
dig @1.1.1.1 www.example.com A # SERVFAIL
dig +cd @1.1.1.1 www.example.com A # +cd bypasses validation
If +cd returns the record while the normal lookup SERVFAILs, it is a validation failure (broken DS or expired RRSIG), not a dead server. Confirm which:
dig +dnssec example.com DS @<parent-ns> # what the parent vouches for
dig +short DNSKEY example.com # what the zone actually serves — do they match?
dig +dnssec www.example.com A | grep RRSIG # is the RRSIG past its expiry (field 5)?
Why: +cd is the single fastest triage in DNSSEC. A record appearing only with checking disabled is the fingerprint of a broken chain; then DS-vs-KSK mismatch points to a stale DS, and a past-expiry RRSIG points to a stalled re-signer.
</details>
<details> <summary>5. (Advanced) Design a ZSK rollover that never opens a validation gap.</summary>
Pre-publish sequence — the registrar is never touched:
- Add the new ZSK to the DNSKEY set alongside the old one; sign nothing with it yet.
- Wait ≥ the DNSKEY TTL so every resolver has cached both public keys.
- Switch signing to the new ZSK and re-sign the zone. Cached RRSIGs made by the old ZSK still validate — resolvers hold that key.
- Wait ≥ the max RRSIG TTL so no old-key signature can still be cached, then remove the old ZSK.
Why: at every instant a resolver has some cached key that validates some cached signature. The two TTL waits are the whole trick; skipping either strands a resolver with a signature it cannot verify → self-inflicted SERVFAIL. With BIND dnssec-policy this runs automatically from the ZSK lifetime.
</details>
<details> <summary>6. (Advanced) Design a KSK rollover across a registrar that supports CDS automation, and name the go/no-go check.</summary>
Double-DS with a CDS-driven parent update:
- Generate the new KSK; add its DNSKEY so both KSKs sign the DNSKEY set.
- Publish a CDS/CDNSKEY record for the new KSK (RFC 7344). The registrar polls it and adds a second DS — the parent now carries two DS records; either chain validates.
- Go/no-go: confirm the new DS is live at the parent and its predecessor’s cache TTL has drained before touching the old key —
dig +dnssec example.com DS @<parent-ns>must show both, and you must wait out the old DS TTL. - Withdraw the old KSK’s CDS (or signal removal); once the registrar drops the old DS and its TTL expires, remove the old KSK from the zone.
Why: the parent must learn the new key before you retire the old one, or resolvers pinned to the old DS SERVFAIL. CDS turns the manual registrar click into a monitored pipeline — but only if the registrar honours it, which is exactly the step that silently fails and causes most DNSSEC outages, so it must alert. </details>
Common beginner mistakes
- “DNSSEC encrypts my DNS traffic.” No — it authenticates, it does not hide. The query and answer are still cleartext. Right model: DNSSEC = a tamper-evident seal (integrity + origin authenticity); use DoH/DoT/DoQ if you also need the envelope hidden.
- “I signed the zone, so I’m protected.” Signing without publishing the matching DS at the parent leaves validators treating the zone as insecure (unsigned) — zero protection. And even a perfect DS only protects people who look up your zone; it does nothing for your apps until your own resolvers validate. Two separate halves.
- “Turning on validation protects my zone.” Validation protects your clients when they resolve other people’s zones. Signing protects consumers of your zone. Enabling one does not do the other’s job.
- “SERVFAIL means the DNS server is down.” Often it is a broken chain, not a dead server. Right move:
dig +cd— if the record comes back with checking disabled, it is a DNSSEC validation failure, and the server is fine. - “NSEC3 keeps my internal names secret.” NSEC3 raises the cost of enumeration; it does not hide names — offline attacks still recover many. Never put confidential hostnames in a public signed zone assuming NSEC3 conceals them.
- “More NSEC3 iterations = more secure.” RFC 9276 says use 0 extra iterations and an empty salt. Extra iterations burn resolver CPU for negligible benefit and are a DoS amplifier. The old “10+ iterations” advice is obsolete.
- “I’ll just delete the old key/DS as soon as I add the new one.” Caches still hold the old material for a full TTL. Remove a key or DS before that drains and you strand validating resolvers → self-inflicted SERVFAIL. Every rollover step is gated on a TTL wait.
- “A validating resolver behind any forwarder is fine.” Any middlebox that strips RRSIG/DNSKEY, ignores the DO bit, or truncates large EDNS responses looks exactly like tampering to the validator downstream, and it fails closed. Keep the whole path DNSSEC-transparent and validate in as few places as possible.
- “Let the automation roll keys and forget it.” The DS-at-parent handoff is the one step automation frequently cannot complete (registrar API gaps, CDS not honoured). Treat it as a monitored, alerting workflow — an unwatched auto-roll whose DS update silently lags is the single most common DNSSEC outage.
Glossary
- DNSSEC — DNS Security Extensions: cryptographic signatures over DNS records that let a resolver prove an answer is authentic and unmodified. Integrity + origin authenticity, not confidentiality.
- RRset — all records of one name+type served together (e.g. every A record for
www.example.com). DNSSEC signs whole RRsets, not individual records. - RRSIG — the signature over an RRset, carrying an inception and expiration time. Expired RRSIG → validators SERVFAIL.
- DNSKEY — the public keys a zone publishes so resolvers can verify its RRSIGs. Contains the ZSK and the KSK.
- KSK (Key Signing Key) — signs only the DNSKEY RRset; the key the parent’s DS points at. Rolled rarely (double-DS) because a roll needs a registrar update.
- ZSK (Zone Signing Key) — signs every RRset except the DNSKEY set. Rolled often (pre-publish), with no registrar involvement.
- DS (Delegation Signer) — a hash of the child’s KSK, published in the parent zone. The single cryptographic link across a delegation; no DS = the child is treated as unsigned.
- Chain of trust — the sequence of DS→DNSKEY→RRSIG links from the root anchor down to the record you asked for. Every link must verify or the answer is rejected.
- Trust anchor — a key a resolver trusts a priori without a parent to vouch for it. In practice the root KSK, shipped with the resolver and kept current via RFC 5011.
- NSEC / NSEC3 — authenticated denial of existence: signed proof that a name (or type) does not exist. NSEC lists the next name (walkable); NSEC3 hashes names to raise enumeration cost.
- NSEC3 opt-out — a mode that skips signing insecure delegations; useful only at TLD scale, weakens denial proofs for a normal zone (leave it off).
- AD flag (Authenticated Data) — set by a resolver in its reply to say “I validated the DNSSEC chain for this answer.” Absence on a signed name means your resolver is not validating.
- CD flag (Checking Disabled) — set by a client (
dig +cd) to ask for the raw answer without validation. The fastest way to tell a DNSSEC break from a server outage. - DO bit (DNSSEC OK) — an EDNS0 flag a resolver sets to request that RRSIGs be included. A middlebox that ignores it strips signatures and breaks validation downstream.
- EDNS0 — the extension mechanism that carries the DO bit and a larger UDP buffer size so signed (bigger) responses fit without always falling back to TCP.
- SERVFAIL — the generic “server failure” response a validating resolver returns when the chain does not validate. DNSSEC is fail-closed: it returns SERVFAIL, never a forged or unverified answer.
- Secure / insecure / bogus — a validator’s three verdicts: secure = chain validated; insecure = provably unsigned (no DS); bogus = signed but the signature/chain is broken → SERVFAIL.
- Pre-publish — the ZSK-rollover technique: publish the new key and wait a TTL before signing with it, so resolvers cache it in advance.
- Double-DS (double-signature) — the KSK-rollover technique: publish the new DS at the parent (two DS records live) before retiring the old KSK, so either chain validates through the switch.
- CDS / CDNSKEY — child-published records (RFC 7344/8078) that tell a cooperating parent/registrar what DS to set, automating the handoff — if the registrar honours it.
- RFC 5011 — the standard for automated root trust-anchor updates; what
dnssec-validation autouses to survive root KSK rollovers without manual edits. - Algorithm rollover — changing the signing algorithm (not just the key); stricter than a key roll because both algorithms must fully sign in parallel through the switch (RFC 6781).
- Negative caching / SOA minimum — how long a resolver caches a signed “does not exist” answer, bounded by the last SOA field (RFC 2308).
- Aggressive NSEC caching — RFC 8198: a resolver uses a cached signed NSEC/NSEC3 span to answer other non-existent names in that range without re-querying the authority.
- Negative trust anchor (NTA) — RFC 7646: an operator’s temporary override to treat one broken zone as insecure until its owner fixes the chain. An escape hatch, never a default.
- Serve-stale — returning expired cached data when the authority is unreachable; risky under DNSSEC because it can serve an expired RRSIG a strict validator rejects.
delv— BIND’s validating lookup tool; unlikedig +dnssec, it performs full validation against the root anchor and prints “; fully validated”.dnssec-verify— BIND tool that checks a signed zone file’s internal consistency (every RRset signed, NSEC3 chain complete, signatures in-window) before it goes live.- Zone-walking — enumerating every name in a zone by following NSEC (trivial) or brute-forcing NSEC3 hashes (costly but feasible). Why NSEC3 is obfuscation, not secrecy.
- ECDSA P-256 (algorithm 13) — the modern default signing algorithm: small keys and signatures, wide support. Used by Azure DNS and Route 53.
- RSASHA256 (algorithm 8) — the conservative RSA fallback; larger signatures, use only for a genuine interop need.
Checklist
DNSSEC pays off only when both ends are honest: a signed zone and a resolver that refuses unsigned-or-tampered answers. The commands are the easy part; the rest is rollover discipline and monitoring so a key never lapses silently - because when it does, DNSSEC does exactly what you told it to and takes the whole zone down. Build the chain, prove it with delv and the AD flag, rehearse the SERVFAIL drills, and alert on expiry long before the cliff.