How to Configure BIND9 DNS Server, Forwarding Zones, and DNSSEC Validation on Ubuntu Server

Architectural Overview of BIND9 and DNSSEC Cryptographic Validation

The Berkeley Internet Name Domain (BIND) Version 9 is the industry-standard software suite implementing the Domain Name System (DNS) protocol across enterprise networks and global internet infrastructure. Operating primarily via the named daemon, BIND9 on Ubuntu Server provides multiple functional roles: recursive caching resolver, authoritative nameserver for private and public zones, and cryptographic validator enforcing DNS Security Extensions (DNSSEC).

Modern BIND (versions 9.18 and later) includes native auto-tuning, an asynchronous network runtime, and built-in DNSSEC validation using managed trust anchors. Deploying BIND9 on Ubuntu requires re-engineering Ubuntu’s local stub resolver (systemd-resolved) to liberate network socket binding on UDP and TCP port 53, while adhering to AppArmor security profiles that govern filesystem read/write access.

BIND9 Functional Role Network Scope Authoritative Data Recursion Behaviour Security Exposure
Caching Recursive Resolver Internal Trusted LAN None (Stores root/TLD answers) Enabled for authorized subnets Must restrict access to prevent DNS amplification
Forwarding Resolver Internal DMZ / Edge None (Relays queries upstream) Forwards to Cloudflare / Quad9 Minimises external zone traffic and edge latency
Authoritative Master Server Internal / Public Primary forward and reverse zones Strictly disabled (Non-recursive) Serves zone transfers (AXFR) to secondary slaves
Authoritative Slave Server Secondary Edge Replicated zone copies Disabled Cryptographically authenticated via TSIG keys

Resolving Port 53 Conflicts with systemd-resolved

Ubuntu Server enables systemd-resolved by default, binding a local stub resolver to 127.0.0.53:53. Before installing BIND9, administrators must adjust this binding to allow named full ownership of loopback and network interfaces.

# Disable the systemd-resolved stub listener in /etc/systemd/resolved.conf
sudo sed -i 's/#DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.conf

# Restart systemd-resolved to release socket bindings on port 53
sudo systemctl restart systemd-resolved

# Re-link /etc/resolv.conf to the static resolver file
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf

# Verify port 53 is completely liberated
sudo ss -tulpn | grep :53

Installing BIND9 and Configuring Core Options

Install BIND9 along with its standard administrative and diagnostic utility packages:

sudo apt-get update
sudo apt-get install -y bind9 bind9utils bind9-doc dnsutils

The BIND configuration hierarchy divides responsibilities across modular files in /etc/bind/:

  • named.conf: Root configuration file importing subordinate modules.
  • named.conf.options: Global operational parameters, listen interfaces, ACLs, forwarding rules, and DNSSEC toggles.
  • named.conf.local: Local zone declarations for authoritative forward and reverse namespaces.

Configuring /etc/bind/named.conf.options

Define an Access Control List (ACL) to restrict recursive lookups exclusively to trusted internal subnets, configure external forwarding resolvers, and mandate automated DNSSEC validation:

// Define trusted internal clients
acl "trusted_lan" {
    127.0.0.1;
    ::1;
    192.168.1.0/24;
    10.0.10.0/24;
};

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

    // Listen on IPv4 and IPv6 loopback and LAN interfaces
    listen-on port 53 { 127.0.0.1; 192.168.1.10; };
    listen-on-v6 port 53 { ::1; };

    // Authorise recursion strictly for trusted internal clients
    allow-query { trusted_lan; };
    allow-recursion { trusted_lan; };
    allow-query-cache { trusted_lan; };

    // Hide BIND version metadata from fingerprinting probes
    version "Unavailable";

    // Upstream forwarding resolvers (Quad9 and Cloudflare DNS)
    forwarders {
        9.9.9.9;
        1.1.1.1;
    };
    forward only;

    // Enforce DNSSEC cryptographic signature verification
    dnssec-validation auto;

    // Conformance and rate limiting
    auth-nxdomain no;
    max-cache-size 256M;
};

Configuring Authoritative Forward and Reverse Zones

To establish an internal authoritative namespace (e.g., corp.internal), register the zone within /etc/bind/named.conf.local:

// Forward lookup zone for corporate infrastructure
zone "corp.internal" {
    type master;
    file "/etc/bind/zones/db.corp.internal";
    allow-transfer { none; }; // Restrict zone transfers
};

// Reverse lookup zone for 192.168.1.0/24 subnet
zone "1.168.192.in-addr.arpa" {
    type master;
    file "/etc/bind/zones/db.192.168.1";
    allow-transfer { none; };
};

