Skip to content
Logo
Table of contents
VeryCloud
DocumentationVPS Linux

BIND9: authoritative DNS server

BIND9: authoritative DNS server

BIND is the historic and reference DNS server. Serve your own authoritative zones (your-domain.com and derivatives), manage DNSSEC, master/slave, views, AXFR transfers. Production-grade since 1984.

Category
VPS Linux
Level
Advanced
Reading time
5 min
Published
May 17, 2026

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

Join our Discord community server

For any questions, suggestions, or just to chat with the community, join us on Discord!

900+Members