LUNAROPS · OPERATIONAL UPLINK 100% UPTIME 1,247d POSTS 893 JEFF.MOON@LUNAROPS.DEV UTC --:--:--

DNSSEC Setup and Operation: KSK/ZSK Rotation, DS Publication, and Validation Debugging

dnsdnssecsecuritybindknotnetworking

DNSSEC (Domain Name System Security Extensions) is the set of protocol extensions that cryptographically sign DNS records, preventing spoofing, cache poisoning, and bogus responses from MITM attackers between your resolver and the authoritative name servers. It’s been around since 1997 (original RFCs), widely deployed in TLDs since the root zone signing in 2010, and still the subject of operational confusion every time someone tries to deploy it. This post is the DNSSEC explanation that walks you from “why” to “how” without losing you at DNSKEY syntax on page three.

The problem DNSSEC solves

Plain DNS has no authentication. When your resolver asks “what’s the A record for example.com?”, it gets a UDP packet back and trusts that the packet came from the right server. It can’t verify:

  • The packet wasn’t forged by an attacker between you and the authoritative server.
  • The authoritative server itself wasn’t compromised or poisoned.
  • The response wasn’t modified in transit.

The classic exploit is Kaminsky-style cache poisoning: flood a resolver with guesses of transaction IDs and source ports to get it to cache a malicious answer. Modern resolvers harden against this with source-port randomization and other mitigations, but the fundamental trust model is still “believe what comes back”.

DNSSEC adds public-key signatures over DNS records. Resolvers that validate DNSSEC verify those signatures against a chain of trust that ultimately terminates at the root zone, whose public key is hardcoded into the resolver. If any link in the chain fails, the resolver returns SERVFAIL rather than a possibly-forged answer.

What DNSSEC is not

A few common misconceptions:

  • DNSSEC does not encrypt DNS. It authenticates, it doesn’t confidentialize. DNS queries and responses are still visible on the wire unless you also use DoH or DoT.
  • DNSSEC does not protect against censorship. A resolver that refuses to answer can still censor; DNSSEC only ensures answers (when given) are authentic.
  • DNSSEC does not prevent DoS. If anything it slightly worsens the attack surface — signed responses are larger and validation costs CPU.
  • DNSSEC does not sign DNS queries. Only responses and records.
  • DNSSEC adoption is voluntary. A zone that isn’t signed simply has no DNSSEC protection, and that’s fine. The chain breaks at the first unsigned link.

The key hierarchy

DNSSEC uses a two-tier key system per zone:

  • Key Signing Key (KSK): signs only the DNSKEY RRset. Its public key is what gets hashed into a DS record at the parent zone. Rotates rarely.
  • Zone Signing Key (ZSK): signs everything else in the zone (A, AAAA, MX, TXT, etc.). Rotates often.

Why two keys? Because the KSK is the one that ties your zone into the parent’s chain of trust via a DS record. Changing it requires coordinating with the parent (your registrar). Changing the ZSK is purely internal. By separating them, you can rotate the ZSK on a schedule (good security hygiene) without involving anyone external.

The record types involved:

  • DNSKEY: the public keys themselves, published in your zone.
  • RRSIG: signatures over specific record sets.
  • DS (Delegation Signer): the hash of your KSK, published at the parent zone. This is what glues your zone into the chain.
  • NSEC / NSEC3: proofs of non-existence (for “this name doesn’t exist” responses).
  • CDNSKEY / CDS: “child DNSKEY / child DS” — your zone’s self-declared DS records, which a parent zone can use to automate DS updates.

When a validating resolver answers a query for example.com:

  1. Fetches the A record and its RRSIG.
  2. Fetches the DNSKEY RRset and verifies the RRSIG with the ZSK.
  3. Verifies the DNSKEY RRset’s RRSIG with the KSK.
  4. Hashes the KSK and compares to the DS record at .com.
  5. Verifies the DS’s RRSIG with the .com ZSK.
  6. Walks up to the root the same way.
  7. If every link verifies, the answer is AUTHENTIC. If any link fails, SERVFAIL.

Algorithms

