Table of Contents
Overview
This post will provide a documented build of two BIND 9 DNS servers in a primary/secondary configuration. I will be using a Rocky Linux 10.1 Minimal ISO to do the base install, but will not be documenting that installation.
I wanted to research and learn how to configure a dual stack, IPv4 and IPv6 setup. There definitely are some quirks to IPv6 that I was not aware of prior to this.
RTFM
BIND 9 Administrator Reference Manual
Initial Configuration
There are a few items that should be accomplished before moving forward. The first is to update the system.
sudo dnf update -y
The next thing is to verify that hostnames and IP addresses are configured and expected.
hostname
This should produce a fully qualified domain name, not a short name.
ip address
This should produce address information for IPv4, IPv6 Global Unique, and IPv6 Link-Local.

Now is a good time to record these addresses.
Download the Packages
I am using the repository packages to install bind.
sudo dnf install -y bind bind-utils
BIND File Structure and Terminology
Bind uses a single configuration file called named.conf. On both servers, this configuration file is located at /etc/named.conf. The file consists of:
Comment
There are various comment styles.
/* This is a BIND comment as in C */
// This is a BIND comment as in C++
# This is a BIND comment as in common Unix shells
# and Perl
; This is a comment used in a zone file
Block
Blocks are containers for statements
Statement
Statements define and control specific behaviors. They may have a single value parameter or consist of multiple argument/value pairs.
Primary Server
On the primary server, build out zone directories for containing forward-mapped zone files and reverse-mapped zone files.
sudo mkdir -p /var/named/zones/{forward, reverse}
The directory should like this:
/var/named
├── data
│ └── named.run
├── dynamic
│ ├── managed-keys.bind
│ └── managed-keys.bind.jnl
├── named.ca
├── named.empty
├── named.localhost
├── named.loopback
└── zones
├── forward
└── reverse
Now apply the appropriate permissions:
sudo find /var/named/zones -type d -exec chmod 750 {} \;
sudo find /var/named/zones -type d -exec chown named:named {} \;
Let’s also ensure SELinux contexts are restored.
sudo restorecon -Rv /var/named
Configure the Service
Primary Server – named.conf
Open up the named.conf file in a text editor.
sudo vi /etc/named.conf
Optionally, set line numbers for easier reference.
:set nu
The statements in the blocks do not need to be in any particular order in the file. The only requirement is the level and block at which they are added. They can be in a logical order or alphabetical. I will add in a truncated example here, which I have sorted alphabetically, but the full list can be found in the BIND Administrator Manual, options Block Grammar section.
options {
allow-query { <address_match_element>; ... };
allow-recursion { <address_match_element>; ... };
directory <quoted_string>;
dnssec-validation ( yes | no | auto );
dump-file <quoted_string>;
forward ( first | only );
forwarders [ port <integer> ] [ tls <string> ] { ( <ipv4_address> | <ipv6_address> ) [ port <integer> ] [ tls <string> ]; ... };
geoip-directory ( <quoted_string> | none );
listen-on [ port <integer> ] [ proxy <string> ] [ tls <string> ] [ http <string> ] { <address_match_element>; ... }; // may occur multiple times
listen-on-v6 [ port <integer> ] [ proxy <string> ] [ tls <string> ] [ http <string> ] { <address_match_element>; ... }; // may occur multiple times
managed-keys-directory <quoted_string>;
memstatistics-file <quoted_string>;
pid-file ( <quoted_string> | none );
recursing-file <quoted_string>;
recursion <boolean>;
secroots-file <quoted_string>;
session-keyfile ( <quoted_string> | none );
statistics-file <quoted_string>;
};
Options Block
Interfaces Statements
In the options block, ensure the following two statements are configured. The first statement is for IPv4 and the second is for IPv6.
listen-on port 53 {
127.0.0.1;
172.16.3.100;
};
listen-on-v6 port 53 {
::1;
2603:7080:7407:dd36:20c:29ff:fea1:c8b5;
};
Access Control
The next statement can be tightened for security, but I am allowing any.
allow-query { any; };
Next, I want to ensure this DNS recursively goes out to get an answer. I also specify from which subnets these are permitted.
recursion yes;
allow-recursion {
127.0.0.1;
172.16.0.0/16;
2603:7080:7407:dd36::/64;
};
Forwarding
In the next statement, I want to ensure that if the forwarders are down, the query will fail. In the forwarders statement, I provided a list of where to forward to.
forward only;
forwarders {
1.1.1.1;
1.0.0.1;
2606:4700:4700::1111;
2606:4700:4700::1001;
};
Boolean Options
I set the dnssec-validation to auto so that it configures itself.
dnssec-validation auto;
Zone Block
At the end of the /etc/named.conf file is where I started to add zone blocks.
This is an example of a zone block.
zone "lab.aaronrombaut.com" IN {
type primary;
file "zones/forward/lab.aaronrombaut.com.zone";
allow-transfer {
172.16.3.101;
2603:7080:7407:dd36:20c:29ff:fe87:ff78;
};
notify explicit;
also-notify {
172.16.3.101;
2603:7080:7407:dd36:20c:29ff:fe87:ff78;
};
};
A couple things to note here:
type primary;
This specifies the kind of zone.
file <quoted_string>;
This specifies the zone’s filename.
allow-transfer [ port <integer> ] [ transport <string> ] { <address_match_element>; ... };
This specifies which hosts are allowed to receive zone transfers from the server.
notify ( explicit | master-only | primary-only | <boolean> );
also-notify [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
This one is a little tricky, there is some logic behind it. The also-notify option is only meaningful when there is a notify option. Notify has various logic built-in.
notify yes
This is the default setting. DNS NOTIFY(SOA) messages are sent when a zone the server is authoritative for changes.
Messages are sent to the servers listed in the zone’s NS records and to any servers listed in the also-notify option.
notify primary-only
Notifies are only sent for primary zones.
notify explicit
Notifies are sent only to servers explicitly listed in the also-notify option.
notify no
No notifies are sent.
Primary Server – Zone Files
One thing to note — and this may be common knowledge for BIND administrators — is that the format for the serial is:
YYYYMMDDnn
YYYY = Year
MM = Month
DD = Day
nn = Revision number for that day
An example of this is:
2026012801
Forward-Mapped Zone File
This is an example of a forward zone file.
$TTL 1h
;------------------------------------------------------------------------------
; Forward Zone: lab.aaronrombaut.com
; Purpose : Authoritative records for lab hosts (A/AAAA/CNAME/MX/TXT/etc.)
; Notes :
; - Use semicolons for comments.
; - Trailing dot (.) means "absolute FQDN". Without it, BIND appends the zone.
; - Parentheses allow a single record (SOA) to span multiple lines.
; - Keep the serial monotonically increasing (recommended format: YYYYMMDDnn).
;------------------------------------------------------------------------------
@ IN SOA dnsntp1-v163-100.lab.aaronrombaut.com. admin.lab.aaronrombaut.com. (
2026012801 ; serial (YYYYMMDDnn)
1h ; refresh (slave checks for updates)
15m ; retry (if refresh fails)
1w ; expire (slave stops serving after this)
1h ) ; minimum (negative cache TTL)
;------------------------------------------------------------------------------
; Authoritative name servers for this zone
;------------------------------------------------------------------------------
IN NS dnsntp1-v163-100.lab.aaronrombaut.com.
IN NS dnsntp2-v163-101.lab.aaronrombaut.com.
;------------------------------------------------------------------------------
; Optional: zone apex records
;------------------------------------------------------------------------------
;@ IN A 172.16.3.10
;@ IN AAAA 2603:7080:7407:dd36::10
;@ IN TXT "lab zone - authoritative DNS via dnsntp1/dnsntp2"
;------------------------------------------------------------------------------
; Infrastructure (examples)
; Keep hostnames on the left as *relative* names (no trailing dot).
;------------------------------------------------------------------------------
dnsntp1-v163-100 IN A 172.16.3.100
dnsntp1-v163-100 IN AAAA 2603:7080:7407:dd36::100
dnsntp2-v163-101 IN A 172.16.3.101
dnsntp2-v163-101 IN AAAA 2603:7080:7407:dd36::101
; Root and Intermediate CA (examples)
rootca-v163-102 IN A 172.16.3.102
rootca-v163-102 IN AAAA 2603:7080:7407:dd36::102
intca1-v163-103 IN A 172.16.3.103
intca1-v163-103 IN AAAA 2603:7080:7407:dd36::103
;------------------------------------------------------------------------------
; Friendly service aliases (stable names you point clients at)
;------------------------------------------------------------------------------
dns IN CNAME dnsntp1-v163-100
ntp IN CNAME dnsntp1-v163-100
;------------------------------------------------------------------------------
; Web/app examples (optional)
;------------------------------------------------------------------------------
;web IN A 172.16.10.50
;web IN AAAA 2603:7080:7407:dd3A::50
;wiki IN CNAME web
;------------------------------------------------------------------------------
; End of zone
;------------------------------------------------------------------------------
Reverse-Mapped Zone File – IPv4
This is an example of a reverse zone file for IPv4.
$TTL 1h
;------------------------------------------------------------------------------
; IPv4 Reverse Zone: 3.16.172.in-addr.arpa
; Purpose : PTR records for 172.16.3.0/24
; Notes :
; - Left-hand side is the last octet only (100, 101, etc.)
;------------------------------------------------------------------------------
@ IN SOA dnsntp1-v163-100.lab.aaronrombaut.com. admin.lab.aaronrombaut.com. (
2026012801
1h
15m
1w
1h )
IN NS dnsntp1-v163-100.lab.aaronrombaut.com.
IN NS dnsntp2-v163-101.lab.aaronrombaut.com.
; PTRs
100 IN PTR dnsntp1-v163-100.lab.aaronrombaut.com.
101 IN PTR dnsntp2-v163-101.lab.aaronrombaut.com.
102 IN PTR rootca-v163-102.lab.aaronrombaut.com.
103 IN PTR intca1-v163-103.lab.aaronrombaut.com.
Reverse-Mapped Zone File – IPv6
This is an example of a reverse zone file for IPv6. Note the records are nibble-reversed.
$TTL 1h
;------------------------------------------------------------------------------
; IPv6 Reverse Zone: 6.3.d.d.7.0.4.7.0.8.0.7.3.0.6.2.ip6.arpa
; Purpose : PTR records for 2603:7080:7407:dd36::/64
;
; Notes (important):
; - IPv6 reverse is nibble-based.
; - For a /64 zone, the owner name contains the *host* nibbles (last 64 bits),
; reversed, which is 16 nibbles total.
; - Example host ::0100 (i.e., ...:0000:0000:0000:0100) becomes:
; 0.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0
;------------------------------------------------------------------------------
@ IN SOA dnsntp1-v163-100.lab.aaronrombaut.com. admin.lab.aaronrombaut.com. (
2026012801
1h
15m
1w
1h )
IN NS dnsntp1-v163-100.lab.aaronrombaut.com.
IN NS dnsntp2-v163-101.lab.aaronrombaut.com.
; PTRs for ::100, ::101, ::102, ::103 (aligned with IPv4 last octet)
; ::0100 -> 0.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0
0.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR dnsntp1-v163-100.lab.aaronrombaut.com.
; ::0101 -> 1.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0
1.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR dnsntp2-v163-101.lab.aaronrombaut.com.
2.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR rootca-v163-102.lab.aaronrombaut.com.
3.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR intca1-v163-103.lab.aaronrombaut.com.
Secondary Server
On the secondary server, build out a secondary directory.
sudo mkdir -p /var/named/secondary
The directory should look like this:
/var/named
├── data
│ └── named.run
├── dynamic
│ ├── managed-keys.bind
│ └── managed-keys.bind.jnl
├── named.ca
├── named.empty
├── named.localhost
├── named.loopback
└── secondary
Now apply the appropriate permissions:
sudo find /var/named/secondary -type d -exec chmod 750 {} \;
sudo find /var/named/secondary -type d -exec chown named:named {} \;
Let’s also ensure SELinux contexts are restored.
sudo restorecon -Rv /var/named
Secondary Server – named.conf
Copy the named.conf file from the primary server. Open up the named.conf file in a text editor.
sudo vi /etc/named.conf
Optionally, set line numbers for easier reference.
:set nu
Update the IP addresses in the listen-on and listen-on-v6 statements with the appropriate addresses from this server.
The only next thing to do is change the options in the zone blocks. The type will be secondary and there will be a primaries option containing a list of primary server addresses.
zone "lab.aaronrombaut.com" IN {
type secondary;
primaries {
172.16.3.100;
2603:7080:7407:dd36:20c:29ff:fea1:c8b5;
};
file "secondary/lab.aaronrombaut.com.zone";
};
Validation Commands
The following commands should be used to validate configurations. They are part of the bind-utils package.
The following can be run on both primary and secondary servers. This will check the named.conf file for any errors.
sudo named-checkconf
These next three blocks check the zone files for correctness on the primary server. If named-checkconf and the zone checks succeed, reloading named should be safe.
sudo named-checkzone lab.aaronrombaut.com \
/var/named/zones/forward/lab.aaronrombaut.com.zone
sudo named-checkzone 3.16.172.in-addr.arpa \
/var/named/zones/reverse/3.16.172.in-addr.arpa.zone
sudo named-checkzone 6.3.d.d.7.0.4.7.0.8.0.7.3.0.6.2.ip6.arpa \
/var/named/zones/reverse/6.3.d.d.7.0.4.7.0.8.0.7.3.0.6.2.ip6.arpa.zone
Reload named after changes.
sudo rndc reload
And then check the zonestatus on both the primary and secondary servers:
sudo rndc zonestatus lab.aaronrombaut.com
Primary
On the primary server, it should reflect the type as primary for the zone.
name: lab.aaronrombaut.com
type: primary
files: zones/forward/lab.aaronrombaut.com.zone
serial: 2026092801
nodes: 3
last loaded: Tue, 27 Jan 2026 15:33:40 GMT
secure: no
dynamic: no
reconfigurable via modzone: no
Secondary
On the secondary server, it should reflect the type as secondary for the zone.
name: lab.aaronrombaut.com
type: secondary
files: secondary/lab.aaronrombaut.com.zone
serial: 2026092801
nodes: 3
last loaded: Wed, 28 Jan 2026 15:42:13 GMT
next refresh: Wed, 28 Jan 2026 17:22:28 GMT
expires: Wed, 04 Feb 2026 15:42:13 GMT
secure: no
dynamic: no
reconfigurable via modzone: no
Firewall Configuration
Get a list of the current defined services.
sudo firewall-cmd --list-services
If DNS is not present, add it.
sudo firewall-cmd --permanent --add-service=dns
Then reload the firewall configuration.
sudo firewall-cmd --reload
Conclusion
This build provides a reliable DNS foundation for a lab environment: the primary server remains the single source of truth for zone content, while the secondary automatically receives updates via zone transfers, improving resiliency and reducing configuration drift. With recursion handled via forwarders, SELinux contexts restored, and firewall rules in place for DNS and NTP, the result is a clean, repeatable deployment that supports both IPv4 and IPv6.
Leave a Reply