Secure BIND Zone Transfers with TSIG on RHEL-Based Linux

Learn how to secure BIND primary-to-secondary DNS zone transfers with TSIG on RHEL, Rocky Linux, AlmaLinux, and CentOS Stream using HMAC-SHA256.

Published

Updated

Read time 11 min read

Reviewed byDeepak Prasad

BIND TSIG key securing a DNS zone transfer between primary and secondary RHEL servers

This guide assumes that you already have a working BIND primary and secondary DNS pair. It focuses specifically on authenticating AXFR and IXFR zone transfers with TSIG.

TSIG uses a shared secret to authenticate DNS transactions between configured systems. It is different from DNSSEC, which signs zone data so that validating resolvers can verify DNS answers.

Tested on: Rocky Linux 10.2 (Red Quartz); BIND 9.18.33; sample zone example.com; primary 192.0.2.10; secondary 192.0.2.20.

IMPORTANT
This article covers TSIG-secured zone replication between an existing BIND primary and secondary. For initial BIND installation and ordinary primary/secondary setup, see configure a BIND primary and secondary DNS server. For DNSSEC zone signing, use a separate DNSSEC workflow with dnssec-policy and inline signing—not the HMAC key shown here.

Quick reference

Task Command or directive
Generate a new TSIG key once tsig-keygen -a hmac-sha256 xfer-example.com > /etc/named/keys/xfer-example.com.key
Restore SELinux labels on the key restorecon -RFv /etc/named/keys
Validate named.conf on each server named-checkconf
Validate the primary zone file named-checkzone example.com /var/named/example.com.zone
Restrict transfers on the primary allow-transfer { key xfer-example.com; }; inside the zone
Associate the TSIG key with the primary primaries { 192.0.2.10 key xfer-example.com; }; on the secondary
Reload BIND systemctl reload named or systemctl reload named-chroot
Follow transfer logs journalctl -u named -f or journalctl -u named-chroot -f
Test AXFR with TSIG dig @192.0.2.10 example.com AXFR -k /etc/named/keys/xfer-example.com.key
Force a secondary retransfer rndc retransfer example.com

TSIG vs DNSSEC for BIND Zone Transfers

Many guides mix these terms because both involve keys. They solve different problems.

Feature TSIG DNSSEC
Authentication target DNS transactions between configured systems DNS data returned to validating resolvers
Typical use AXFR, IXFR, NOTIFY, dynamic updates Zone signing and response validation
Key model Shared secret Public/private key pairs
Encrypts traffic No No
Used in this tutorial Yes No

TSIG protects the authenticity and integrity of the zone transfer transaction. It does not hide transferred records from passive network observers. RFC 8945 identifies primary-to-secondary zone transfers as a common TSIG use case.

DNSSEC deployment on current RHEL-family systems involves zone signing, DNSSEC policy, key rollover, DS publication, and resolver validation—none of which replace TSIG for transfer authentication between your authoritative servers.


Lab Setup and Prerequisites

Role Hostname IP address
Primary DNS ns1.example.com 192.0.2.10
Secondary DNS ns2.example.com 192.0.2.20
Zone example.com

The addresses above use the RFC 5737 documentation range. Replace them with your production addresses.

Before you add TSIG, confirm the baseline:

  • BIND is installed and serving example.com on the primary
  • bind-utils is installed on administrative hosts
  • named or named-chroot is running on both servers
  • TCP and UDP port 53 are permitted between the primary and secondary
  • System clocks are synchronized through chronyd
  • The secondary already receives the zone through the existing transfer configuration

If you still need the base replication layout, complete configure a BIND primary and secondary DNS server first. This page adds authentication on top of that working pair.

For a new lab, verify ordinary primary-to-secondary replication before adding TSIG. Do not temporarily permit unrestricted AXFR on an internet-facing server.

Clock skew matters because TSIG performs time validation. If transfers fail with a clocks-unsynchronized message, verify chronyd on both hosts before you regenerate keys.


Generate and Protect the TSIG Key

Generate the key once with tsig-keygen. It replaces the older dnssec-keygen -a HMAC-MD5 workflow and defaults to HMAC-SHA256 with a 256-bit secret suitable for inclusion in named.conf.

Create a protected directory for the TSIG key:

bash
install -d -m 750 -o root -g named /etc/named/keys

Create the destination file with restrictive permissions before writing the secret:

bash
install -m 640 -o root -g named /dev/null /etc/named/keys/xfer-example.com.key

Generate the key directly into that file:

bash
tsig-keygen -a hmac-sha256 xfer-example.com > /etc/named/keys/xfer-example.com.key
WARNING
Run tsig-keygen only when creating or intentionally rotating the key. Each run generates a different secret. Overwriting the file on only one server causes the primary and secondary keys to stop matching.

Display the generated BIND key block:

bash
cat /etc/named/keys/xfer-example.com.key

Example structure:

