TSIG for zone transfers and key rotation, the secure way
The TSIG secret in your DNS config was generated years ago by someone who has since left, it is hmac-md5, and three servers, a backup and a wiki page know it. Rotating it feels risky, so nobody does.
The short answer
Use one hmac-sha256 TSIG key per primary and secondary pair, named with a version. On the primary, allow transfer only when both the source address and the key match. Rotate by overlap: the primary accepts old and new keys, the secondary switches, NOTIFY switches, then the old key is removed and tested as refused.
On this page
What goes wrong
A zone transfer (AXFR or IXFR) copies the whole zone from a primary to a secondary. NOTIFY tells the secondary to fetch a new version. Without authentication, anyone who can reach the primary can dump the zone, and anyone can send NOTIFY to make secondaries poll.
TSIG adds a signature to each DNS message with a shared secret (an HMAC). It proves the message came from a host that knows the key, and it is only valid within a short time window.
The usual problems are old keys and no rotation. Many deployments still use
hmac-md5, one key shared by every server and every tool, and the same
secret for years. When a laptop or a backup with that key leaks, the only
fix is a rotation that nobody has practiced.
What the docs say
It should be possible for more than one key to be in simultaneous use among a set of interacting hosts. This allows for periodic key rotation as per best operational practices
Source: RFC 8945, Section 4.2
The secret SHOULD be at least as long as the keyed hash output
Source: RFC 8945, Section 8
This value MUST be exactly the same as the name of the TSIG key on the opposite primary/secondary server(s).
Source: Knot DNS 3.5 reference, key section
RFC 8945 marks HMAC-MD5 as MUST NOT use and hmac-sha1 as NOT
RECOMMENDED. It allows several keys at once, but neither the RFC nor the
Knot docs give a rotation procedure. The Knot ACL reference does say the
address and key of a rule must both match, which is what makes a leaked
key alone useless against Knot. PowerDNS behaves differently: a TSIG key
there skips the IP check (see the hidden primary page).
The secure configuration
Generate one key per primary and secondary pair. Put a version in the name, so two keys can exist side by side during a rotation:
keymgr -t xfr-2026a hmac-sha256 # 256-bit random secret, as long as the hash outputPrimary, /etc/knot/knot.conf:
key:
- id: xfr-2026a # must match the name on the secondary exactly
algorithm: hmac-sha256 # RFC 8945: RECOMMENDED; never hmac-md5 or hmac-sha1
secret: <base64 secret>
remote:
- id: secondary
address: 198.51.100.20@53
key: xfr-2026a # signs outgoing NOTIFY
acl:
- id: secondary_xfr
address: 198.51.100.20 # the secondary's address AND ...
key: xfr-2026a # ... the key: both must match
action: transfer
zone:
- domain: example.com
notify: secondary
acl: secondary_xfrSecondary:
key:
- id: xfr-2026a
algorithm: hmac-sha256
secret: <base64 secret>
remote:
- id: primary
address: 203.0.113.10@53
via: 198.51.100.20 # source address the primary's ACL expects
key: xfr-2026a # signs SOA checks, AXFR and IXFR
acl:
- id: primary_notify
address: 203.0.113.10
key: xfr-2026a
action: notify
zone:
- domain: example.com
master: primary
acl: primary_notifyKeep secrets out of the main file so the config can live in git:
include: /etc/knot/keys/xfr.conf # mode 0640, owner root, group knot; not in gitRotation from xfr-2026a to xfr-2026b. Each step is a config change and
knotc reload; nothing restarts, and no transfer fails:
1. Both sides: add key xfr-2026b to the key section.
2. Primary: acl secondary_xfr key: [xfr-2026a, xfr-2026b]
3. Secondary: remote primary key: xfr-2026b
acl primary_notify key: [xfr-2026a, xfr-2026b]
4. Primary: remote secondary key: xfr-2026b (NOTIFY now signed with B)
acl secondary_xfr key: xfr-2026b (A no longer accepted)
5. Secondary: acl primary_notify key: xfr-2026b
6. Both sides: delete key xfr-2026a; test that it is refused.Prove it
The test (secure-tests/tsig-zone-transfers-key-rotation/run.sh) runs two
Knot instances in one container. The primary listens on 127.0.0.1, the
secondary on 127.0.0.2.
Key generation:
keymgr -t xfr-2026a hmac-sha256# hmac-sha256:xfr-2026a:9aGx64MjijfVY6QcNT5R62MGWBP0zQY7oVNyDJIMDYw=
key:
- id: xfr-2026a
algorithm: hmac-sha256
secret: 9aGx64MjijfVY6QcNT5R62MGWBP0zQY7oVNyDJIMDYw=With key A, the secondary transfers the zone and both logs name the key:
secondary: info: [example.com.] AXFR, incoming, remote 127.0.0.1@53 TCP, key xfr-2026a., finished, remote serial 1, 0.00 seconds, 1 messages, 286 bytes
secondary: info: [example.com.] refresh, remote 127.0.0.1@53, key xfr-2026a., zone updated, 0.00 seconds, serial none -> 1, expires in 600 seconds
primary: info: [example.com.] AXFR, outgoing, remote 127.0.0.2@36931 TCP, key xfr-2026a., started, serial 1
primary: info: [example.com.] AXFR, outgoing, remote 127.0.0.2@36931 TCP, key xfr-2026a., finished, 0.00 seconds, 1 messages, 286 bytesNo key, a wrong secret, and the right key from the wrong address are all refused:
kdig @127.0.0.1 example.com AXFR +tcp
kdig @127.0.0.1 example.com AXFR -y hmac-sha256:xfr-2026a:<wrong secret>
kdig @127.0.0.1 -b 127.0.0.9 example.com AXFR -y hmac-sha256:xfr-2026a:<secret A>;; ERROR: server replied with error 'NOTAUTH'
;; ERROR: failed to query server 127.0.0.1@53(TCP)
;; ERROR: server replied with error 'NOTAUTH'
;; ERROR: failed to query server 127.0.0.1@53(TCP)
;; ERROR: server replied with error 'NOTAUTH'
;; ERROR: failed to query server 127.0.0.1@53(TCP)Overlap: the primary accepts A and B, the secondary switches to B. The next change transfers with key B:
secondary: info: [example.com.] refresh, remote 127.0.0.1@53, key xfr-2026b., remote serial 2, zone is outdated
secondary: info: [example.com.] refresh, remote 127.0.0.1@53, key xfr-2026b., zone updated, 0.00 seconds, serial 1 -> 2, expires in 600 seconds
primary: info: [example.com.] AXFR, outgoing, remote 127.0.0.2@41301 TCP, key xfr-2026b., finished, 0.00 seconds, 1 messages, 286 bytesThe primary signs NOTIFY with B and drops A. The next change still flows:
secondary: info: [example.com.] refresh, remote 127.0.0.1@53, key xfr-2026b., remote serial 3, zone is outdated
secondary: info: [example.com.] refresh, remote 127.0.0.1@53, key xfr-2026b., zone updated, 0.00 seconds, serial 2 -> 3, expires in 600 seconds
primary: info: [example.com.] AXFR, outgoing, remote 127.0.0.2@44455 TCP, key xfr-2026b., finished, 0.00 seconds, 1 messages, 286 bytesThe old key is refused even from the secondary's own address; the new key works:
kdig @127.0.0.1 -b 127.0.0.2 example.com AXFR -y hmac-sha256:xfr-2026a:<secret A>
kdig @127.0.0.1 -b 127.0.0.2 example.com AXFR -y hmac-sha256:xfr-2026b:<secret B> | tail -4 | head -1
kdig @127.0.0.2 example.com SOA +short;; ERROR: server replied with error 'NOTAUTH'
;; ERROR: failed to query server 127.0.0.1@53(TCP)
xfr-2026b. 0 ANY TSIG hmac-sha256. 1790283061 300 32 l0pk9z+6ftvQu/0M78swyzpQzOIqNKxYE/lXTrcCJXk= 48883 NOERROR 0
ns1.example.com. hostmaster.example.com. 3 60 60 600 300The 300 in the TSIG record is the fudge: the signature is accepted only
within 300 seconds of the signer's clock.
Mistakes people make
One key for every server and every tool
A single key shared by all secondaries, the ACME client and a CI job means one leak exposes everything, and one rotation touches everything. Use one key per pair of hosts, and separate keys for dynamic updates.
Key-only ACLs
An ACL with key: but no address: accepts the key from anywhere. In Knot,
put both in the same rule so a leaked key alone is refused, as the test
shows. In PowerDNS, a key bypasses IP settings, so use network isolation.
Reusing the key name for the new secret
If the new key has the same name as the old one, both sides must change at
exactly the same moment. Any gap fails transfers. A version in the name
(xfr-2026a, xfr-2026b) allows the overlap.
Forgetting NOTIFY
Teams rotate the transfer key and forget that the primary signs NOTIFY with
the key in its remote entry. The secondary then drops NOTIFY and only
refreshes on the SOA timer, so changes arrive minutes or hours late.
Clock drift
TSIG signatures carry a time and a 300-second fudge. A secondary with a stopped NTP client fails every transfer with a time error. Monitor time sync on every DNS server.
Secrets in git
The key file ends up in the same repository as the zone files. Keep the secret in a separate included file, readable only by root and the knot group, and delivered by your secret manager.
Checklist
- Generate TSIG keys with
keymgr -t <name> hmac-sha256. - Remove every
hmac-md5andhmac-sha1key. - Use one key per primary and secondary pair, with a version in the name.
- Use separate keys for zone transfers and for dynamic updates.
- Put both
addressandkeyin every Knot transfer and notify ACL. - Set
keyon eachremoteso SOA checks, transfers and NOTIFY are signed. - Keep secrets in an included file with mode 0640, outside git.
- Rotate on a schedule with the overlap steps; never reuse a key name.
- After rotation, test that the old key gets
NOTAUTH. - Monitor NTP on every DNS server.
A TSIG rotation you have rehearsed is a routine config change. One you have not rehearsed is the reason that hmac-md5 key is still in production.
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 freeThe 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