Introduction
BIND9 lets you:
- Host your own authoritative DNS zones
- Primary + secondary (master/slave)
- Enable DNSSEC
- Serve different views per client (split-horizon)
- Do recursion (often decoupled today, prefer Unbound)
- Detailed logging / monitoring
⚠️ BIND is complex with vulnerability history. For pure recursion, prefer Unbound. For authoritative, BIND remains solid.
Prerequisites
- Linux VPS Debian / Ubuntu
- 2 public IPs (ideally 1 primary + 1 secondary at another provider)
- Root access
- Controlled domain (DNS delegation pointed to your servers)
- Port 53 UDP/TCP open
Step 1: Installation
sudo apt update
sudo apt install -y bind9 bind9utils bind9-doc dnsutils
named -v
Step 2: Main config
/etc/bind/named.conf.options:
options {
directory "/var/cache/bind";
recursion no;
allow-query { any; };
auth-nxdomain no;
listen-on-v6 { any; };
listen-on { any; };
version "Not disclosed";
hostname "Not disclosed";
server-id "Not disclosed";
minimal-responses yes;
dnssec-validation auto;
rate-limit {
responses-per-second 10;
window 5;
};
};
Step 3: Define a zone
/etc/bind/named.conf.local:
zone "example.com" {
type primary;
file "/etc/bind/zones/db.example.com";
allow-transfer { 192.0.2.10; };
notify yes;
also-notify { 192.0.2.10; };
};
sudo mkdir -p /etc/bind/zones
Step 4: Zone file
/etc/bind/zones/db.example.com:
$TTL 3600
@ IN SOA ns1.example.com. admin.example.com. (
2026051701 ; Serial (YYYYMMDDNN)
3600 ; Refresh
1800 ; Retry
604800 ; Expire
86400 ) ; Minimum TTL
@ IN NS ns1.example.com.
@ IN NS ns2.example.com.
@ IN A 192.0.2.1
www IN A 192.0.2.1
ns1 IN A 192.0.2.1
ns2 IN A 192.0.2.10
mail IN A 192.0.2.20
@ IN AAAA 2001:db8::1
@ IN MX 10 mail.example.com.
@ IN TXT "v=spf1 ip4:192.0.2.20 -all"
_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:[email protected]"
ftp IN CNAME www.example.com.
blog IN CNAME www.example.com.
⚠️ Don't forget trailing dot on FQDNs or zone is appended.
Step 5: Verify zone
sudo named-checkzone example.com /etc/bind/zones/db.example.com
zone example.com/IN: loaded serial 2026051701
OK
Global check:
sudo named-checkconf
Step 6: Start and test
sudo systemctl restart bind9
sudo systemctl status bind9
dig @127.0.0.1 example.com
dig @127.0.0.1 mail.example.com
dig @127.0.0.1 example.com MX
dig @127.0.0.1 example.com SOA
External test (after delegating at registrar):
dig @192.0.2.1 example.com
Step 7: Secondary (slave)
On secondary, same install. named.conf.local:
zone "example.com" {
type secondary;
primaries { 192.0.2.1; };
file "/var/cache/bind/db.example.com";
allow-transfer { none; };
};
sudo systemctl restart bind9
sudo journalctl -u bind9 | grep transfer
Step 8: TSIG (transfer auth)
tsig-keygen -a hmac-sha256 transfer-key > /etc/bind/transfer.key
key "transfer-key" {
algorithm hmac-sha256;
secret "abc123...";
};
On primary, in named.conf:
include "/etc/bind/transfer.key";
zone "example.com" {
type primary;
file "/etc/bind/zones/db.example.com";
allow-transfer { key "transfer-key"; };
};
On secondary:
include "/etc/bind/transfer.key";
server 192.0.2.1 {
keys { "transfer-key"; };
};
Step 9: DNSSEC
sudo mkdir /etc/bind/keys
cd /etc/bind/keys
sudo dnssec-keygen -a ECDSAP256SHA256 -n ZONE example.com
sudo dnssec-keygen -a ECDSAP256SHA256 -n ZONE -f KSK example.com
In named.conf.local:
zone "example.com" {
type primary;
file "/etc/bind/zones/db.example.com";
dnssec-policy default;
inline-signing yes;
};
sudo systemctl restart bind9
Get DS record for registrar:
sudo dnssec-dsfromkey -a SHA-256 /etc/bind/keys/Kexample.com.+013+12345.key
Publish this DS record at your registrar.
Step 10: Views (split-horizon)
acl "internal" { 192.168.0.0/16; 10.0.0.0/8; };
view "internal" {
match-clients { internal; };
recursion yes;
zone "example.com" {
type primary;
file "/etc/bind/zones/db.example.com.internal";
};
};
view "external" {
match-clients { any; };
recursion no;
zone "example.com" {
type primary;
file "/etc/bind/zones/db.example.com";
};
};
Internal sees dev.example.com -> 192.168.1.10, external sees dev.example.com -> 51.x.x.x.
Step 11: Detailed logging
logging {
channel queries_log {
file "/var/log/named/queries.log" versions 5 size 10m;
severity info;
print-time yes;
print-category yes;
print-severity yes;
};
channel security_log {
file "/var/log/named/security.log" versions 5 size 10m;
severity info;
};
category queries { queries_log; };
category security { security_log; };
category dnssec { security_log; };
};
sudo mkdir /var/log/named
sudo chown bind:bind /var/log/named
sudo systemctl restart bind9
Step 12: RNDC (remote control)
sudo rndc-confgen -a
sudo systemctl restart bind9
sudo rndc status
sudo rndc reload
sudo rndc reload example.com
sudo rndc reconfig
sudo rndc dumpdb -cache
sudo rndc stats
sudo rndc flush
sudo rndc querylog on
Troubleshooting
Zone won't load
sudo named-checkzone example.com /etc/bind/zones/db.example.com
Often: serial not incremented, missing trailing dot, malformed SOA.
"Server failed: SERVFAIL"
DNSSEC signing issue. Check keys, regenerate if needed.
AXFR refused
Check allow-transfer and TSIG key.
dig @primary example.com AXFR -y transfer-key:abc123
No propagation
After change:
- Increment SOA serial
sudo rndc reload example.com- Check secondary transfer
- TTL: 86400 = up to 24h propagation
"permission denied" on zone file
sudo chown bind:bind /etc/bind/zones/db.example.com
sudo chmod 644 /etc/bind/zones/db.example.com
sudo chown bind:bind /etc/bind/zones
sudo chmod 755 /etc/bind/zones
Useful commands
sudo systemctl status bind9
sudo systemctl restart bind9
sudo named-checkconf
sudo named-checkzone example.com /etc/bind/zones/db.example.com
sudo rndc reload
sudo rndc reload example.com
dig @127.0.0.1 example.com
dig @ns1.example.com example.com SOA
dig +trace example.com
dig +dnssec example.com
sudo rndc stats
cat /var/cache/bind/named_stats.txt
sudo journalctl -u bind9 -f
sudo tail -f /var/log/named/queries.log
Conclusion
BIND9 gives you:
- Production-grade authoritative DNS
- Full DNSSEC
- Primary/secondary replication
- Split-horizon views
- Fine logging
Limits:
- Complex, verbose configuration
- CVE history, keep updated
- For pure recursion, Unbound is simpler
Going further:
- For simple prod, consider PowerDNS (web UI, more user-friendly)
- For DNS as a service, check NSD (auth only, simpler) or host on Cloudflare/AWS Route 53
- For DDNS, look at Knot DNS or nsupdate
Resources
- Official docs: https://bind9.readthedocs.io
- ISC: https://www.isc.org/bind/
- DNSSEC: https://datatracker.ietf.org/doc/html/rfc4033
- Best practices: https://bind9.readthedocs.io/en/latest/best-practices.html

