text
key "xfer-example.com" {
    algorithm hmac-sha256;
    secret "REDACTED_BASE64_SECRET";
};

The key name (xfer-example.com), algorithm (hmac-sha256), and secret must match on both servers. Treat the secret like a password: do not commit it to Git, paste it into tickets, or publish a real value in documentation screenshots.

Copy the same file to the secondary through SSH or another protected channel. Restore and verify the SELinux context on both servers:

bash
restorecon -RFv /etc/named/keys
bash
ls -lZ /etc/named/keys/xfer-example.com.key

Sample output:

output
-rw-r-----. 1 root named system_u:object_r:named_conf_t:s0 108 Jul 11 17:35 /etc/named/keys/xfer-example.com.key

The key file should use the named_conf_t SELinux type on RHEL-family systems because it is part of the BIND configuration. named_cache_t is normally associated with writable secondary-zone locations such as /var/named/slaves/, not files under /etc/named/.

If the type is still incorrect after restorecon, compare the expected context:

bash
matchpathcon /etc/named/keys/xfer-example.com.key

Sample output:

output
/etc/named/keys/xfer-example.com.key    system_u:object_r:named_conf_t:s0

Configure TSIG-Authenticated Zone Transfers

BIND still accepts the legacy keywords master, slave, and masters. This article uses primary, secondary, and primaries in examples because they match current documentation. The TSIG syntax is the same either way.

Configure the Primary DNS Server

Include the key once near the top of /etc/named.conf or in a dedicated snippet included from there:

output
include "/etc/named/keys/xfer-example.com.key";
NOTE
On RHEL-family named-chroot installations, the service exposes selected host paths inside /var/named/chroot through bind mounts. If BIND reports that it cannot find the included key file, confirm that /etc/named is available through /etc/named-chroot.files and inspect the named-chroot journal.

Restrict transfers inside the authoritative zone block rather than globally when possible:

text
zone "example.com" IN {
    type primary;
    file "/var/named/example.com.zone";
    allow-transfer { key xfer-example.com; };
    also-notify { 192.0.2.20 key xfer-example.com; };
};

allow-transfer { key xfer-example.com; }; requires AXFR and IXFR requests to use the TSIG key.

The optional also-notify entry below explicitly notifies the secondary and signs that NOTIFY message with the same key:

text
also-notify { 192.0.2.20 key xfer-example.com; };

You can omit also-notify when the secondary has an NS record in the zone and ordinary automatic NOTIFY messages are sufficient. Retain it when the secondary is not represented by an NS record or when you intentionally want the explicit NOTIFY message to be TSIG-signed.

WARNING

Do not assume that listing an IP address and a key in the same simple allow-transfer ACL requires both conditions:

text
allow-transfer { 192.0.2.20; key xfer-example.com; };

That configuration can authorize a request by matching either ACL element. The key-only rule used in this guide makes possession of the TSIG secret the transfer requirement.

Configure the Secondary DNS Server

Include the same key file, then define the secondary zone with the TSIG key associated with the primary server address:

text
include "/etc/named/keys/xfer-example.com.key";

zone "example.com" IN {
    type secondary;
    file "slaves/example.com.zone";
    primaries { 192.0.2.10 key xfer-example.com; };
    allow-transfer { none; };
};

Specifying the key in primaries causes the secondary's SOA queries and AXFR or IXFR requests to the primary to be TSIG-signed. Zone data is transferred from the primary to the secondary; the secondary sends the signed request.

Associating the key with the primary inside the zone definition is more precise than a global server { keys ...; }; block, which would apply the key to all communication with that address.

Legacy equivalent accepted by BIND:

text
zone "example.com" IN {
    type slave;
    file "slaves/example.com.zone";
    masters { 192.0.2.10 key xfer-example.com; };
    allow-transfer { none; };
};

Set allow-transfer { none; }; on the secondary unless it should forward the zone to another authorized server.


Validate and Reload BIND

Validate syntax on each server before you reload the service. Run named-checkconf separately on the primary and secondary after editing each host. Both configurations must load the identical key name, algorithm, and secret.

Check the main configuration:

bash
named-checkconf

The command exits silently with status 0 when the configuration is valid.

On the primary, check the zone file:

bash
named-checkzone example.com /var/named/example.com.zone

Sample output:

output
zone example.com/IN: loaded serial 2026071201
OK

Reload the running service. On a standard installation:

bash
systemctl reload named

On a chroot deployment:

bash
systemctl reload named-chroot

If the reload command fails, inspect its output and the systemd journal. BIND normally continues using the previously loaded valid configuration, but the new TSIG configuration will not take effect.


Verify the Secure Zone Transfer

Confirm an Unsigned AXFR Is Rejected

Run the unsigned test from the secondary server or another host that has network access to the primary:

bash
dig @192.0.2.10 example.com AXFR

The request should fail with REFUSED, followed by Transfer failed, because it does not contain the required TSIG signature.

Use the same source host for the signed test below. Testing both requests from the same host helps confirm that the difference is TSIG authentication rather than routing, firewall, or source-address rules.

Confirm a TSIG-Signed AXFR Succeeds

From the same host, repeat the request with the TSIG key:

bash
dig @192.0.2.10 example.com AXFR -k /etc/named/keys/xfer-example.com.key

Run the command as root or another account that is permitted to read the protected key file. A successful AXFR prints the zone records. If authentication failed, dig reports BADKEY, BADSIG, or BADTIME before any useful zone data appears.

Update the Zone and Compare SOA Serials

Edit a harmless record on the primary and increment the SOA serial in /var/named/example.com.zone:

output
@       IN SOA  ns1.example.com. hostmaster.example.com. (
                        2026071202 ; serial

Reload the primary zone:

bash
rndc reload example.com

On the secondary, follow the service journal:

bash
journalctl -u named -f

On a chroot deployment, substitute named-chroot for the unit name.

Sample log lines from a successful TSIG transfer:

output
zone example.com/IN: Transfer started.
transfer of 'example.com/IN' from 192.0.2.10#53: connected using 192.0.2.20#45678
zone example.com/IN: transferred serial 2026071202: TSIG 'xfer-example.com'
transfer of 'example.com/IN' from 192.0.2.10#53: Transfer completed: 1 messages, 9 records, 412 bytes, 0.004 secs

The TSIG 'xfer-example.com' line confirms the transfer was authenticated with your key name—not DNSSEC signing.

Compare SOA serials from both servers:

bash
dig @192.0.2.10 example.com SOA +short
bash
dig @192.0.2.20 example.com SOA +short

Matching serial numbers mean the secondary caught up. Query the changed record from the secondary to confirm the data itself replicated.

Check that the secondary database exists:

bash
ls -l /var/named/slaves/

Modern BIND may store secondary zone data in raw binary format depending on configuration. Do not assume the transferred file is always plain text.

If NOTIFY did not trigger a refresh, force a retransfer from the secondary:

bash
rndc retransfer example.com

rndc uses its own control key and is separate from TSIG.


Troubleshoot TSIG and Zone Transfer Errors

Error or symptom Likely cause Check or recovery
BADKEY or unknown key Key name differs Compare key names in both include files and zone blocks
BADSIG Secret or algorithm differs Regenerate or securely recopy the key file
BADTIME or clocks unsynchronized Time skew between servers Verify chronyc tracking and timedatectl on both hosts
Transfer failed or REFUSED allow-transfer mismatch Confirm the zone allows key xfer-example.com
not authoritative Wrong server or zone definition Check the primary zone type and file path
Secondary serial does not change SOA serial not incremented Bump the serial and reload the primary zone
Connection timeout Firewall or routing issue Verify TCP/UDP 53 and reachability between hosts
Permission denied on key file Ownership, mode, or SELinux label Check root:named, mode 640, run restorecon, confirm named_conf_t
Permission denied while using dig -k Current shell user cannot read the protected key Run the test as root or use a separately protected administrative copy
Could not find expected TSIG or unsigned response One side expected a signed transaction but the other did not use the configured key Check primaries, also-notify, and whether both servers loaded the key include
Secondary keeps old data Transfer not triggered Review NOTIFY in the journal or run rndc retransfer

Practical diagnostics: journalctl -u named, named-checkconf, named-checkzone, SOA queries with dig, and a controlled dig ... AXFR -k keyfile test from the secondary.

For general BIND service problems outside TSIG, see configure a BIND DNS server with named chroot.


TSIG Security Best Practices and Limitations

  • Use hmac-sha256 for new TSIG keys; avoid legacy hmac-md5 and hmac-sha1.
  • Use a different TSIG key for unrelated server relationships where practical.
  • Restrict access to the shared-secret file with root:named ownership and mode 640.
  • Transfer keys only through SSH or another protected channel.
  • Rotate the key when a server or administrator with key access is compromised.
  • Remove obsolete keys only after the replacement transfer succeeds.
  • Do not confuse TSIG with rndc authentication—they protect different operations.
  • Do not confuse TSIG with DNSSEC zone signing.
  • TSIG authenticates and integrity-checks transfers; it does not encrypt zone contents.
  • When confidentiality is required and your BIND versions support it, evaluate network-layer protection or zone transfer over TLS in addition to TSIG.

Summary

TSIG lets a BIND primary accept zone transfers only from a secondary that possesses the matching shared secret. Generate the key once with tsig-keygen, store it in a protected include file on both servers, restrict allow-transfer on the primary zone, and associate the key with the primary address in the secondary zone definition.

Verify the control with named-checkconf on both hosts, journal logs showing TSIG 'key-name', and matching SOA serials after you increment the zone serial. For the base replication layout, start with configure a BIND primary and secondary DNS server. For signed authoritative answers to resolvers, follow your distribution's DNSSEC signing documentation separately from this transfer-authentication workflow.


References

Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …