Debian

Configure BIND DNS Server on Debian 13 / Debian 12 – Primary and Secondary

BIND (Berkeley Internet Name Domain) is the most widely deployed DNS server software on the internet. It handles authoritative DNS for domains and recursive resolution for clients. If you manage your own domain and want full control over DNS records, running your own BIND server is the standard approach.

This guide covers installing and configuring BIND 9 as both a primary (master) and secondary (slave) DNS server on Debian 13 and Debian 12. You will set up forward and reverse zones, configure zone transfers between primary and secondary servers, open firewall ports, and verify everything with dig and nslookup. For the official BIND documentation, see the BIND 9 Administrator Reference Manual.

Prerequisites

  • Two servers running Debian 13 (Trixie) or Debian 12 (Bookworm) with static IP addresses
  • Root or sudo access on both servers
  • A domain name you want to host DNS for (this guide uses example.lan as the example domain)
  • Port 53 (TCP and UDP) open on both servers
  • Static IPs assigned – DHCP will cause problems when the address changes

Our lab environment uses these values throughout the guide – replace them with your actual IPs and domain:

ServerHostnameIP Address
Primary DNSns1.example.lan10.0.1.10
Secondary DNSns2.example.lan10.0.1.11
Web Serverwww.example.lan10.0.1.20
Mail Servermail.example.lan10.0.1.30

Step 1: Install BIND 9 DNS Server on Debian

Run these commands on both the primary and secondary servers. Start by updating the package index:

sudo apt update

Install BIND 9 and the DNS utilities package:

sudo apt install -y bind9 bind9utils bind9-doc dnsutils

Confirm that BIND installed and is running:

systemctl status bind9

The output should show the service as active (running):

● named.service - BIND Domain Name Server
     Loaded: loaded (/lib/systemd/system/named.service; enabled; preset: enabled)
     Active: active (running)
   Main PID: 1234 (named)
      Tasks: 5 (limit: 4915)
     Memory: 28.0M
        CPU: 85ms
     CGroup: /system.slice/named.service
             └─1234 /usr/sbin/named -f -u bind

Check the installed BIND version:

named -v

You should see the BIND version string confirming a 9.18+ or 9.19+ release depending on your Debian version:

BIND 9.18.28-1~deb12u2-Debian (Extended Support Version) <id:...>

Step 2: Configure BIND Options (named.conf.options)

The global BIND options file controls forwarders, recursion, and access control. Edit it on the primary server:

sudo vi /etc/bind/named.conf.options

Replace the default contents with the following configuration. This sets up DNS forwarders, enables recursion for your network, and listens on all interfaces:

options {
    directory "/var/cache/bind";

    // Forwarders - upstream DNS servers for queries this server cannot resolve
    forwarders {
        8.8.8.8;
        1.1.1.1;
    };

    // Listen on all interfaces
    listen-on { any; };
    listen-on-v6 { any; };

    // Allow queries from your network
    allow-query { localhost; 10.0.1.0/24; };

    // Enable recursion for local clients
    recursion yes;
    allow-recursion { localhost; 10.0.1.0/24; };

    // Disable zone transfers by default (override per-zone)
    allow-transfer { none; };

    // DNSSEC validation
    dnssec-validation auto;
};

Key settings explained:

  • forwarders – upstream resolvers for queries outside your authoritative zones
  • allow-query – restricts who can send DNS queries to this server
  • recursion – lets the server resolve names on behalf of clients (disable on public-facing authoritative-only servers)
  • allow-transfer { none; } – blocks zone transfers globally; we allow them per-zone for the secondary server
  • dnssec-validation auto – validates DNSSEC signatures using the built-in trust anchor

Step 3: Create Forward and Reverse Zones on the Primary Server

Zone declarations tell BIND which domains it is authoritative for. Edit the local configuration file:

sudo vi /etc/bind/named.conf.local

Add the forward zone and reverse zone definitions. The allow-transfer and also-notify directives enable zone transfers to the secondary server:

// Forward zone
zone "example.lan" IN {
    type master;
    file "/etc/bind/zones/db.example.lan";
    allow-transfer { 10.0.1.11; };   // Secondary server IP
    also-notify { 10.0.1.11; };      // Notify secondary on changes
    allow-update { none; };
};

// Reverse zone for 10.0.1.0/24
zone "1.0.10.in-addr.arpa" IN {
    type master;
    file "/etc/bind/zones/db.10.0.1";
    allow-transfer { 10.0.1.11; };
    also-notify { 10.0.1.11; };
    allow-update { none; };
};

Create the directory for zone files:

sudo mkdir -p /etc/bind/zones

Step 4: Create the Forward Zone File

The forward zone file maps hostnames to IP addresses. Create it:

sudo vi /etc/bind/zones/db.example.lan

Add the following DNS records. Adjust the domain name, IPs, and hostnames for your environment:

$TTL    604800
@       IN      SOA     ns1.example.lan. admin.example.lan. (
                     2026032201         ; Serial (YYYYMMDDNN format)
                         604800         ; Refresh (7 days)
                          86400         ; Retry (1 day)
                        2419200         ; Expire (28 days)
                         604800 )       ; Negative Cache TTL (7 days)

; Name servers
@       IN      NS      ns1.example.lan.
@       IN      NS      ns2.example.lan.

; A records for name servers
ns1     IN      A       10.0.1.10
ns2     IN      A       10.0.1.11

; Mail exchanger
@       IN      MX  10  mail.example.lan.

; A records for hosts
www     IN      A       10.0.1.20
mail    IN      A       10.0.1.30

; CNAME records
ftp     IN      CNAME   www.example.lan.

Important points about the zone file format:

  • All fully qualified domain names (FQDNs) must end with a dot (.)
  • The serial number must be incremented every time you modify the zone – the secondary server uses this to detect changes
  • The SOA admin.example.lan. represents the admin email ([email protected] – the first dot replaces the @)

Step 5: Create the Reverse Zone File

The reverse zone maps IP addresses back to hostnames (PTR records). Create the reverse zone file:

sudo vi /etc/bind/zones/db.10.0.1

Add the reverse DNS records. The numbers on the left represent the last octet of each IP address:

$TTL    604800
@       IN      SOA     ns1.example.lan. admin.example.lan. (
                     2026032201         ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL

; Name servers
@       IN      NS      ns1.example.lan.
@       IN      NS      ns2.example.lan.

; PTR records
10      IN      PTR     ns1.example.lan.
11      IN      PTR     ns2.example.lan.
20      IN      PTR     www.example.lan.
30      IN      PTR     mail.example.lan.

Step 6: Validate BIND Configuration and Zone Files

Before restarting BIND, check for syntax errors in the configuration:

sudo named-checkconf

If there are no errors, the command returns silently to the prompt. Any syntax issues will be printed with line numbers.

Validate the forward zone file:

sudo named-checkzone example.lan /etc/bind/zones/db.example.lan

A valid zone file returns “OK” with the serial number:

zone example.lan/IN: loaded serial 2026032201
OK

Validate the reverse zone file:

sudo named-checkzone 1.0.10.in-addr.arpa /etc/bind/zones/db.10.0.1

You should see the same “OK” confirmation:

zone 1.0.10.in-addr.arpa/IN: loaded serial 2026032201
OK

Restart BIND and enable it to start at boot:

sudo systemctl restart bind9
sudo systemctl enable bind9

Verify the service is running after restart:

systemctl status bind9 --no-pager

Step 7: Configure the Secondary (Slave) DNS Server

A secondary DNS server provides redundancy and load distribution. It automatically receives zone data from the primary through zone transfers. All commands in this section run on the secondary server (10.0.1.11).

After installing BIND 9 (Step 1), configure the options file on the secondary server:

sudo vi /etc/bind/named.conf.options

Use the same options as the primary server, but adjust allow-query to match your network:

options {
    directory "/var/cache/bind";

    forwarders {
        8.8.8.8;
        1.1.1.1;
    };

    listen-on { any; };
    listen-on-v6 { any; };
    allow-query { localhost; 10.0.1.0/24; };

    recursion yes;
    allow-recursion { localhost; 10.0.1.0/24; };
    allow-transfer { none; };

    dnssec-validation auto;
};

Now configure the secondary zones. Edit the local configuration:

sudo vi /etc/bind/named.conf.local

Add the slave zone definitions pointing to the primary server’s IP. The type slave directive tells BIND to request zone data from the master rather than reading local files:

// Forward zone (secondary)
zone "example.lan" IN {
    type slave;
    file "/var/cache/bind/db.example.lan";
    masters { 10.0.1.10; };    // Primary server IP
};

// Reverse zone (secondary)
zone "1.0.10.in-addr.arpa" IN {
    type slave;
    file "/var/cache/bind/db.10.0.1";
    masters { 10.0.1.10; };
};

The secondary stores received zone data in /var/cache/bind/. These files are created automatically after a successful zone transfer – you do not need to create them manually.

Check the configuration syntax and restart BIND on the secondary:

sudo named-checkconf
sudo systemctl restart bind9
sudo systemctl enable bind9

Check the BIND logs to confirm zone transfers completed successfully:

sudo journalctl -u bind9 --no-pager -n 20

Look for lines indicating successful zone transfers like “transfer of ‘example.lan/IN’ from 10.0.1.10: Transfer status: success”.

Step 8: Open Firewall Ports for DNS (Port 53)

DNS uses port 53 on both TCP and UDP. TCP is required for zone transfers and large responses, while UDP handles standard queries. If you are running firewalld on Debian, open port 53 on both servers:

sudo apt install -y firewalld
sudo systemctl enable --now firewalld

Add the DNS service to the firewall and reload:

sudo firewall-cmd --permanent --add-service=dns
sudo firewall-cmd --reload

Verify port 53 is open:

sudo firewall-cmd --list-services

The output should include dns in the active services list. If you use ufw instead of firewalld, run sudo ufw allow 53/tcp and sudo ufw allow 53/udp.

If you use nftables directly, add these rules:

sudo nft add rule inet filter input tcp dport 53 accept
sudo nft add rule inet filter input udp dport 53 accept

Step 9: Test DNS Resolution with dig and nslookup

From any client machine on the same network, test the primary DNS server. First, set the DNS resolver to point to the primary server by editing /etc/resolv.conf:

sudo vi /etc/resolv.conf

Add the primary server as the nameserver:

nameserver 10.0.1.10
nameserver 10.0.1.11

Forward Lookup Test

Query the primary server for the A record of www.example.lan:

dig www.example.lan @10.0.1.10

The ANSWER SECTION should return the IP address 10.0.1.20:

;; ANSWER SECTION:
www.example.lan.     604800  IN  A   10.0.1.20

Reverse Lookup Test

Test reverse DNS resolution by looking up an IP address:

dig -x 10.0.1.20 @10.0.1.10

The PTR record should return the hostname:

;; ANSWER SECTION:
20.1.0.10.in-addr.arpa. 604800 IN  PTR www.example.lan.

nslookup Test

You can also use nslookup for a quick check:

nslookup www.example.lan 10.0.1.10

The response should show the server address and the resolved IP:

Server:     10.0.1.10
Address:    10.0.1.10#53

Name:       www.example.lan
Address:    10.0.1.20

Step 10: Test Zone Transfer Between Primary and Secondary

Verify that the secondary server received the zone data from the primary. Query the secondary server directly:

dig www.example.lan @10.0.1.11

The response should match what the primary server returns – the same A record with IP 10.0.1.20.

You can also request a full zone transfer (AXFR) from the primary to verify it works:

dig axfr example.lan @10.0.1.10

This should return all records in the zone. If it shows “Transfer failed”, check the allow-transfer directive on the primary server and ensure the secondary’s IP is listed.

To test that zone updates propagate, modify a record on the primary server and increment the serial number. Then run on the secondary:

sudo rndc retransfer example.lan

This forces an immediate zone transfer without waiting for the refresh interval.

Step 11: DNSSEC Basics for BIND DNS Server

DNSSEC adds cryptographic signatures to DNS records, protecting against cache poisoning and man-in-the-middle attacks. BIND 9.18+ on Debian ships with DNSSEC validation enabled by default (dnssec-validation auto).

To sign your own zones with DNSSEC, you need to generate keys and configure BIND to use them. For a detailed walkthrough of zone signing and key management, see our guide on securing BIND DNS with DNSSEC keys.

At minimum, verify that DNSSEC validation is working on your server:

dig +dnssec cloudflare.com @10.0.1.10

Look for the ad flag (Authenticated Data) in the response header flags. If present, DNSSEC validation is working correctly.

Step 12: Common BIND DNS Troubleshooting

If DNS resolution fails or zones do not transfer, these commands help diagnose the issue.

Check BIND logs for errors:

sudo journalctl -u bind9 --no-pager -n 50

Verify BIND is listening on port 53:

sudo ss -tulnp | grep named

You should see named listening on port 53 for both TCP and UDP:

udp   UNCONN 0      0       0.0.0.0:53       0.0.0.0:*    users:(("named",pid=1234,fd=512))
tcp   LISTEN 0      10      0.0.0.0:53       0.0.0.0:*    users:(("named",pid=1234,fd=513))

Reload zones without restarting the service (useful after editing zone files):

sudo rndc reload

Check the status of all loaded zones:

sudo rndc status

If you run into “REFUSED” responses, verify that the querying client’s IP is included in the allow-query directive. If zone transfers fail, check that the secondary’s IP is in allow-transfer on the primary. You can also monitor BIND DNS performance with tools like Prometheus and Grafana for BIND to track query rates, cache hit ratios, and zone transfer events.

Conclusion

You now have a working BIND 9 DNS setup on Debian 13 / Debian 12 with primary and secondary servers, forward and reverse zones, zone transfers, and firewall rules in place. The secondary server provides redundancy – if the primary goes down, DNS resolution continues from the secondary’s cached zone data.

For production environments, consider implementing DNSSEC zone signing, enabling query logging for auditing, and setting up monitoring to track DNS performance. If you need a DNS management web interface, PowerDNS with PowerDNS-Admin on Debian is a solid alternative. For lightweight internal DNS, see our guide on Dnsmasq installation and configuration.

Related Articles

Monitoring Install Zabbix Agent 2 on Debian 13/12 – Step-by-Step Setup Security Install and Use pi-hole on Linux – A black hole for Internet advertisements Databases How To Install Apache Spark on Debian 11 / Debian 10 Networking Cheap Ethernet Gigabit Switches with VLAN to buy in 2023

Press ESC to close