DNS and DNSSEC

Offline KSK with Knot, the secure way

An offline KSK is the DNSSEC version of keeping the master key in a safe. It works well until the day everyone forgets the safe has a timer, and the zone goes dark on a Sunday.

The short answer

Run Knot with offline-ksk on and manual key management. Knot pre-generates ZSKs and exports a KSR; an air-gapped machine holding only the KSK signs it into an SKR; Knot imports it. The server never holds the KSK private key. Repeat the ceremony well before the SKR period ends, and alert on the warning Knot logs near the end.

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

In a normal Knot setup, the KASP database on the server holds both private keys. The ZSK signs records. The KSK signs the DNSKEY set, and the parent's DS record points to it. Anyone who takes the server takes the KSK, and can then sign a complete fake zone that validates everywhere until you change the DS at the registrar. That can take days.

With an offline KSK, the server holds only ZSKs. The KSK lives on a machine with no network. That machine signs, in advance, every DNSKEY set the server will need for the next months. A stolen server then gives the attacker a ZSK that you can replace yourself, quickly, without the registrar.

The new risk is time. The signed DNSKEY sets cover a fixed period. If nobody runs the next ceremony, the last DNSKEY signature expires and every validating resolver rejects the whole zone.

What the docs say

Knot DNS allows a special mode of operation where the private part of the Key Signing Key is not available to the daemon, but it is rather stored securely in an offline storage.

Source: Knot DNS 3.5 docs, DNSSEC Offline KSK

The entire procedure must be repeated before the time period selected at the beginning passes, or whenever a configuration is changed significantly.

Source: Knot DNS 3.5 docs, Generating and signing future ZSKs

The KSKs (on the KSK side) must be managed manually, but manual KSK roll-over is possible.

Source: Knot DNS 3.5 docs, Offline KSK roll-over

The docs give the commands and the settings for both sides. They do not say how to run the KSK machine (air gap, backups, who may touch it), and they do not mention how to monitor the end of the signed period. Knot does log a warning when it reaches the last imported records, shown in the test below; nothing alerts on it unless you wire it up.

The secure configuration

ZSK side, the running server, /etc/knot/knot.conf:

yaml
policy:
  - id: offline
    manual: on                  # required on both sides
    offline-ksk: on             # the server never needs the KSK private key
    algorithm: ecdsap256sha256
    zsk-lifetime: 30d           # pregenerate plans ZSK rollovers from this
    propagation-delay: 1h
    dnskey-ttl: 3600            # required explicitly with offline KSK
    zone-max-ttl: 86400         # required explicitly with offline KSK
    keytag-modulo: 0/2          # ZSK tags even

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

KSK side, the offline machine, /etc/knot/ksk.conf (no server runs here; only keymgr reads it):

yaml
database:
  storage: /srv/ksk             # encrypted disk; backed up to a second offline medium

policy:
  - id: offline
    manual: on
    offline-ksk: on
    algorithm: ecdsap256sha256
    rrsig-lifetime: 21d         # validity of each signed DNSKEY set
    rrsig-refresh: 7d
    keytag-modulo: 1/2          # KSK tags odd: never collides with a ZSK tag

zone:
  - domain: example.com
    dnssec-policy: offline

The ceremony. Run it for a period much longer than the interval between ceremonies, for example sign 6 months every 3 months:

bash
# Once, on the KSK machine: create the KSK and send its DS to the registrar.
keymgr -c /etc/knot/ksk.conf example.com generate ksk=yes zsk=no algorithm=ecdsap256sha256
keymgr -c /etc/knot/ksk.conf example.com ds

# 1. ZSK side: plan future ZSKs and export the request (public keys only).
keymgr example.com pregenerate +6mo
keymgr example.com generate-ksr +0 +6mo > /media/transfer/example.com.ksr

# 2. KSK side, offline: sign it.
keymgr -c /etc/knot/ksk.conf example.com sign-ksr /media/transfer/example.com.ksr \
  > /media/transfer/example.com.skr

# 3. ZSK side: check, import, and apply.
keymgr example.com validate-skr /media/transfer/example.com.skr
keymgr example.com import-skr /media/transfer/example.com.skr
knotc zone-keys-load example.com

Monitoring, run daily on the ZSK side:

bash
# Last expiry among the imported DNSKEY signatures; alert when under 45 days.
keymgr example.com show-offline | awk '/RRSIG DNSKEY/{print $8}' | sort | tail -1

Prove it

The test (secure-tests/offline-ksk-knot/run.sh) runs knotd in one container (the ZSK side) and runs the KSK side in separate containers with --network none. Files move through a shared directory, like a USB stick. Lab values: ZSK lifetime 60 seconds, KSR period 180 seconds, SKR signature lifetime 120 seconds.

The KSK is created offline, and its DS is what the parent would publish:

text
$ keymgr -c /ksk/knot.conf example.com generate ksk=yes zsk=no algorithm=ecdsap256sha256
88a631068d828ca5845aa576c59d24a00388b410
$ keymgr -c /ksk/knot.conf example.com ds
example.com. DS 62929 13 2 578be360c62d54bc2f6f1179e9a314887554123f45eb7a8019b620083477293a

The first ceremony:

text
=== [20:42:29] ZSK side: pregenerate ZSKs and write the KSR ===
$ keymgr example.com pregenerate +180
OK
$ keymgr example.com generate-ksr +0 +180 > /usb/request.ksr
snapshots in KSR: 8

=== [20:42:29] KSK side (offline): sign the KSR ===
$ keymgr -c /ksk/knot.conf example.com sign-ksr /usb/request.ksr > /usb/response.skr
;; SignedKeyResponse 1.0 generated at 2026-09-24T20:42:30Z by Knot DNS 3.5.4

