DNS and DNSSEC

Knot DNSSEC key rollover, the secure way

The ZSK rollover is the easy one: Knot does it alone and nobody notices. The KSK rollover needs a human at a registrar web form, and Knot has an option that will happily finish the rollover without that human.

The short answer

Let Knot roll ZSKs automatically with pre-publication. For KSKs, set a finite lifetime, a submission section that checks the parent for the new DS, and a parent-delay equal to the parent's DS TTL. Never set a submission timeout. Update the DS at the registrar when CDS appears, and validate before and after.

Updated Houssam Hammoudi, CTOTested with Knot DNS 3.5.4, BIND delv 9.20.29

On this page
  1. What goes wrong
  2. What the docs say
  3. The secure configuration
  4. Prove it
  5. Mistakes people make
  6. Checklist

What goes wrong

A zone has two kinds of keys. The zone signing key (ZSK) signs the records. The key signing key (KSK) signs the DNSKEY set, and the parent zone publishes a DS record that points to the KSK. That DS is the link resolvers follow.

A ZSK rollover touches only your zone. Knot publishes the new key, waits for caches to learn it, switches signing, then removes the old key. You can automate it completely.

A KSK rollover touches the parent. The new KSK is useless until the parent publishes a DS for it, and the old KSK must stay until every cache has the new DS. For most domains, only the registrar can change the DS. If the old KSK disappears before the new DS is live, every validating resolver returns SERVFAIL for the whole zone. That is a full outage, and it is self-inflicted.

Knot has an option that makes this outage easy. The timeout in a submission section declares the DS "submitted" after a fixed time, whether or not the parent changed anything.

What the docs say

The ZSK rollover is performed with Pre-publish method, KSK rollover uses Double-Signature scheme, as described in RFC 6781.

Source: Knot DNS 3.5 docs, DNSSEC key rollovers

After this time period (in seconds) the KSK submission is automatically considered successful, even if all the checks were negative or no parents are configured.

Source: Knot DNS 3.5 reference, submission timeout

The DS check tolerates unknown DS records at the parent, but it requires the previously known DS record to be removed; otherwise, the DS check remains negative.

Source: Knot DNS 3.5 docs, Automatic KSK and ZSK rollovers example

In practice, Dreg will not be a fixed time: instead, the end of Dreg will be signaled by the appearance of the DS record in the parent zone.

Source: RFC 7583, Section 3.3.1

The Knot docs explain every key state, but they leave the registrar step to you: nothing in Knot can log in to a registrar. They also do not warn that timeout removes the old KSK while the parent still points to it. RFC 7583 is clear that the registration delay is not a fixed time, which is exactly why a fixed timeout is wrong.

The secure configuration

yaml
remote:
  - id: validating_resolver
    address: 198.51.100.53@53      # a resolver you trust, or the parent's servers

submission:
  - id: parent_ds
    parent: [ validating_resolver ]  # Knot asks here for the new DS
    check-interval: 1h
    parent-delay: 1d                 # the parent's DS TTL: caches must forget the old DS
    # no "timeout": the rollover waits for the real DS, however long it takes

policy:
  - id: secure
    algorithm: ecdsap256sha256
    zsk-lifetime: 30d                # automatic, pre-publish, no parent involved
    ksk-lifetime: 365d               # finite, so the KSK rollover path is exercised
    propagation-delay: 1h            # your slowest secondary plus signing time
    dnskey-ttl: 3600
    zone-max-ttl: 86400              # set explicitly; each step waits for it
    cds-cdnskey-publish: rollover    # CDS/CDNSKEY appear only while a DS is needed
    ksk-submission: parent_ds

zone:
  - domain: example.com
    dnssec-signing: on
    dnssec-policy: secure

When the KSK rollover reaches submission, Knot logs KSK submission, waiting for confirmation and publishes CDS. Do the registrar step:

bash
# 1. Read the DS for the new KSK (the tag is in the log and in the CDS record).
kdig @ns1.example.com example.com CDS +short
keymgr example.com ds <new-ksk-tag>

# 2. At the registrar: replace the old DS with the new one (digest type 2).
#    Replace, do not add: Knot's DS check stays negative while the old DS is there.

