BIND9 Deployment | Advanced Linux Administration

Slide 1 of 34  |  ALA-W3-BIND  |  Week 3 of 8
BIND9 Deployment
Install, Configure, Operate
named.conf  •  Zone Files  •  Forward/Reverse Zones  •  AXFR  •  rndc
The sector node needs a local authoritative DNS server. External DNS cannot be trusted for internal resolution. You are standing up BIND9 from a clean Ubuntu 22.04 install. Every command in this module maps to a real deployment decision.
34 Slides ALA-W3-BIND Week 3 of 8 Ubuntu 22.04 LTS
Slide 2 of 34
BIND9: What It Is
Berkeley Internet Name Domain version 9. The reference implementation of the DNS protocol.
named :53 UDP/TCP Zone Files db.matrix.internal db.10.0.1 Cache /var/cache/bind Forwarders 8.8.8.8 1.1.1.1
What BIND9 Can Do
Run as an authoritative server (holds zone data), a recursive resolver (walks the hierarchy for clients), or both simultaneously. Supports split-horizon views, DNSSEC signing, dynamic DNS updates, access control lists, and response rate limiting.
Key Processes
named is the DNS daemon. rndc is the remote name daemon control tool -- it connects to named's control channel to issue commands without restarting the service. Both are in the same package.
Alternatives
NSD (authoritative only, minimal footprint), Knot DNS (authoritative, fast), Unbound (recursive resolver only, security-focused), PowerDNS (database-backed, API-driven). BIND9 is the most widely deployed and the best choice for learning the full DNS stack.
Why BIND9 Specifically
BIND9 implements every RFC-defined DNS feature. Understanding it gives you the conceptual foundation to operate any DNS server. Its configuration syntax is the reference that all documentation and tutorials use. Learn BIND9 first.
Slide 3 of 34
BIND9 Installation
Clean install on Ubuntu 22.04. Three packages, one disable, one verify.
# Update package index first sudo apt update # Install BIND9 and utilities sudo apt install -y bind9 bind9utils bind9-doc # bind9 -- the named daemon and startup scripts # bind9utils -- rndc, named-checkconf, named-checkzone, nsupdate # bind9-doc -- manpages and documentation # Disable systemd-resolved stub listener (conflicts with BIND on port 53) sudo sed -i 's/#DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.conf sudo systemctl restart systemd-resolved # Verify named is running sudo systemctl status named sudo ss -tulnp | grep ':53' # named should own port 53 # Verify named starts on boot sudo systemctl enable named
AppArmor
Ubuntu ships with AppArmor profiles for named. The profile restricts what files named can read and write. If named cannot read your zone files, check the AppArmor profile at /etc/apparmor.d/usr.sbin.named. Zone files in /etc/bind/ are permitted by default.
Slide 4 of 34
BIND9 File Layout
Know every file and directory before touching any of them.
/etc/bind/ named.conf* db.* zone files rndc.key /var/cache/bind/ journals (.jnl) secondary zones named_stats.txt /var/run/named/ named.pid control socket /var/log/ syslog named/
# Primary configuration directory /etc/bind/ named.conf # main config -- usually just includes others named.conf.options # global options: forwarders, recursion, ACLs named.conf.local # your zones go here named.conf.default-zones # root hints, localhost zones (do not edit) db.local # zone file for localhost (reference example) db.127 # reverse zone file for 127.0.0.1 db.root # root hints -- list of root server addresses rndc.key # authentication key for rndc commands # Runtime directory (writable by named user) /var/cache/bind/ # journal files, secondary zone data /var/run/named/ # PID file and control socket # Logging /var/log/syslog # BIND9 logs here by default (named category) # Process user grep bind /etc/passwd # bind:x:117:122::/var/cache/bind:/usr/sbin/nologin
Never Put Zone Files in /tmp or /home
AppArmor restricts named to specific directories. Your zone files must live in /etc/bind/ or /var/cache/bind/. Files placed elsewhere will cause silent load failures.
Slide 5 of 34
named.conf.options: Global Settings
The options block controls recursion, forwarders, ACLs, and security posture.
// /etc/bind/named.conf.options // Define trusted networks that may recurse acl "trusted" { 127.0.0.1; 10.0.0.0/8; 192.168.0.0/16; }; options { directory "/var/cache/bind"; // working directory for named recursion yes; allow-recursion { trusted; }; // only trusted clients may recurse allow-query { trusted; }; // only trusted clients may query allow-transfer { none; }; // deny all zone transfers by default // Forwarders: upstream resolvers for names we don't own forwarders { 8.8.8.8; 1.1.1.1; }; forward only; // don't recurse independently -- use forwarders dnssec-validation auto; // validate DNSSEC responses listen-on { any; }; // listen on all interfaces listen-on-v6 { any; }; };
Slide 6 of 34
named.conf.local: Zone Declarations
This is where you declare your zones. The zone type and file path are the two critical fields.
// /etc/bind/named.conf.local // Forward zone -- name to IP zone "matrix.internal" { type master; // this server is authoritative file "/etc/bind/db.matrix.internal"; // zone file path allow-transfer { 10.0.1.11; }; // allow ns2 to transfer notify yes; // notify ns2 on zone change also-notify { 10.0.1.11; }; }; // Reverse zone for 10.0.1.0/24 zone "1.0.10.in-addr.arpa" { type master; file "/etc/bind/db.10.0.1"; allow-transfer { 10.0.1.11; }; notify yes; }; // Secondary zone -- pulls from primary via AXFR zone "partner.example" { type slave; masters { 10.0.2.10; }; // primary server IP file "/var/cache/bind/db.partner.example"; // stored in cache dir };
Slide 7 of 34
Zone File Structure
The anatomy of a forward zone file. Every field has a specific meaning and strict syntax.
$TTL 3600 SOA Record serial / refresh retry / expire / min NS ns1, ns2 Resource Records A AAAA CNAME MX TXT SRV
; /etc/bind/db.matrix.internal ; Semicolons begin comments in zone files $TTL 3600 ; default TTL for all records without explicit TTL $ORIGIN matrix.internal. ; appended to unqualified names (optional -- set by zone name) ; SOA record -- required, must be first @ IN SOA ns1.matrix.internal. admin.matrix.internal. ( 2026040901 ; serial 3600 ; refresh (secondary polls primary every 1 hour) 900 ; retry (on refresh failure, retry after 15 min) 604800 ; expire (secondary stops answering after 7 days without refresh) 300 ; minimum TTL (negative cache TTL) ) ; NS records -- required, define authoritative name servers @ IN NS ns1.matrix.internal. @ IN NS ns2.matrix.internal. ; A records ns1 IN A 10.0.1.10 ns2 IN A 10.0.1.11 ops IN A 10.0.1.50 db1 IN A 10.0.1.60 ; MX record @ IN MX 10 mail.matrix.internal. mail IN A 10.0.1.70
Slide 8 of 34
Reverse Zone File
PTR records that map IP addresses back to hostnames. The format is different from forward zones.
; /etc/bind/db.10.0.1 ; Reverse zone for 10.0.1.0/24 ; Zone name: 1.0.10.in-addr.arpa. $TTL 3600 @ IN SOA ns1.matrix.internal. admin.matrix.internal. ( 2026040901 3600 900 604800 300 ) @ IN NS ns1.matrix.internal. @ IN NS ns2.matrix.internal. ; PTR records: only the last octet (zone provides the rest) 10 IN PTR ns1.matrix.internal. 11 IN PTR ns2.matrix.internal. 50 IN PTR ops.matrix.internal. 60 IN PTR db1.matrix.internal. 70 IN PTR mail.matrix.internal. ; Test reverse lookup ; dig -x 10.0.1.50 @127.0.0.1 --> ops.matrix.internal.
Zone Name Derivation
For network 10.0.1.0/24, reverse the network octets: 1.0.10 then append .in-addr.arpa. Full zone name: 1.0.10.in-addr.arpa.. For 192.168.5.0/24 the zone name is 5.168.192.in-addr.arpa..
Slide 9 of 34
Validating: named-checkconf and named-checkzone
Never reload BIND without running both tools first. Syntax errors cause zone load failures.
# Validate the main configuration file sudo named-checkconf # No output = no errors. Any error is fatal -- do not proceed. # Validate a specific zone file sudo named-checkzone matrix.internal /etc/bind/db.matrix.internal # Expected good output: # zone matrix.internal/IN: loaded serial 2026040901 # OK # Validate reverse zone sudo named-checkzone 1.0.10.in-addr.arpa /etc/bind/db.10.0.1 # Common errors and what they mean # no SOA RR found -- SOA record is missing or has a syntax error # missing '.' -- FQDN missing trailing dot # out of zone data -- a name resolves outside the zone (missing trailing dot) # CNAME and other data -- a CNAME record coexists with another record type # Validate ALL zones defined in named.conf at once sudo named-checkconf -z
The Workflow
Edit zone file. Increment serial. Run named-checkconf -z. Fix any errors. Reload. This sequence is non-negotiable. Skipping validation and reloading a broken zone causes BIND to stop serving that zone with no warning to clients.
Slide 10 of 34
Loading Changes: rndc reload
How to apply configuration and zone changes without dropping connections or restarting the daemon.
# Reload all configuration and all zones sudo rndc reload # server reload successful # Reload a specific zone only (faster for large deployments) sudo rndc reload matrix.internal # Reload just the configuration file (without zone files) sudo rndc reconfig # Alternative: systemctl reload (sends SIGHUP -- same effect as rndc reload) sudo systemctl reload named # Full restart (only needed after changing listening addresses or port) sudo systemctl restart named # Verify the zone loaded successfully after reload sudo rndc status # shows loaded zones count and version dig SOA matrix.internal @127.0.0.1 # Check serial number in the SOA matches the file
rndc vs systemctl
Use rndc for DNS-specific operations -- it communicates through named's control channel and returns accurate status. Use systemctl only for full service start/stop/restart. Never kill named with SIGKILL -- it may corrupt journal files.
Slide 11 of 34
rndc: Command Reference
The full set of operational commands for a running BIND9 server.
# Status and diagnostics rndc status # server uptime, version, loaded zones, memory rndc zonestatus matrix.internal # detailed status for one zone # Zone management rndc reload # reload all zones and config rndc reload ZONE # reload specific zone rndc reconfig # re-read named.conf without zone reload rndc refresh ZONE # force secondary to re-check primary (on secondary) rndc retransfer ZONE # force full AXFR from primary (on secondary) rndc freeze ZONE # suspend dynamic updates (safe for manual editing) rndc thaw ZONE # resume dynamic updates after freeze # Cache management rndc flush # clear the entire resolver cache rndc flushname NAME # remove specific name from cache rndc dumpdb # dump cache to /var/cache/bind/named_dump.db # Logging rndc querylog on # enable per-query logging (verbose -- use briefly) rndc querylog off # disable query logging rndc trace # increase debug logging level rndc notrace # return to normal logging level
Slide 12 of 34
Testing: dig Against Your Server
After every configuration change, verify with dig before declaring success.
# Always query your server directly -- use @127.0.0.1 not @127.0.0.53 # 127.0.0.53 = systemd-resolved stub (may cache old data) # 127.0.0.1 = BIND9 directly (no caching between you and the zone) # Test forward lookup dig A ops.matrix.internal @127.0.0.1 # Expect: NOERROR, aa flag (authoritative answer), correct IP # Test reverse lookup dig -x 10.0.1.50 @127.0.0.1 # Expect: ops.matrix.internal. # Test SOA -- verify serial matches your zone file dig SOA matrix.internal @127.0.0.1 +multiline # Test NXDOMAIN behavior dig A ghost.matrix.internal @127.0.0.1 # Expect: NXDOMAIN, aa flag, authority section shows SOA # Test recursive forwarding (should resolve external names) dig A google.com @127.0.0.1 # Expect: NOERROR, no aa flag (not authoritative for google.com)
Slide 13 of 34
Zone Transfers: Primary to Secondary
Configure ns1 (primary) and ns2 (secondary) for automatic zone synchronization.
ns1 (primary) 10.0.1.10 type master NOTIFY (serial changed) AXFR (full) / IXFR (incremental) ns2 (secondary) 10.0.1.11 type slave
// ns1 (primary) -- named.conf.local zone "matrix.internal" { type master; file "/etc/bind/db.matrix.internal"; allow-transfer { 10.0.1.11; }; // only allow ns2 to transfer notify yes; // NOTIFY ns2 when serial changes also-notify { 10.0.1.11; }; // must list explicitly if not in NS records }; // ns2 (secondary) -- named.conf.local zone "matrix.internal" { type slave; masters { 10.0.1.10; }; // ns1 IP file "/var/cache/bind/db.matrix.internal"; }; # Verify zone transfer occurred on ns2 dig SOA matrix.internal @10.0.1.11 # serial should match ns1 ls -la /var/cache/bind/db.matrix.internal # file should exist and be recent # Force a retransfer on ns2 sudo rndc retransfer matrix.internal # run this on ns2 # Watch the transfer in logs sudo journalctl -u named -f | grep 'transfer'
Slide 14 of 34
BIND9 Logging
Configure structured logging to separate query logs, security events, and general errors.
// Add a logging block to named.conf.options logging { channel default_log { file "/var/log/named/named.log" versions 3 size 20m; print-time yes; print-severity yes; print-category yes; severity dynamic; // controlled by rndc trace }; channel query_log { file "/var/log/named/queries.log" versions 5 size 50m; print-time yes; severity info; }; category queries { query_log; }; category default { default_log; }; category security { default_log; }; category config { default_log; }; category xfer-in { default_log; }; category xfer-out { default_log; }; }; # Create log directory (named must own it) sudo mkdir -p /var/log/named sudo chown bind:bind /var/log/named sudo rndc reconfig
Slide 15 of 34
Access Control: ACLs
Named ACLs let you define client groups once and reference them throughout the configuration.
// Named ACLs -- define at top of named.conf.options before options block acl "internal-nets" { 127.0.0.0/8; 10.0.0.0/8; 172.16.0.0/12; 192.168.0.0/16; }; acl "ns2-only" { 10.0.1.11; // only ns2 may transfer zones }; options { allow-query { internal-nets; }; allow-recursion { internal-nets; }; allow-transfer { ns2-only; }; // Blackhole: silently discard queries from these IPs (rate limiters, scanners) blackhole { 203.0.113.99; }; }; // Per-zone transfer override (overrides global allow-transfer) zone "matrix.internal" { type master; file "/etc/bind/db.matrix.internal"; allow-transfer { ns2-only; }; // explicit -- belt and suspenders };
Slide 16 of 34
Dynamic DNS: nsupdate
Update zone records without editing files or reloading -- critical for DHCP and automated environments.
# nsupdate sends dynamic update packets to a zone's primary server # The zone must have allow-update configured in named.conf // In named.conf.local -- allow updates from localhost (for testing) zone "matrix.internal" { type master; file "/etc/bind/db.matrix.internal"; allow-update { 127.0.0.1; }; // ONLY for testing -- use TSIG in prod }; # Interactive nsupdate session nsupdate > server 127.0.0.1 > zone matrix.internal > update add temp.matrix.internal 300 A 10.0.1.99 > send > quit # Verify the update dig A temp.matrix.internal @127.0.0.1 # Delete a record via nsupdate nsupdate <<EOF server 127.0.0.1 zone matrix.internal update delete temp.matrix.internal A send EOF
Slide 17 of 34
TSIG: Transaction Signatures
Shared-secret authentication for zone transfers and dynamic updates. Required in production.
# Generate a TSIG key sudo tsig-keygen -a hmac-sha256 matrix-transfer-key | sudo tee /etc/bind/tsig-keys.conf # Output added to tsig-keys.conf: # key "matrix-transfer-key" { # algorithm hmac-sha256; # secret "BASE64SECRET=="; # }; # Include the key file in named.conf // named.conf include "/etc/bind/tsig-keys.conf"; // Use TSIG for zone transfer authentication zone "matrix.internal" { type master; file "/etc/bind/db.matrix.internal"; allow-transfer { key "matrix-transfer-key"; }; }; # On ns2 (secondary), reference the same key in masters block zone "matrix.internal" { type slave; masters { 10.0.1.10 key "matrix-transfer-key"; }; file "/var/cache/bind/db.matrix.internal"; };
Slide 18 of 34
Forwarders vs Full Recursion
Two strategies for resolving external names. Each has different performance and privacy tradeoffs.
Client query Receive ACL check Cache hit / miss miss Forward (upstream) Recurse (root) Response to client
Forward-Only Mode
Your BIND9 server forwards all external queries to upstream resolvers (e.g., 8.8.8.8). Simple, low overhead, relies on upstream availability. All query data is visible to the upstream provider. Best for most internal deployments.
Full Recursive Mode
BIND9 walks the DNS hierarchy itself starting from root servers. No dependency on a third-party resolver. Better privacy (query data stays local). Slightly higher latency on first lookup. Required if you need DNSSEC validation without trusting an upstream.
// Forward-only: delegate all external resolution to upstream options { forwarders { 8.8.8.8; 1.1.1.1; }; forward only; // do not fall back to recursion if forwarder fails }; // Full recursive: walk the tree yourself options { forwarders { }; // empty -- no forwarders recursion yes; dnssec-validation auto; }; // Hybrid: forward most queries but recurse for specific zones zone "partner.internal" { type forward; forwarders { 10.0.2.10; }; // forward only this zone to partner DNS };
Slide 19 of 34
Response Rate Limiting: RRL
Prevent your authoritative server from being used as an amplifier in DNS-based DDoS attacks.
Attacker spoofs src IP 60-byte query BIND9 Server RRL: 10 resp/sec 3000-byte response 50x amplification Victim Server overwhelmed RRL THROTTLES RESPONSES
// Add rate-limit block inside options { } options { rate-limit { responses-per-second 10; // max responses per client IP per second window 5; // sliding window in seconds slip 2; // 1-in-N responses get TC flag (triggers TCP retry) log-only no; // actually enforce (yes = observe only) exempt-clients { 10.0.0.0/8; }; // trusted networks are exempt }; }; # Monitor RRL in action sudo journalctl -u named | grep 'rate-limit' # Throttled responses are logged with client IP and query type # Test RRL (flood queries and see drops) for i in $(seq 1 20); do dig +short A ops.matrix.internal @127.0.0.1; done
DNS Amplification Basics
An attacker sends small queries with a spoofed source IP (the victim's IP). Your server sends large responses to the victim. A single query packet can result in a response 50-100x larger. RRL limits the response rate per source IP, dramatically reducing amplification potential.
Slide 20 of 34
Troubleshooting BIND9
Systematic diagnosis when named is not behaving as expected.
# Is named running and what port is it on? sudo systemctl status named sudo ss -tulnp | grep named # What does named think of its current state? sudo rndc status # Check logs for errors (last 50 lines) sudo journalctl -u named -n 50 # Does the zone load correctly? sudo named-checkconf -z 2>&1 | grep -i error # Is named listening on the right interface? sudo ss -unap sport = :53 # UDP port 53 sudo ss -tnap sport = :53 # TCP port 53 # Enable debug logging briefly sudo rndc trace 3 # level 3 is very verbose dig A ops.matrix.internal @127.0.0.1 sudo journalctl -u named -n 100 sudo rndc notrace # return to normal -- do not leave trace on # AppArmor blocking file access? sudo journalctl -k | grep 'apparmor.*named'
Slide 21 of 34
Firewall Rules for DNS
UFW rules to allow DNS queries and zone transfers while blocking everything else.
# DNS queries arrive on UDP/53 and TCP/53 # Zone transfers (AXFR) use TCP/53 # Large responses (>512 bytes) fall back to TCP/53 automatically # Allow DNS queries from internal network sudo ufw allow from 10.0.0.0/8 to any port 53 proto udp sudo ufw allow from 10.0.0.0/8 to any port 53 proto tcp # Allow rndc from localhost only sudo ufw allow from 127.0.0.1 to any port 953 proto tcp # Verify firewall status sudo ufw status numbered # Test from a remote host dig A ops.matrix.internal @10.0.1.10 # from another machine on 10.0.0.0/8 # Test TCP fallback explicitly dig +tcp A ops.matrix.internal @10.0.1.10 # If AXFR is needed from ns2: sudo ufw allow from 10.0.1.11 to any port 53 proto tcp # on ns1
Slide 22 of 34
Zone File Best Practices
Conventions that prevent the most common errors in zone file management.
Use $TTL at the Top
Always set a global default TTL with the $TTL directive as the very first line. Without it, BIND uses the SOA minimum, which may be too low for production records. Override per-record when needed.
Trailing Dots on FQDNs
Any hostname that is not relative to the zone must end with a dot. mail.partner.com. with the dot is an absolute name. mail.partner.com without the dot becomes mail.partner.com.matrix.internal. -- a common misconfiguration.
Increment Serial Every Edit
Use the YYYYMMDDnn format. One increment per deployment. If you make multiple changes in one session, increment once at the end. Never decrement a serial -- secondaries ignore zones with lower serials than their cached copy.
Validate Before Every Reload
Running named-checkzone takes under a second. Loading a broken zone takes your authoritative server offline for that domain. The check is not optional, it is the minimum acceptable standard.
Version Control Your Zone Files
Track zone files in git. Every change has an audit trail, a diff, and a way to revert. Pair with a pre-commit hook that runs named-checkzone automatically.
Slide 23 of 34
Deployment Checklist
Step-by-step verification sequence after bringing up a new BIND9 server.
1Package installed and named running: systemctl status named shows active (running).
2Port 53 owned by named: ss -tulnp | grep named shows 0.0.0.0:53 or specific IP:53.
3Config validates: named-checkconf -z returns zero errors.
4Forward zone resolves: dig A ops.matrix.internal @127.0.0.1 returns correct IP with aa flag.
5Reverse zone resolves: dig -x 10.0.1.50 @127.0.0.1 returns correct hostname.
6External resolution works: dig A google.com @127.0.0.1 returns answer (forwarders work).
7Zone transfer to secondary: serial on ns2 matches ns1 (dig SOA zone @ns2 vs @ns1).
8Firewall allows clients: test from a host on the trusted network, not just localhost.
9AXFR blocked from outside: dig AXFR matrix.internal @ns1-public-ip from internet returns REFUSED.
Slide 24 of 34
Views: Split-Brain DNS
Serve different zone data to internal clients vs. the internet from a single BIND9 instance.
Internal 10.0.0.0/8 External any (public) BIND9 match-clients view "internal" web.hexworth.com -> 10.0.1.50 view "external" web.hexworth.com -> 203.0.113.50
// named.conf -- views replace the global zone declarations // All zones must be inside view blocks when views are used view "internal" { match-clients { 10.0.0.0/8; 127.0.0.0/8; }; recursion yes; zone "matrix.internal" { type master; file "/etc/bind/internal/db.matrix.internal"; }; zone "hexworth.com" { type master; file "/etc/bind/internal/db.hexworth.com"; // internal IPs }; include "/etc/bind/named.conf.default-zones"; }; view "external" { match-clients { any; }; recursion no; zone "hexworth.com" { type master; file "/etc/bind/external/db.hexworth.com"; // public IPs only }; };
Slide 25 of 34
named.conf: Include Structure
The default Ubuntu layout uses includes to keep configuration modular. Understand it before changing it.
named.conf .conf.options ACLs + forwarders .conf.local your zone blocks .conf.default-zones root hints + localhost
// /etc/bind/named.conf (the root file -- rarely edited directly) include "/etc/bind/named.conf.options"; // global options, forwarders, ACLs include "/etc/bind/named.conf.local"; // your zone declarations include "/etc/bind/named.conf.default-zones"; // localhost, root hints // /etc/bind/named.conf.default-zones (do not modify) // Contains: root hints zone, localhost forward/reverse, broadcast zones zone "." { // root hints -- how to find root servers type hint; file "/usr/share/dns/root.hints"; }; // You only need to edit two files: // named.conf.options -- global settings // named.conf.local -- your zones # Verify the include chain is valid sudo named-checkconf /etc/bind/named.conf # This recursively validates all included files
Root Hints
The root hints file contains the IP addresses of the 13 root server clusters. When BIND9 is configured for full recursion (not forward-only), it reads these to know where to start. The hints file is updated occasionally -- but root server IPs rarely change in practice.
Slide 26 of 34
Zone File: Safe Editing Workflow
The exact sequence for making zone changes safely in production.
#!/bin/bash # zone-update.sh -- safe zone update workflow ZONE="matrix.internal" ZONE_FILE="/etc/bind/db.matrix.internal" # Step 1: Freeze the zone (prevents journal conflicts with dynamic updates) sudo rndc freeze $ZONE # Step 2: Back up current zone file sudo cp $ZONE_FILE ${ZONE_FILE}.bak.$(date +%Y%m%d%H%M%S) # Step 3: Edit the zone file sudo nano $ZONE_FILE # increment serial, make changes # Step 4: Validate sudo named-checkzone $ZONE $ZONE_FILE if [ $? -ne 0 ]; then echo "Zone file invalid -- restoring backup" sudo cp ${ZONE_FILE}.bak.* $ZONE_FILE sudo rndc thaw $ZONE exit 1 fi # Step 5: Thaw and reload sudo rndc thaw $ZONE sudo rndc reload $ZONE # Step 6: Verify dig SOA $ZONE @127.0.0.1 +short
Slide 27 of 34
Secondary Name Server: Full Setup
Complete configuration for ns2 as a slave server that auto-syncs from ns1.
# On ns2 (10.0.1.11): install BIND9 sudo apt install -y bind9 bind9utils // /etc/bind/named.conf.options on ns2 options { directory "/var/cache/bind"; recursion yes; allow-recursion { 10.0.0.0/8; 127.0.0.0/8; }; allow-transfer { none; }; // ns2 is not a transfer source forwarders { 8.8.8.8; 1.1.1.1; }; forward only; }; // /etc/bind/named.conf.local on ns2 zone "matrix.internal" { type slave; masters { 10.0.1.10; }; file "/var/cache/bind/db.matrix.internal"; }; zone "1.0.10.in-addr.arpa" { type slave; masters { 10.0.1.10; }; file "/var/cache/bind/db.10.0.1"; }; # Start named on ns2 -- zone transfer happens automatically sudo systemctl enable --now named sudo journalctl -u named -f # watch transfer in real time
Slide 28 of 34
Monitoring BIND9 Health
Metrics and log patterns to watch in production. Know what normal looks like before something breaks.
# rndc stats -- dumps detailed counters to /var/cache/bind/named_stats.txt sudo rndc stats cat /var/cache/bind/named_stats.txt | head -60 # Key metrics to watch: # queries received -- total incoming query count # queries answered -- successful responses # NXDOMAIN responses -- high rate = misconfigured clients or enumeration # SERVFAIL responses -- high rate = upstream or zone problems # REFUSED responses -- potential unauthorized query sources # Watch for SERVFAIL spike (forwarder down) sudo journalctl -u named --since "10 min ago" | grep -i servfail # Watch for zone transfer failures sudo journalctl -u named | grep 'transfer.*failed' # Check zone loaded times sudo rndc zonestatus matrix.internal # Memory usage of named ps aux | grep named sudo rndc status | grep -i memory
Slide 29 of 34
Common BIND9 Errors Decoded
Log messages you will encounter and exactly what they mean.
E1zone matrix.internal/IN: not loaded due to errors -- zone file has a syntax error. Run named-checkzone to identify the line.
E2could not listen on UDP socket: address already in use -- another process owns port 53. Check ss -tulnp | grep :53. Often systemd-resolved stub listener.
E3no valid RRSIG -- DNSSEC validation failure. The zone has DNSSEC but signatures are expired or the chain is broken.
E4Transfer failed. REFUSED -- the primary's allow-transfer does not include your secondary IP. Add it to named.conf.local on the primary.
E5connection refused on rndc -- rndc cannot reach named's control socket. Check that named is running and rndc.key is correct on both sides.
E6permission denied on zone file -- AppArmor or file ownership issue. Zone files must be owned or readable by the bind user and in an allowed directory.
Slide 30 of 34  |  Applied Scenario
From Zero: Complete BIND9 Setup
The complete command sequence. Copy-paste ready, verified for Ubuntu 22.04.
# Phase 1: Install sudo apt update && sudo apt install -y bind9 bind9utils sudo sed -i 's/#DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.conf sudo systemctl restart systemd-resolved # Phase 2: Configure options sudo tee /etc/bind/named.conf.options <<'EOF' acl "trusted" { 127.0.0.0/8; 10.0.0.0/8; }; options { directory "/var/cache/bind"; recursion yes; allow-recursion { trusted; }; allow-query { trusted; }; allow-transfer { none; }; forwarders { 8.8.8.8; 1.1.1.1; }; forward only; dnssec-validation auto; }; EOF # Phase 3: Validate and start sudo named-checkconf sudo systemctl enable --now named sudo ss -tulnp | grep named
Slide 31 of 34
Verifying the Full Delegation Chain
Use dig +trace to confirm that delegation from registrar through your zone is correct.
. (root) KSK + ZSK trust anchor DS .com KSK signs DS for child zones DS hexworth.com DNSKEY + RRSIG signs zone data VALIDATED ad flag set
# +trace shows every step from root through your authoritative server dig +trace A ops.hexworth.com # Expected output sequence: # 1. Queries a root server: gets NS records for .com # . 518400 IN NS a.root-servers.net. # 2. Queries a .com TLD server: gets NS records for hexworth.com # hexworth.com. 172800 IN NS ns1.hexworth.com. # 3. Queries ns1.hexworth.com: gets the A record # ops.hexworth.com. 300 IN A 10.0.1.50 # If delegation is broken, the trace shows exactly where it stops # Common break points: # - .com TLD still shows old NS records (registrar not updated) # - ns1.hexworth.com returns REFUSED (firewall or allow-query) # - ns1.hexworth.com not reachable (glue record wrong IP) # Compare what registrar thinks vs what your server thinks dig NS hexworth.com @a.gtld-servers.net # authoritative TLD view dig NS hexworth.com @ns1.hexworth.com # your server's view
Slide 32 of 34
BIND9 Security Hardening
Minimum viable hardening for a production BIND9 deployment.
chroot /var/bind AppArmor profile named /etc/bind/* /var/cache/bind/* /home/* /tmp/* ACL Filters allow-query allow-recursion allow-transfer
// named.conf.options -- security settings options { // Prevent version disclosure to anonymous queries version "not disclosed"; hostname "not disclosed"; // Limit recursion to known networks allow-recursion { 10.0.0.0/8; 127.0.0.0/8; }; // Block all zone transfers by default allow-transfer { none; }; // Block queries from known bad actors blackhole { badguys; }; // Response rate limiting to prevent amplification DDoS rate-limit { responses-per-second 10; }; // Validate DNSSEC dnssec-validation auto; // Disable CHAOS class queries (version info leakage) allow-query-cache { 10.0.0.0/8; 127.0.0.0/8; }; }; # Verify version is hidden dig CHAOS TXT version.bind @127.0.0.1 # should return "not disclosed"
Slide 33 of 34
BIND9 Operational Quick Reference
The commands you will run every time you touch a BIND9 server. In order of frequency.
# Validate then reload (the standard change workflow) sudo named-checkconf -z && sudo rndc reload ZONE # Verify zone loaded correctly dig SOA ZONE @127.0.0.1 +short # Check server status sudo rndc status # Reload only config (no zone reload) sudo rndc reconfig # Flush cache sudo rndc flush # Check zone transfer status on secondary sudo rndc zonestatus ZONE # Force zone refresh on secondary sudo rndc retransfer ZONE # Enable query logging for debugging sudo rndc querylog on sudo journalctl -u named -f sudo rndc querylog off # always turn off when done # Test TCP fallback dig +tcp A NAME @127.0.0.1 # Attempt zone transfer (test allow-transfer) dig AXFR ZONE @127.0.0.1 # should return REFUSED from localhost if configured
Slide 34 of 34  |  Module Summary
BIND9 Deployment: Key Takeaways
What you must be able to execute without reference after this module.
1Install: apt install bind9 bind9utils. Disable systemd-resolved stub listener. Verify named owns port 53.
2named.conf structure: root file includes options, local, and default-zones. Edit only named.conf.options and named.conf.local.
3Zone files: $TTL first, SOA second, NS records, then A/CNAME/MX records. Serial is YYYYMMDDnn -- increment on every change.
4Reverse zones: name is the network written backwards plus .in-addr.arpa.. PTR records use only the last octet.
5Change workflow: edit file, increment serial, named-checkconf -z, rndc reload ZONE, verify with dig SOA @127.0.0.1.
6Security defaults: allow-transfer { none; } globally, restrict recursion to trusted networks, hide version string, enable RRL.
7Secondaries: type slave with masters { PRIMARY-IP; }. Zone files stored in /var/cache/bind/. Monitor with rndc zonestatus.