=== [20:42:30] ZSK side: validate and import the SKR ===
$ keymgr example.com validate-skr /usb/response.skr
OK
$ keymgr example.com import-skr /usb/response.skr
OK
$ knotc zone-keys-load example.com
OK
[20:42:34] $ delv @127.0.0.1 -a anchor.conf +root=example.com www.example.com A
; fully validated

The server's key database holds only ZSKs. The KSK is served as a public DNSKEY record from the imported SKR:

bash
keymgr example.com list
kdig @127.0.0.1 example.com DNSKEY +norecurse +short | cut -c1-44
keymgr example.com show-offline | grep RRSIG | tail -1
text
efbc38a9d0696e9eb734aa017ac1d68eeaa239db 57964 ZSK ECDSAP256SHA256 created=1790282549 publish=1790282549 active=1790282549 retire=1790282611 remove=1790282613
a10794bc582ead2cf8510ca7f05f2154c37d1956  1664 ZSK ECDSAP256SHA256 created=1790282609 publish=1790282609 active=1790282611 retire=1790282673 remove=1790282675
b55d85d2b8bf27f2e73c5d6bcb9b4a2166b0bc7f 29730 ZSK ECDSAP256SHA256 created=1790282671 publish=1790282671 active=1790282673

256 3 13 30Q2Ol+tX3/ZfnJxX0yd/kC4yLzo/eTfmYz
257 3 13 eLYApEI/tmPgzHszL4wpimeTvMYI3ayFPRb

example.com.        	5 RRSIG DNSKEY 13 2 5 20260924204729 (

The last signed DNSKEY set expires at 20:47:29. Within the period, ZSK rollovers run on their own and the chain stays valid:

text
20:43:33 info: [example.com.] DNSSEC, key, tag  1664, algorithm ECDSAP256SHA256, public, active
20:43:33 info: [example.com.] DNSSEC, key, tag 29730, algorithm ECDSAP256SHA256
20:43:33 info: [example.com.] DNSSEC, next signing at 2026-09-24T20:44:31+0000
[20:43:50] $ delv @127.0.0.1 -a anchor.conf +root=example.com www.example.com A
; fully validated

The trap: nobody runs the next ceremony. Knot warns once it reaches the last records, then the signature runs out and the zone is bogus:

text
20:45:29 warning: [example.com.] DNSSEC, using last offline KSK records available, import new SKR before RRSIGs expire

$ kdig @127.0.0.1 example.com DNSKEY +dnssec +norecurse +noall +answer | grep RRSIG | cut -c1-80
example.com.        	5	IN	RRSIG	DNSKEY 13 2 5 20260924204729 20260924191529 6292
now: 20260924204757
[20:47:57] $ delv @127.0.0.1 -a anchor.conf +root=example.com www.example.com A
;; validating example.com/DNSKEY: verify failed due to bad signature (keyid=62929): RRSIG has expired
;; validating example.com/DNSKEY: no valid signature found (DS)
;; no valid RRSIG resolving 'example.com/DNSKEY/IN': 127.0.0.1#53

The next ceremony (late here, on purpose) restores the chain:

text
=== [20:47:58] ZSK side: pregenerate ZSKs and write the KSR ===
$ keymgr example.com pregenerate +600
OK
$ keymgr example.com generate-ksr +0 +600 > /usb/request.ksr
snapshots in KSR: 28
...
$ keymgr example.com import-skr /usb/response.skr
OK
$ knotc zone-keys-load example.com
OK
[20:48:03] $ delv @127.0.0.1 -a anchor.conf +root=example.com www.example.com A
; fully validated

Mistakes people make

Signing exactly one period at a time

If each ceremony covers three months and happens every three months, one late ceremony is an outage. Sign twice the interval, so a missed date is a warning and not a failure.

Nobody watches the warning

Knot logs using last offline KSK records available when it reaches the last signed set. In the test, that came about two minutes before the zone broke. In production it comes one rrsig-lifetime before. Route that log line to an alert, and check show-offline expiry dates on a schedule.

"Offline" that is only switched off

A KSK machine that joins Wi-Fi for updates is not offline. Keep it without network interfaces, move files on dedicated media, keep the KASP directory on an encrypted disk, and keep a second encrypted copy in another place. Losing the only copy of the KSK forces a KSK rollover through the registrar.

Same keytag space on both sides

Without keytag-modulo, a new ZSK can get the same key tag as the KSK. Set 0/2 on the ZSK side and 1/2 on the KSK side, as the docs recommend.

Changing the ZSK side config after the ceremony

The SKR contains DNSKEY sets computed from the ZSK plan. Changing zsk-lifetime, dnskey-ttl or the algorithm makes the imported records wrong for the new plan. Repeat the ceremony after any significant change.

Checklist

  • Set manual: on and offline-ksk: on on both sides.
  • Set dnskey-ttl and zone-max-ttl explicitly on the ZSK side.
  • Set keytag-modulo: 0/2 on the ZSK side and 1/2 on the KSK side.
  • Keep the KSK machine without network access and its KASP directory encrypted.
  • Keep a second encrypted copy of the KSK KASP directory in a separate place.
  • Sign each KSR for at least twice the planned interval between ceremonies.
  • Run keymgr <zone> validate-skr before every import-skr.
  • Run knotc zone-keys-load <zone> after every import.
  • Alert on the log line using last offline KSK records available.
  • Check the last RRSIG DNSKEY expiry from keymgr <zone> show-offline daily.
  • Validate with delv after every ceremony.

Moving the KSK offline swaps a security risk for a calendar risk. The calendar risk is the easier one to manage, as long as someone owns it.

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