Step 1: Compose the Forward Zone File

Create the dedicated zone catalogue and populate the resource records for corp.internal:

sudo mkdir -p /etc/bind/zones
sudo cat << 'EOF' > /etc/bind/zones/db.corp.internal
$TTL    86400
@       IN      SOA     ns1.corp.internal. admin.corp.internal. (
                        2026102301      ; Serial (YYYYMMDDNN)
                        10800           ; Refresh (3 hours)
                        3600            ; Retry (1 hour)
                        604800          ; Expire (1 week)
                        86400 )         ; Minimum Negative Cache TTL (1 day)

; Authoritative Name Servers
@       IN      NS      ns1.corp.internal.

; Infrastructure A Records
ns1     IN      A       192.168.1.10
gateway IN      A       192.168.1.1
srv01   IN      A       192.168.1.50
db01    IN      A       192.168.1.60

; Canonical Name Aliases
ldap    IN      CNAME   srv01.corp.internal.
auth    IN      CNAME   srv01.corp.internal.
EOF

Step 2: Compose the Reverse Pointer Zone File

Construct the corresponding PTR reverse mapping records under /etc/bind/zones/db.192.168.1:

sudo cat << 'EOF' > /etc/bind/zones/db.192.168.1
$TTL    86400
@       IN      SOA     ns1.corp.internal. admin.corp.internal. (
                        2026102301      ; Serial
                        10800           ; Refresh
                        3600            ; Retry
                        604800          ; Expire
                        86400 )         ; Negative TTL

; Authoritative Name Server
@       IN      NS      ns1.corp.internal.

; Reverse Pointer (PTR) Records
10      IN      PTR     ns1.corp.internal.
1       IN      PTR     gateway.corp.internal.
50      IN      PTR     srv01.corp.internal.
60      IN      PTR     db01.corp.internal.
EOF

Validating Configuration Files and Testing Resolution

Never restart the named service without verifying syntax compliance. An undetected semicolon omission can collapse DNS resolution across the infrastructure.

# 1. Validate master configuration syntax
sudo named-checkconf

# 2. Check individual zone files for semantic and SOA errors
sudo named-checkzone corp.internal /etc/bind/zones/db.corp.internal
sudo named-checkzone 1.168.192.in-addr.arpa /etc/bind/zones/db.192.168.1

# 3. Restart and enable BIND9 systemd daemon
sudo systemctl restart bind9
sudo systemctl enable bind9

# 4. Verify service status
sudo systemctl status bind9

Step 3: Verification via dig and DNSSEC Validation Checks

Audit both internal authoritative responses and external DNSSEC cryptographic integrity:

# Query local authoritative zone
dig @127.0.0.1 srv01.corp.internal +short

# Query reverse pointer record
dig @127.0.0.1 -x 192.168.1.50 +short

# Verify DNSSEC validation by querying a known signed domain (look for 'ad' flag)
dig @127.0.0.1 cloudflare.com +dnssec | grep -E "flags:|RRSIG"

# Verify that intentionally invalid DNSSEC signatures return SERVFAIL (dnssec-failed.org)
dig @127.0.0.1 dnssec-failed.org

When querying an intentionally broken DNSSEC zone, BIND9 must return status: SERVFAIL, demonstrating that cryptographic signature verification is actively shielding clients from cache poisoning.

Troubleshooting Common BIND9 and AppArmor Faults

Observed Symptom Probable Root Cause Diagnostic Verification Remediation Protocol
status: SERVFAIL for valid external domains System clock skew preventing DNSSEC RRSIG cryptographic validation Run timedatectl status; compare system time against NTP Synchronise server clock via Chrony or systemd-timesyncd
named: permission denied when reading zone files Ubuntu AppArmor profile or file ownership preventing daemon access Inspect kernel logs: dmesg | grep -i apparmor Execute sudo chown -R bind:bind /etc/bind/zones/; verify 644 permissions
bind: address already in use on port 53 systemd-resolved or dnsmasq actively holding port 53 Execute sudo ss -lntup 'sport = :53' Disable DNSStubListener in /etc/systemd/resolved.conf and restart service
zone corp.internal/IN: serial number not increased Zone file edited without incrementing SOA serial integer Compare serial number in zone file with loaded memory version Increment serial (e.g., 2026102302) and execute sudo rndc reload
Clients receive Query Refused error Client IP subnet not defined in trusted_lan ACL Review BIND query log or inspect client source IP Add client CIDR range to acl "trusted_lan" in named.conf.options

Get the best tech tips delivered straight to your inbox.

Join thousands of readers mastering Apple, Google, Microsoft, and Linux.