Azure Lesson 60 of 137

DNSSEC End to End: Signing Public Zones and Enforcing Validation on Hybrid Resolvers

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.

DNSSEC: zone signing (ZSK/KSK) + the DS chain of trust to the parent for validation

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

After this lesson you can

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-keygen flow 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:

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:

  1. Publish the new ZSK in the DNSKEY set alongside the old one. Sign nothing with it yet.
  2. Wait at least the DNSKEY TTL so every resolver has both keys cached.
  3. 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.
  4. 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:

  1. Generate the new KSK and add its DNSKEY to the zone. Both KSKs now sign the DNSKEY set.
  2. Publish the new KSK’s DS at the registrar so the parent now has two DS records. Either chain validates.
  3. Wait for the old DS’s TTL to expire from caches so no resolver is pinned to only the old DS.
  4. 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:

  1. Add the new ZSK to the DNSKEY set alongside the old one; sign nothing with it yet.
  2. Wait ≥ the DNSKEY TTL so every resolver has cached both public keys.
  3. Switch signing to the new ZSK and re-sign the zone. Cached RRSIGs made by the old ZSK still validate — resolvers hold that key.
  4. 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:

  1. Generate the new KSK; add its DNSKEY so both KSKs sign the DNSKEY set.
  2. 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.
  3. 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.
  4. 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

Glossary

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.

DNSDNSSECRoute 53Azure DNSSecurityHybrid
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments