How to Set Up a DNS Server with BIND
Install and configure BIND 9 as a caching resolver and authoritative nameserver, with forward/reverse zones, common resource records, and firewall rules.
Before you start
- ▸Root or sudo access on the target server
- ▸A static IP address assigned to the server
- ▸Basic familiarity with DNS concepts (zones, records, TTL)
- ▸Firewall management tools installed (firewalld, ufw, or nftables)
BIND (Berkeley Internet Name Domain) remains the most widely deployed DNS server software on the internet. This guide walks through installing and configuring BIND 9 as both a caching resolver and an authoritative nameserver for a primary zone. You will configure forward and reverse zones, add common resource records, lock down recursion, and verify everything resolves correctly. Commands are shown for Debian/Ubuntu, Fedora/RHEL, and Arch Linux where they differ.
Install BIND 9
Debian / Ubuntu
sudo apt update
sudo apt install bind9 bind9-utils bind9-doc
Fedora / RHEL 9 / Rocky Linux 9
sudo dnf install bind bind-utils
Arch Linux
sudo pacman -S bind
The primary configuration directory is /etc/bind/ on Debian-family systems and /etc/named/ (with the main file at /etc/named.conf) on RHEL-family. Arch uses /etc/named.conf directly. Zone data typically lives under /var/lib/bind/ (Debian) or /var/named/ (RHEL/Arch).
Configure the Caching Resolver
Edit the main named options file. On Debian/Ubuntu this is /etc/bind/named.conf.options; on RHEL/Arch it is the options block in /etc/named.conf.
sudo nano /etc/bind/named.conf.options # Debian/Ubuntu
# sudo nano /etc/named.conf # RHEL/Arch
Replace or merge the options block with the following. Substitute your actual trusted networks for the ACL.
acl "trusted" {
127.0.0.1;
::1;
192.168.1.0/24; # your LAN subnet
};
options {
directory "/var/cache/bind"; # /var/named on RHEL/Arch
recursion yes;
allow-recursion { trusted; };
allow-query { trusted; };
allow-transfer { none; }; # lock down zone transfers
forwarders {
1.1.1.1;
8.8.8.8;
};
forward only; # remove this line to fall back to root hints
dnssec-validation auto;
listen-on { any; };
listen-on-v6 { any; };
};
Security note: setting recursion yes without the ACL guard creates an open resolver that can be abused for amplification attacks. The ACL above ensures only your trusted hosts can recurse.
Define Authoritative Zones
On Debian/Ubuntu, zone declarations belong in /etc/bind/named.conf.local. On RHEL/Arch, add them directly to /etc/named.conf. The example domain is example.lan with the subnet 192.168.1.0/24.
sudo nano /etc/bind/named.conf.local # Debian/Ubuntu
# Forward zone
zone "example.lan" {
type master;
file "/var/lib/bind/db.example.lan"; # /var/named/db.example.lan on RHEL/Arch
allow-update { none; }; # no dynamic updates for now
};
# Reverse zone for 192.168.1.0/24
zone "1.168.192.in-addr.arpa" {
type master;
file "/var/lib/bind/db.192.168.1"; # /var/named/db.192.168.1 on RHEL/Arch
allow-update { none; };
};
Create Zone Files
Forward Zone
sudo nano /var/lib/bind/db.example.lan
$TTL 86400
@ IN SOA ns1.example.lan. hostmaster.example.lan. (
2024060101 ; Serial (YYYYMMDDnn)
3600 ; Refresh
900 ; Retry
604800 ; Expire
300 ) ; Negative TTL
; Name servers
@ IN NS ns1.example.lan.
@ IN NS ns2.example.lan.
; A records
ns1 IN A 192.168.1.10
ns2 IN A 192.168.1.11
@ IN A 192.168.1.10
www IN A 192.168.1.20
mail IN A 192.168.1.30
; MX record
@ IN MX 10 mail.example.lan.
; CNAME example
ftp IN CNAME www.example.lan.
; TXT record (SPF example)
@ IN TXT "v=spf1 mx -all"
Increment the Serial every time you edit the zone file — secondary nameservers use it to detect changes. The format YYYYMMDDnn is conventional but any monotonically increasing 32-bit integer works.
Reverse Zone
sudo nano /var/lib/bind/db.192.168.1
$TTL 86400
@ IN SOA ns1.example.lan. hostmaster.example.lan. (
2024060101
3600
900
604800
300 )
@ IN NS ns1.example.lan.
@ IN NS ns2.example.lan.
; PTR records (last octet only)
10 IN PTR ns1.example.lan.
11 IN PTR ns2.example.lan.
20 IN PTR www.example.lan.
30 IN PTR mail.example.lan.
Set File Permissions
BIND runs as the bind user on Debian/Ubuntu and named on RHEL/Arch. Zone files must be readable by that user.
# Debian/Ubuntu
sudo chown bind:bind /var/lib/bind/db.example.lan /var/lib/bind/db.192.168.1
sudo chmod 640 /var/lib/bind/db.example.lan /var/lib/bind/db.192.168.1
# RHEL / Arch
sudo chown named:named /var/named/db.example.lan /var/named/db.192.168.1
sudo chmod 640 /var/named/db.example.lan /var/named/db.192.168.1
Validate Configuration and Enable the Service
Check syntax before starting
sudo named-checkconf
sudo named-checkzone example.lan /var/lib/bind/db.example.lan
sudo named-checkzone 1.168.192.in-addr.arpa /var/lib/bind/db.192.168.1
A clean run produces no output from named-checkconf and a short OK line from each named-checkzone call. Fix any reported errors before proceeding.
Enable and start named
sudo systemctl enable --now named # RHEL/Arch name
# OR
sudo systemctl enable --now bind9 # Debian/Ubuntu name
Open the Firewall
firewalld (Fedora / RHEL / Rocky)
sudo firewall-cmd --add-service=dns --permanent
sudo firewall-cmd --reload
ufw (Debian / Ubuntu)
sudo ufw allow 53/tcp
sudo ufw allow 53/udp
nftables (Arch or manual setup)
sudo nft add rule inet filter input udp dport 53 accept
sudo nft add rule inet filter input tcp dport 53 accept
Verify Resolution
# Query the local server directly
dig @127.0.0.1 www.example.lan A
dig @127.0.0.1 example.lan MX
dig @127.0.0.1 -x 192.168.1.20 # reverse lookup
# Test external recursion (should work from trusted subnet)
dig @192.168.1.10 google.com A
# Confirm an external IP cannot recurse
dig @ google.com A # should return REFUSED
Expected output from dig @127.0.0.1 www.example.lan A includes a ANSWER SECTION with www.example.lan. 86400 IN A 192.168.1.20 and a status of NOERROR. Actual TTL and flags will vary slightly.
Troubleshooting
- Service fails to start: Run
sudo journalctl -u named -xe(orbind9) for detailed errors. Most startup failures are syntax errors caught bynamed-checkconf. - SERVFAIL on external names: Check that your forwarders are reachable and that outbound UDP/TCP 53 is not blocked by your upstream firewall. Temporarily remove
forward only;to fall back to root hints. - Zone not loading: Confirm the file path in
named.confmatches the actual file, the serial is a valid 32-bit integer, and the file is readable by thebind/nameduser. - PTR queries return NXDOMAIN: Verify the reverse zone name matches your subnet exactly (e.g.,
1.168.192.in-addr.arpafor192.168.1.0/24) and that PTR records use only the last octet. - SELinux denials on RHEL/Rocky: If zone files are stored outside
/var/named/, restore the correct context:sudo restorecon -Rv /var/named/. Alternatively move files to the expected path.
Frequently asked questions
- What is the difference between an authoritative server and a caching resolver?
- An authoritative server holds the master copy of zone data and answers definitively for its own domains. A caching resolver queries other servers on behalf of clients, stores the results temporarily, and is not authoritative for any zone.
- How do I reload zone changes without restarting BIND?
- After incrementing the serial and editing the zone file, run 'sudo rndc reload example.lan' to reload just that zone, or 'sudo rndc reload' for all zones. No service interruption occurs.
- Why does named-checkzone pass but the zone still fails to load at runtime?
- The most common reasons are a file permission problem (the named/bind user cannot read the file) or, on SELinux-enforcing systems, a missing file context. Check journalctl output and run restorecon on the zone directory.
- Can I run BIND as both a caching resolver and an authoritative server on the same instance?
- Yes, and this guide does exactly that. BIND handles both roles simultaneously: it answers authoritatively for declared zones and recurses to external servers for everything else.
- How should I set up a secondary (slave) nameserver?
- On the primary, add the secondary's IP to the allow-transfer option for each zone. On the secondary, declare the same zones with 'type slave;' and a 'masters { <primary-ip>; };' directive. BIND will automatically pull zone transfers via AXFR.
Related guides
Build a Mesh VPN with Nebula
Build a fully self-hosted mesh VPN with Nebula: create a CA, sign node certs, configure lighthouses, enforce group-based firewall rules, and run as a systemd service.
Common Linux Network Ports Reference
Learn Linux port ranges, read /etc/services, find what's listening with ss and nmap, and apply solid firewall rules to expose or block the right ports.
How to Configure a Static IP on Linux
Configure a static IP on Linux using Netplan, NetworkManager (nmcli), or systemd-networkd across Ubuntu, Fedora, Debian, and Arch with verified steps.
Expose a Service with Cloudflare Tunnel
Expose local services to the internet without port-forwarding using Cloudflare Tunnel. Install cloudflared, create a named tunnel, configure ingress rules, and run as a systemd service.