| Tested on | Rocky Linux 8.4 (Green Obsidian) |
|---|---|
| Package | bind 9.11.26, bind-chroot 9.11.26 |
| Applies to | RHEL 8, Rocky Linux 8, AlmaLinux 8, CentOS Stream 8, Oracle Linux 8 |
| Privilege | sudo or root |
| Scope | BIND primary-to-secondary replication on Rocky Linux 8: allow-transfer, NOTIFY, slave zones, named-chroot, and AXFR checks. You need a working primary with forward and reverse zones first. |
| Related guides | Configure BIND DNS server on Linux Secure BIND zone transfers with TSIG firewalld cheat sheet View logs with journalctl dnf command |
One DNS server is a single point of failure. Add a secondary BIND host and it copies zone files from the primary through AXFR or IXFR, keeps answering when the primary is down, and shares query load.
This guide builds on configure a BIND DNS server on Linux. You already have a working primary, or you create one first, then wire up replication. The examples use Rocky Linux 8 with named-chroot, domain example.com, and subnet 172.29.10.0/24. Swap in your own IPs and zone names where the commands show mine.
allow-transfer on a trusted internal network. It is not a substitute for the primary BIND install walkthrough, TSIG-authenticated transfers, DNSSEC signing, or a dnsmasq caching resolver.
Lab environment
Use two Rocky Linux 8.4 hosts on the same subnet. I named mine master and slave:
| Role | Hostname | IP address |
|---|---|---|
| Primary DNS | master |
172.29.10.4/24 |
| Secondary DNS | slave |
172.29.10.12/24 |
Forward zone: example.com
Reverse zone: 10.29.172.in-addr.arpa (reverse the first three octets of 172.29.10.0/24)
If the primary already runs BIND from the earlier guide, keep your zone files and add only the transfer settings below.
Configure the primary server for zone transfer
On the primary, open zone transfer to the secondary IP and tell BIND to NOTIFY that host when a zone changes.
Install BIND on the primary
Package installs use the dnf command.
dnf install -y bind bind-chrootOn Rocky Linux 8.4 you should see bind and bind-chroot 9.11.26 install:
Rocky Linux 8 - AppStream
Rocky Linux 8 - BaseOS
Rocky Linux 8 - Extras
Total download size: 2.2 M
Installed size: 4.5 M
Downloading Packages:
bind-chroot-9.11.26-4.el8_4.x86_64.rpm
bind-9.11.26-4.el8_4.x86_64.rpm
Installed:
bind-32:9.11.26-4.el8_4.x86_64
bind-chroot-32:9.11.26-4.el8_4.x86_64
Complete!Allow transfer and NOTIFY in named.conf
Edit /etc/named.conf on the primary. In my setup the server listens on all interfaces and allows zone transfer only to 172.29.10.12:
options {
listen-on port 53 { 127.0.0.1; any; };
listen-on-v6 port 53 { ::1; };
directory "/var/named";
dump-file "/var/named/data/cache_dump.db";
statistics-file "/var/named/data/named_stats.txt";
memstatistics-file "/var/named/data/named_mem_stats.txt";
recursing-file "/var/named/data/named.recursing";
secroots-file "/var/named/data/named.secroots";
allow-query { localhost; any; };
allow-query-cache { localhost; any; };
allow-transfer { 172.29.10.12; };
notify yes;
also-notify { 172.29.10.12; };
recursion yes;
...
};
include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";allow-transfer names the host that may pull a full zone copy. also-notify pings that host right after you reload, so it does not sit idle until the SOA refresh timer expires.
Declare primary zones in named.rfc1912.zones
Add the forward and reverse zone blocks to /etc/named.rfc1912.zones on the primary:
zone "example.com" IN {
type master;
file "example.com.zone";
allow-update { none; };
};
zone "10.29.172.in-addr.arpa" IN {
type master;
file "example.com.rzone";
allow-update { none; };
};Forward zone file on the primary
Skip this part if example.com already exists on the primary from the BIND install guide. Otherwise create /var/named/example.com.zone along these lines:
cat /var/named/example.com.zone$TTL 1D
@ IN SOA example.com. root (
5 ; serial
1D ; refresh
1H ; retry
1W ; expire
3H ) ; minimum
IN NS localhost
localhost IN A 127.0.0.1
ns-master IN A 172.29.10.4
server1 IN A 172.29.10.5
server2 IN A 172.29.10.6
slave IN A 172.29.10.12Raise the SOA serial every time you add or change a record. Secondaries only pick up edits when that number increases.
Reverse zone file on the primary
The reverse zone lives in /var/named/example.com.rzone:
cat /var/named/example.com.rzone$TTL 1D
@ IN SOA example.com. root (
5 ; serial
1D ; refresh
1H ; retry
1W ; expire
3H ) ; minimum
IN NS localhost.
4 IN PTR ns-master.example.com.
5 IN PTR server1.example.com.
6 IN PTR server2.example.com.
12 IN PTR slave.example.com.Only the last octet of each address appears on the left because the zone origin is already 10.29.172.in-addr.arpa.
Enable the chroot environment on the primary
On Rocky Linux 8 I ran BIND inside /var/named/chroot:
/usr/libexec/setup-named-chroot.sh /var/named/chroot onConfirm the config files are visible inside the chroot tree:
mount | grep chroot/dev/mapper/rl-root on /var/named/chroot/etc/named.conf type xfs (rw,relatime,...)
/dev/mapper/rl-root on /var/named/chroot/etc/named.rfc1912.zones type xfs (rw,relatime,...)
/dev/mapper/rl-root on /var/named/chroot/var/named type xfs (rw,relatime,...)
tmpfs on /var/named/chroot/run/named type tmpfs (rw,nosuid,nodev,mode=755)Validate primary zone syntax
Check syntax before you start named:
named-checkconf -t /var/named/chroot /etc/named.confValidate each custom zone file:
named-checkzone example.com /var/named/example.com.zonezone example.com/IN: loaded serial 5
OKCheck the reverse zone the same way:
named-checkzone 10.29.172.in-addr.arpa /var/named/example.com.rzonezone 10.29.172.in-addr.arpa/IN: loaded serial 5
OKOK on both checks means BIND can load the zone files inside chroot.
Open DNS on the primary firewall
Open DNS in firewalld on the primary. The firewalld cheat sheet covers permanent service rules and custom ports.
firewall-cmd --add-service=dns --permanent--permanent saves the rule across reboots. Reload firewalld to apply it now:
firewall-cmd --reloadPoint the primary at itself for resolution
Append the primary address to /etc/resolv.conf on the primary host:
echo "nameserver 172.29.10.4" >> /etc/resolv.confOn production systems, set DNS through NetworkManager or a resolv.conf drop-in instead of appending by hand.
Start named-chroot on the primary
Stop the plain named service so only one daemon binds port 53:
systemctl stop namedPrevent the non-chroot unit from starting at boot:
systemctl disable namedEnable and start the chroot service:
systemctl enable --now named-chrootConfirm the daemon is running:
systemctl status named-chroot● named-chroot.service - Berkeley Internet Name Domain (DNS)
Loaded: loaded (/usr/lib/systemd/system/named-chroot.service; enabled; vendor preset: disabled)
Active: active (running) since Tue 2021-08-10 07:34:32 EAT; 42s ago
...
Aug 10 07:34:32 master named[1956]: zone example.com/IN: loaded serial 5
Aug 10 07:34:32 master named[1956]: all zones loaded
Aug 10 07:34:32 master named[1956]: running
Aug 10 07:34:32 master named[1956]: zone 10.29.172.in-addr.arpa/IN: sending notifies (serial 5)
Aug 10 07:34:32 master named[1956]: zone example.com/IN: sending notifies (serial 5)The sending notifies lines mean BIND told the secondary that fresh zone data is ready.
Configure the secondary server
The secondary does not hold writable zone files. It pulls copies from the primary and answers queries from those files.
Install BIND on the secondary
Install the same packages on slave:
dnf install -y bind bind-chrootYou get the same bind and bind-chroot 9.11.26 packages as on the primary.
Configure named.conf on the secondary
The secondary listens on its own address (172.29.10.12) and does not define master zones in options:
options {
listen-on port 53 { 127.0.0.1; 172.29.10.12; };
listen-on-v6 port 53 { ::1; };
directory "/var/named";
...
allow-query { localhost; any; };
recursion yes;
dnssec-enable yes;
...
};
include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";Declare slave zones
Add matching forward and reverse slave stanzas to /etc/named.rfc1912.zones on the secondary:
zone "example.com" IN {
type slave;
masters { 172.29.10.4; };
file "slaves/example.com.zone";
};
zone "10.29.172.in-addr.arpa" IN {
type slave;
masters { 172.29.10.4; };
file "slaves/example.com.rzone";
};masters points at the primary. BIND writes transferred copies under /var/named/slaves/ (inside chroot that path is under /var/named/chroot/var/named/slaves/).
Enable chroot on the secondary
Run the chroot setup script on the slave host:
/usr/libexec/setup-named-chroot.sh /var/named/chroot onVerify mounts:
mount | grep chroot/dev/mapper/rl-root on /var/named/chroot/etc/named.conf type xfs (rw,relatime,...)
/dev/mapper/rl-root on /var/named/chroot/var/named type xfs (rw,relatime,...)
tmpfs on /var/named/chroot/run/named type tmpfs (rw,nosuid,nodev,mode=755)Validate and start the secondary
Check main configuration syntax:
named-checkconf -t /var/named/chroot /etc/named.confPut both DNS addresses in /etc/resolv.conf on the slave:
nameserver 172.29.10.4
nameserver 172.29.10.12Stop the plain named unit:
systemctl stop namedPrevent the non-chroot unit from starting at boot:
systemctl disable namedStart the chroot service:
systemctl start named-chrootOnce the primary is up and reachable, check that zone files arrived:
ls -l /var/named/slaves/total 8
-rw-r--r-- 1 named named 510 Aug 10 05:31 example.com.rzone
-rw-r--r-- 1 named named 430 Aug 10 05:31 example.com.zoneFiles with non-zero size under slaves/ mean AXFR copied both zones from the primary.
Verify zone transfer and replication
After the secondary starts, read its log. On newer hosts you can also follow the service with journalctl:
tail /var/named/data/named.runzone 10.29.172.in-addr.arpa/IN: loaded serial 5
zone example.com/IN: loaded serial 5
all zones loaded
runningBoth zones show serial 5, so the secondary loaded the same revision as the primary.
Test NOTIFY after a record change
Edit the forward zone on the primary and bump the SOA serial from 5 to 6. I added labserver pointing at 172.29.10.50:
$TTL 1D
@ IN SOA example.com root (
6 ; serial
1D ; refresh
1H ; retry
1W ; expire
3H ) ; minimum
IN NS localhost
localhost IN A 127.0.0.1
ns-master IN A 172.29.10.4
server1 IN A 172.29.10.5
server2 IN A 172.29.10.6
slave IN A 172.29.10.12
labserver IN A 172.29.10.50Reload the primary without a full restart:
systemctl reload named-chrootOn the secondary, tail the log again:
tail /var/named/data/named.runclient 172.29.10.12#953: received notify for zone 'example.com'
zone example.com/IN: Transfer started.
transfer of 'example.com/IN' from 172.29.10.4#53: connected using 172.29.10.12#44589
zone example.com/IN: transferred serial 6
transfer of 'example.com/IN' from 172.29.10.4#53: Transfer completed: 1 messages, 7 records, 280 bytes, 0.003 secs (127500 bytes/sec)
zone example.com/IN: sending notifies (serial 6)transferred serial 6 in the log means the update reached the secondary.
Ask the secondary for the new name:
dig labserver.example.com.;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28493
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 3
;; QUESTION SECTION:
;labserver.example.com. IN A
;; ANSWER SECTION:
labserver.example.com. 86400 IN A 172.29.10.50
;; AUTHORITY SECTION:
example.com. 86400 IN NS ns-master.example.com.
example.com. 86400 IN NS slave.example.com.
;; SERVER: 172.29.10.12#53(172.29.10.12)The aa flag and the A record show the secondary is serving the updated zone authoritatively.
Before you expose replication beyond a lab LAN, lock down transfers with TSIG instead of IP addresses alone. See secure BIND zone transfers with TSIG.
Troubleshoot BIND replication
| Symptom | Likely cause | Fix |
|---|---|---|
/var/named/slaves/ empty |
allow-transfer missing or wrong secondary IP on primary |
Add the secondary address; reload primary |
| Secondary never updates after edit | SOA serial not incremented | Bump serial; systemctl reload named-chroot on primary |
REFUSED or NOTAUTH in logs |
Secondary not in allow-transfer ACL |
Match ACL to secondary IP or TSIG key |
| Primary starts, secondary does not | Firewall blocks TCP/UDP 53 between hosts | firewall-cmd --add-service=dns on both nodes |
named-checkzone fails |
Syntax error in zone file | Fix file; re-run check before reload |
Two named instances fight for port 53 |
Both named and named-chroot enabled |
Disable plain named; use one unit only |
References
Summary
You set up BIND primary and secondary servers on Rocky Linux 8 with named-chroot, forward and reverse zones for example.com, and zone transfer to 172.29.10.12. The primary needs allow-transfer and also-notify; the secondary declares type slave zones and stores copies under /var/named/slaves/. When I bumped the SOA serial and reloaded the primary, NOTIFY triggered a transfer, the log showed transferred serial 6, and dig on the secondary returned the new labserver record.
Rocky Linux 8 still ships named-chroot.service; on newer RHEL-family releases you may run plain named without /var/named/chroot and keep the same zone transfer stanzas. If the primary is not in place yet, start with configure a BIND DNS server on Linux. Add TSIG before replication crosses an untrusted network.