Picking a DNSSEC algorithm matters less than it used to, but it still matters a little:

  • RSASHA256 (algorithm 8) — ubiquitously supported, signatures are large (~256 bytes). Widely deployed default on older infrastructure.
  • ECDSAP256SHA256 (algorithm 13) — much smaller signatures (~64 bytes), same security, vastly preferred for new zones.
  • ED25519 (algorithm 15) — modern, fast, compact. Not yet universally supported by registrars, so verify before adopting.

Use ECDSAP256SHA256 for new zones unless you have a specific reason otherwise. RSA signatures bloat DNS responses, pushing them past the 512-byte UDP safety limit and forcing truncation/TCP fallback — historically a reliability issue on networks that block DNS-over-TCP.

Avoid SHA-1-based algorithms (5, 7). They still work but are deprecated and will eventually be removed.

Signing a zone with BIND

BIND has signed zones for decades. Modern BIND (9.16+) has dnssec-policy, a declarative policy language that handles key management, rollover, and signing automatically. Use it — the older manual workflow (dnssec-keygen, dnssec-signzone) is error-prone.

Minimal configuration

# /etc/bind/named.conf
options {
    directory "/var/cache/bind";
    dnssec-validation auto;       // as a resolver
    allow-transfer { none; };
};

dnssec-policy default {
    keys {
        ksk key-directory lifetime P10Y algorithm ecdsap256sha256;
        zsk key-directory lifetime P90D algorithm ecdsap256sha256;
    };
    dnskey-ttl PT1H;
    max-zone-ttl P1D;
    nsec3param iterations 0 optout no salt-length 0;
};

zone "example.com" {
    type primary;
    file "/etc/bind/db.example.com";
    dnssec-policy default;
    inline-signing yes;
};
  • dnssec-policy default tells BIND to generate keys, sign the zone, and roll them per the declared schedule.
  • inline-signing yes keeps the zone file human-editable and plain; BIND produces a signed view separately.
  • lifetime P10Y on the KSK = 10 years; lifetime P90D on the ZSK = 90 days.
  • nsec3param iterations 0 — NSEC3 with zero iterations. Prior guidance liked high iteration counts; RFC 9276 now says “0 is fine, don’t waste CPU”.

BIND will generate keys on first load, create the signed zone, and start responding authoritatively to DNSSEC queries. Check:

1
rndc dnssec -status example.com

Output shows current key states, scheduled rolls, and the DS records you need to publish at your parent.

Publishing the DS to the parent

After BIND generates the KSK, extract the DS:

1
dnssec-dsfromkey -2 /etc/bind/Kexample.com.+013+<id>.key

The output looks like:

example.com. IN DS 12345 13 2 AABBCCDD...

Copy the numeric fields (key tag, algorithm, digest type, digest hex) into your registrar’s DNSSEC configuration. Every registrar has a different UI for this. Once accepted, the parent (.com in this case) publishes the DS, and validating resolvers can now verify your zone.

Don’t publish a DS before your zone is fully signed and serving correctly. A DS pointing at a key that doesn’t validate will cause every DNS lookup for your domain to fail.

Signing a zone with Knot DNS

Knot is the modern alternative to BIND for authoritative hosting — smaller, faster, designed from scratch for automation. Knot’s DNSSEC policy is similarly declarative:

# /etc/knot/knot.conf
policy:
  - id: default
    algorithm: ecdsap256sha256
    ksk-lifetime: 10y
    zsk-lifetime: 90d
    cds-cdnskey-publish: always
    nsec3: on

zone:
  - domain: example.com
    file: /var/lib/knot/zones/example.com.zone
    dnssec-signing: on
    dnssec-policy: default

Knot’s cds-cdnskey-publish: always is worth understanding. CDS and CDNSKEY are records the zone publishes in itself, declaring its own DS. Some registrars now auto-publish DS records by polling for CDS — RFC 7344/8078 “automated DNSSEC”. When it works, it eliminates manual coordination with the registrar during rollovers. Cloudflare, Google Domains, and a growing number of registrars support it. Check yours.

Validating: the other side

If you’re running a recursive resolver (BIND, Unbound, dnsmasq, Knot Resolver, PowerDNS Recursor), enable DNSSEC validation:

Unbound

# /etc/unbound/unbound.conf
server:
    auto-trust-anchor-file: "/var/lib/unbound/root.key"
    val-permissive-mode: no
    val-log-level: 1

auto-trust-anchor-file maintains the root trust anchor using RFC 5011. The file is seeded by package install; Unbound keeps it updated as the root KSK rolls. val-permissive-mode: no means fail-closed: bad signatures return SERVFAIL. Permissive mode returns the data with a warning — useful for staged rollout, not for production.

BIND

options {
    dnssec-validation auto;
    # or: dnssec-validation yes;  with an explicit trust anchor
};

auto uses the built-in root trust anchor and maintains it via RFC 5011.

Systemd-resolved

[Resolve]
DNSSEC=yes

Three modes: no (don’t validate), allow-downgrade (try, fall back if upstream doesn’t support it — the common practical choice), yes (strict, fail-closed).

Testing a resolver

1
2
3
4
5
6
7
8
9
# Should succeed (example.com is signed)
dig +dnssec example.com

# AD flag in the response indicates "Authenticated Data"
dig example.com +dnssec | grep -i "flags:"
# flags: qr rd ra ad;

# A deliberately broken DNSSEC zone — should SERVFAIL
dig +dnssec dnssec-failed.org

The AD flag is the signal that validation succeeded. Without it, either DNSSEC isn’t enabled on your resolver or the zone isn’t signed. If the query returns SERVFAIL and dig +cd +dnssec (checking-disabled) returns success, the zone has broken DNSSEC.

KSK and ZSK rollover

ZSK rollover (routine)

Every 30–90 days, you rotate the ZSK. The procedure with BIND’s dnssec-policy is automatic:

  1. BIND generates a new ZSK ahead of time.
  2. Publishes both old and new DNSKEY records for a TTL window (pre-publish).
  3. Starts signing with the new ZSK.
  4. Stops publishing the old ZSK after another TTL window.

You don’t touch the parent. No coordination required. Modern DNS software does this correctly if configured; your job is to configure it once and monitor.

KSK rollover (significant)

KSK rollover requires updating the DS at the parent. Two approaches:

Double-DS (common): publish the new KSK in your zone, give resolvers time to cache it, then publish the new DS at the parent alongside the old one, wait, then remove the old DS, wait, remove the old KSK.

Double-KSK: similar but you sign the DNSKEY RRset with both old and new KSKs simultaneously for the rollover window.

Both patterns work. Both require precise TTL-aware timing. Mess this up and your zone goes dark for the duration of cached bad state.

The right way to handle KSK rollover in 2026:

  1. Use software with a dnssec-policy that does it automatically (BIND 9.16+, Knot 3.0+, PowerDNS 4.5+).
  2. Use CDS/CDNSKEY to let the registrar auto-publish the new DS.
  3. Use a registrar that supports CDS scanning.

If you’re doing this manually, you’re working harder than necessary and risking an outage. Pick software and registrars that automate it.

Algorithm rollover

Changing from RSASHA256 to ECDSAP256SHA256 is an algorithm rollover, strictly more complex than a simple key rollover. The steps, in order:

  1. Add keys of the new algorithm. Sign with both algorithms.
  2. Publish DS records for the new algorithm at the parent.
  3. Wait for TTL + propagation.
  4. Remove DS records for the old algorithm.
  5. Remove keys of the old algorithm.

dnssec-policy automates this too. Declare the new policy, reload BIND, wait. Monitor. Don’t rush.

Operational monitoring

Things you must monitor for a signed zone:

  • Signature expiration. RRSIGs expire (default 30 days). If signing fails, signatures expire before replacement; resolvers return SERVFAIL. Alert if any signature is within 7 days of expiration.
  • DS record presence at parent. dig DS example.com @a.gtld-servers.net — parent must have the current DS.
  • DNSKEY matches DS. dig DNSKEY example.com — the KSK hash should match the published DS.
  • Validation succeeds from an external validating resolver. Test with dig +dnssec example.com @1.1.1.1 — AD flag means success.

Tools:

  • dnsviz.net — web-based DNSSEC visualization. Paste a domain, see the full validation chain. The best tool for understanding what’s broken.
  • Verisign Labs’ DNSSEC Debugger — quick online check.
  • dig +dnssec +cd +multi — local-side debugging.
  • delv — like dig, but does its own validation and shows the result. Indispensable.
  • ldns-verify-zone — validates a signed zone file offline.

Common failure modes

“My zone went dark”

Classic causes:

  1. DS at parent doesn’t match current KSK. Often because you regenerated keys without republishing. Fix: extract new DS with dnssec-dsfromkey and publish via registrar.
  2. Signatures expired. Signing is failing — check logs for the signing daemon.
  3. Clock skew. Signatures have validity windows based on wall clock. A server that’s more than a few hours off wall-clock time signs with bad windows. NTP. Always NTP.
  4. KSK rollover mid-flight, TTLs too short or too long. Resolvers have inconsistent state.
  5. Resolver bug. Rare but happens. Test with multiple validating resolvers to isolate.

Debugging a SERVFAIL

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Does it resolve with CD (checking disabled)?
dig +cd example.com
# If yes: DNSSEC is broken. If still fails: unrelated.

# Show full DNSSEC chain:
delv example.com +vtrace

# Inspect records
dig DNSKEY example.com +multi
dig DS example.com +multi

# Against your own authoritative
dig @ns1.example.com +dnssec +multi example.com SOA

DNSViz (online) almost always pinpoints the issue visually.

“Validation is too slow”

DNSSEC validation adds CPU cost and more queries (DNSKEY, DS, RRSIG records). For a normally cached resolver, the overhead is negligible. For cold queries, a few extra ms. If your resolver is underpowered, this matters. Modern hardware, no.

“My resolver returns SERVFAIL for some domains”

Usually means: those domains have broken DNSSEC. Contact the domain owner, or — as a last resort — disable validation locally. But most of the time the “broken” domain is legitimately broken, not your resolver’s fault. Use dnsviz to confirm.

Is it worth enabling?

As an authoritative zone operator: yes for domains that matter. The operational overhead with modern tooling is low. The protection against cache-poisoning and MITM attacks is real, especially for mail (DANE TLSA records only work with DNSSEC), and public registrars and TLDs increasingly expect it.

As a resolver operator: yes. The cost is small, the protection non-zero, and some modern protocols (DANE, SSHFP via DNSSEC) depend on it.

For a homelab: arguable. If you’re already running Unbound, flipping validation on is one config line and you’re done. For your own internal zones served by Pi-hole or similar, signing is overhead for little gain — your threat model doesn’t usually include attackers in your LAN path.

Myths worth dispelling

  • “DNSSEC breaks DNS.” In practice it rarely does, but when it does, it does so catastrophically. Modern deployments with dnssec-policy or Knot’s automation are solid; manual deployments have historically been where horror stories come from.
  • “DNSSEC is obsolete with DoH/DoT.” They solve different problems. DoH encrypts the transport between you and the resolver. DNSSEC authenticates the answer end-to-end. You benefit from both.
  • “It increases DNS response size and causes problems.” True for RSA signatures, much less so for ECDSA. Modern zones signed with algorithm 13 or 15 have modest size overhead.
  • “Nobody uses it.” Many more do than you’d guess. The root zone, every TLD I can think of, most government zones, banks, and a growing portion of commercial domains. Checking dig DS <tld> +short often surprises.

Practical checklist for signing a new zone

  1. Pick ECDSAP256SHA256 (algorithm 13) unless something forces otherwise.
  2. Configure dnssec-policy in BIND or equivalent in Knot/PowerDNS.
  3. Reload, verify keys generated with rndc dnssec -status <zone> or keymgr.
  4. Verify zone is signed with dig @localhost +dnssec <zone> (AD flag from validating resolver or RRSIGs present).
  5. Extract DS with dnssec-dsfromkey.
  6. Publish DS at parent via registrar.
  7. Wait for parent to propagate (minutes to hours depending on TLD).
  8. Test externally with delv and DNSViz.
  9. Monitor signature expiration, DS presence, validation from multiple resolvers.
  10. Enable CDS/CDNSKEY if your registrar supports it, so the next KSK rollover is automatic.

DNSSEC has a reputation for operational pain that came from the era of manual signing with dnssec-signzone and cron jobs. In 2026, with BIND’s dnssec-policy, Knot’s built-in automation, and CDS-aware registrars, the day-to-day is close to “configure once, monitor occasionally”. The protocol is complex; operating signed zones no longer has to be.

Comments