# 3. Confirm the parent serves it, then let Knot's check pick it up.
kdig example.com DS @a.gtld-servers.net +norecurse

If you cannot give Knot a parent to query, leave parent unset and confirm by hand after step 3 with knotc zone-ksk-submitted example.com.

Prove it

The test (secure-tests/knot-dnssec-key-rollover/run.sh) runs one Knot 3.5.4 instance with two zones. A stand-in com. zone plays the parent registry and accepts DS changes only with a TSIG key named registrar. example.com is the child, with lab lifetimes: KSK 40 seconds, ZSK 25 seconds, TTLs of 5 seconds. delv validates from a com. trust anchor.

Before: a submission timeout and nobody at the registrar

Policy with timeout: 8s in the submission section. The chain validates at the start:

text
[20:24:22] registrar publishes DS for the first KSK 38820
[20:24:28] $ delv @127.0.0.1 -a anchor.conf +root=com www.example.com A
; fully validated

The KSK rollover starts, waits 8 seconds, and declares success:

text
20:24:59 info: [example.com.] DNSSEC, KSK rollover started
20:25:06 notice: [example.com.] DNSSEC, KSK submission, waiting for confirmation
20:25:06 info: [example.com.] DNSSEC, key, tag 41202, algorithm ECDSAP256SHA256, KSK, public, ready, active+
20:25:14 notice: [example.com.] DNSSEC, KSK submission, confirmed
20:25:14 info: [example.com.] DNSSEC, next key action, KSK tag 38820, remove at 2026-09-24T20:25:14+0000

The parent still points to the removed key, and the zone is bogus:

bash
kdig @127.0.0.1 example.com DS +short
kdig @127.0.0.1 example.com DNSKEY +short | cut -c1-40
delv @127.0.0.1 -a anchor.conf +root=com www.example.com A
text
38820 13 2 A964687067C91442284AF6EE0A2631635ADB8147C192F53C656F48A4B1DB0189

256 3 13 JDDOBRnUTOON0Y5DtoKiQq0qxtdKcEo
257 3 13 UIaWg3Q64zy+dJtL+6MwA8R+nUr3wuq

;; validating example.com/DNSKEY: no valid signature found (DS)
;; no valid RRSIG resolving 'example.com/DNSKEY/IN': 127.0.0.1#53
;; broken trust chain resolving 'www.example.com/A/IN': 127.0.0.1#53

After: a parent DS check and the registrar step

Policy with parent: [parent], check-interval: 2s and parent-delay: 5s, no timeout. The ZSK rolls first, with no parent involvement, and the chain stays valid:

text
20:25:54 info: [example.com.] DNSSEC, ZSK rollover started
20:25:54 info: [example.com.] DNSSEC, next key action, ZSK tag 43496, replace at 2026-09-24T20:26:01+0000
20:26:01 info: [example.com.] DNSSEC, next key action, ZSK tag 23651, remove at 2026-09-24T20:26:08+0000
[20:26:03] $ delv @127.0.0.1 -a anchor.conf +root=com www.example.com A
; fully validated

The KSK rollover reaches submission. Knot asks the parent and gets "negative" until the DS changes:

text
20:26:09 info: [example.com.] DNSSEC, KSK rollover started
20:26:16 notice: [example.com.] DNSSEC, KSK submission, waiting for confirmation
20:26:16 info: [example.com.] DNSSEC, key, tag  5435, algorithm ECDSAP256SHA256, KSK, public, ready, active+
20:26:20 info: [example.com.] DS check, outgoing, remote 127.0.0.1@53 TCP, KSK submission check: negative
20:26:22 info: [example.com.] DS check, outgoing, remote 127.0.0.1@53 TCP, KSK submission check: negative
bash
kdig @127.0.0.1 example.com CDS +short
text
5435 13 2 6325EB84578DCCE1A32ACC71BA0A2E327E1CBD9176CDB922102EA1AB69415049

The registrar step (in the lab, a TSIG-signed update to com.) replaces the DS. The next check is positive, and only then does the old KSK get a removal time, after parent-delay:

text
[20:26:22] REGISTRAR STEP: replace the DS at the parent with the DS for KSK 5435
20:26:24 info: [example.com.] DS check, outgoing, remote 127.0.0.1@53 TCP, KSK submission check: positive
20:26:24 notice: [example.com.] DNSSEC, KSK submission, confirmed
20:26:24 info: [example.com.] DNSSEC, next key action, KSK tag 56911, remove at 2026-09-24T20:26:34+0000
20:26:24 info: [example.com.] DNSSEC, key, tag 56911, algorithm ECDSAP256SHA256, KSK, public, active+
20:26:24 info: [example.com.] DNSSEC, key, tag 43496, algorithm ECDSAP256SHA256, public, active
20:26:24 info: [example.com.] DNSSEC, key, tag  5435, algorithm ECDSAP256SHA256, KSK, public, active

After the rollover, one KSK remains, it matches the parent, and the chain validates. (The second ZSK is the next ZSK rollover, already pre-published.)

bash
kdig @127.0.0.1 example.com DS +short
kdig @127.0.0.1 example.com DNSKEY +short | cut -c1-40
delv @127.0.0.1 -a anchor.conf +root=com www.example.com A
text
5435 13 2 6325EB84578DCCE1A32ACC71BA0A2E327E1CBD9176CDB922102EA1AB69415049

256 3 13 kDMrQG9HLB4L68+5zkcz2H7UY7X0AAZ
256 3 13 u2Bj5jYLNi9solkgYfnwoafLoscNIVE
257 3 13 TpH+jD4IAG7SPfcvrk+arJTX8wpSdb6

[20:26:39] $ delv @127.0.0.1 -a anchor.conf +root=com www.example.com A
; fully validated

Mistakes people make

The timeout that finishes without you

timeout in a submission section looks like a safety net. It is the opposite. After it expires, Knot retires the old KSK whether or not the parent changed. If the registrar step was forgotten, the zone goes bogus, as the test shows. Leave timeout unset (the default, 0, means infinity).

Adding the new DS but never removing the old one

Some registrar forms add a DS record next to the existing one. Knot's check requires the previously known DS to be gone, so the check stays negative and the rollover never finishes. Replace the DS; do not append.

Setting parent-delay to zero in production

When the parent starts serving the new DS, caches still hold the old DS for up to its TTL. If the old KSK leaves before that, those caches fail to validate. Set parent-delay to at least the parent's DS TTL. Check it with kdig example.com DS @<parent server> +norecurse.

Pointing the DS check at a caching forwarder

A plain caching forwarder can keep answering with the old DS until its cached copy expires, so the check lags behind the parent. Point parent at the parent's authoritative servers, or at a validating resolver you control and can flush.

Changing TTLs in the middle of a rollover

Knot plans each rollover step from dnskey-ttl, zone-max-ttl and propagation-delay. Its reference warns: "Ensure all DNSKEYs with updated TTL are propagated before any subsequent DNSKEY rollover starts." Change TTLs only when no rollover runs, then wait at least one old TTL.

Checklist

  • Set ksk-lifetime and zsk-lifetime explicitly in the policy.
  • Configure a submission section with parent set to the parent's servers or a validating resolver.
  • Set parent-delay to at least the parent's DS TTL.
  • Remove any timeout from every submission section.
  • Alert on the log line KSK submission, waiting for confirmation.
  • At the registrar, replace the old DS with the new one; do not keep both.
  • Confirm the new DS with kdig DS against every parent server before walking away.
  • Run delv against the real root trust anchor before, during and after each KSK rollover.
  • Check knotc zone-status for the next signing time after each step.

Knot handles every rollover step it can see. The one step it cannot see, the registrar form, is the only one that ever takes a zone down, so give it a check, not a timer.

H2-CPQE

Learn it on a live range

DNSSEC with Knot, in Edge and Post-Quantum Networking: a real host in your browser, and every objective checked on the machine.

Start free

The Dome

Want it run for you?

The Dome puts post-quantum TLS, a WAF that blocks, signed DNS and a zero-trust mesh in front of your application. Tell us what you run.

See the Dome