Setting up BIND9 for Public DNS on RHEL9

Setting up BIND9 for Public DNS on RHEL9 #

This guide covers setting up BIND9/named as an authoritative public DNS server on RHEL9. Unlike internal DNS servers that provide recursive resolution, public authoritative DNS servers are responsible for answering queries about domains you own and control.

This setup includes security hardening measures such as proper logging and zone transfer restrictions to protect your DNS infrastructure from abuse.

All IP addresses, domain names, and server configurations in this guide are examples. Replace them with your actual values.

For the entirety of the guide we’ll be running every single command as root.

All the commands are intended only for RPM and dnf/yum based systems such as Red Hat, Fedora, CentOS etc.

Domain Registrar Configuration #

The first thing you need to do is configure your two DNS servers as glue records with your domain registrar:

  • ns01.example.com (203.0.113.10)
  • ns02.example.com (203.0.113.20)

Installation #

Installing BIND9 #

# Install BIND9 and utilities
dnf install bind bind-utils

# Start and enable the service
systemctl start named
systemctl enable named

# Check the service status
systemctl status named

Firewall Configuration #

Open the necessary ports for DNS services:

# Allow DNS traffic on both UDP and TCP
firewall-cmd --permanent --add-port=53/udp
firewall-cmd --permanent --add-port=53/tcp

# Reload firewall rules
firewall-cmd --reload

# Verify the configuration
firewall-cmd --list-all

Network Planning #

For this public DNS setup, I’m using:

  • Primary DNS server: 203.0.113.10 (ns01.example.com)
  • Secondary DNS server: 203.0.113.20 (ns02.example.com)
  • Example domains:
    • example.com

Main Configuration File #

The main BIND9 configuration file is /etc/named.conf. Here’s a complete public DNS configuration with security hardening:

//
// BIND9 Public DNS Configuration
// Authoritative-only DNS server with security hardening
//

options {
    // Listen on specific interfaces - replace with your server's IP
    listen-on port 53 { 127.0.0.1; 203.0.113.10; };
    listen-on-v6 port 53 { ::1; };

    // Directory for zone files
    directory "/var/named";

    // Log and statistics files
    dump-file "/var/named/data/cache_dump.db";
    statistics-file "/var/named/data/named_stats.txt";
    memstatistics-file "/var/named/data/named_mem_stats.txt";
    secroots-file "/var/named/data/named.secroots";
    recursing-file "/var/named/data/named.recursing";

    // Allow queries from anyone
    allow-query { any; };

    // CRITICAL: Disable recursion for public authoritative servers
    recursion no;

    // Security settings
    version "not currently available";  // Hide BIND version
    allow-transfer { none; };           // Disable zone transfers by default

    // Enable query logging for monitoring
    querylog yes;

    // Disable DNSSEC (enable if you plan to sign your zones)
    dnssec-enable no;
    dnssec-validation no;

    // Process and key file locations
    managed-keys-directory "/var/named/dynamic";
    pid-file "/run/named/named.pid";
    session-keyfile "/run/named/session.key";

    // Include system crypto policies
    include "/etc/crypto-policies/back-ends/bind.config";
};

// Comprehensive logging configuration
logging {
    channel default_debug {
        file "data/named.run";
        severity dynamic;
    };

    // Zone transfer logging
    channel zone_transfer_log {
        file "data/transfer.log" versions 10 size 50m;
        print-time yes;
        print-category yes;
        print-severity yes;
        severity info;
    };

    // Blocked queries logging
    channel security_log {
        file "data/security.log" versions 10 size 50m;
        severity info;
        print-time yes;
        print-category yes;
        print-severity yes;
    };

    // Query logging
    channel query_log {
        file "data/query.log" versions 10 size 100m;
        severity info;
        print-time yes;
        print-category yes;
        print-severity yes;
    };

    // Category mappings
    category notify { zone_transfer_log; };
    category xfer-in { zone_transfer_log; };
    category xfer-out { zone_transfer_log; };
    category security { security_log; };
    category queries { query_log; };
};

// Root hints
zone "." IN {
    type hint;
    file "named.ca";
};

// Include standard zones
include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";

// Your domain zones
zone "example.com" {
    type master;
    file "/var/named/example.com";
    allow-transfer { 203.0.113.20; };  // Secondary DNS server
};

Zone Files #

Primary Domain Zone (example.com) #

Create /var/named/example.com:

$ORIGIN example.com.
$TTL 300
@         IN  SOA  ns01.example.com.  admin.example.com. (
              2025070301  ; serial (YYYYMMDDNN format)
              21600       ; refresh after 6 hours
              3600        ; retry after 1 hour
              1209600     ; expire after 2 weeks
              86400 )     ; minimum TTL of 1 day

; Name servers
          IN  NS     ns01.example.com.
          IN  NS     ns02.example.com.

; A records
www       IN  A      203.0.113.100
mail      IN  A      203.0.113.101
ftp       IN  A      203.0.113.102
ns01      IN  A      203.0.113.10
ns02      IN  A      203.0.113.20

; CNAME records for services
webmail   IN  CNAME  mail.example.com.
blog      IN  CNAME  www.example.com.
shop      IN  CNAME  www.example.com.
api       IN  CNAME  www.example.com.
cdn       IN  CNAME  www.example.com.
support   IN  CNAME  www.example.com.
docs      IN  CNAME  www.example.com.
admin     IN  CNAME  www.example.com.
portal    IN  CNAME  www.example.com.
status    IN  CNAME  www.example.com.

; Mail exchange records
@         IN  MX     10  mail.example.com.
@         IN  MX     20  backup-mail.example.com.

; TXT records
@         IN  TXT    "v=spf1 include:_spf.example.com ~all"
_dmarc    IN  TXT    "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com"
mail._domainkey IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC..."
@         IN  TXT    "example domain for documentation"

Configuration Validation #

Syntax Validation #

Always validate your configuration before restarting the service:

# Check named.conf syntax
named-checkconf /etc/named.conf

# Check individual zone files
named-checkzone example.com /var/named/example.com

Restart Service #

# Restart BIND9
systemctl restart named

# Check service status
systemctl status named

# View recent logs
journalctl -u named -f

DNS Testing #

Test your DNS configuration:

# Test local resolution
dig @127.0.0.1 example.com NS
dig @127.0.0.1 ns01.example.com A

# Test from external resolver
dig @8.8.8.8 example.com NS
dig @8.8.8.8 ns01.example.com A

# Test specific records
dig @127.0.0.1 www.example.com A
dig @127.0.0.1 example.com MX
dig @127.0.0.1 example.com TXT

Secondary DNS Server Setup #

For redundancy, configure a secondary DNS server:

Note: Complete the installation and firewall configuration steps on the secondary server (203.0.113.20) before proceeding.

On Secondary Server #

# Add zone configurations to named.conf
zone "example.com" {
    type slave;
    file "/var/named/slaves/example.com";
    masters { 203.0.113.10; };
};

Create Slaves Directory #

# Create directory for slave zone files
mkdir -p /var/named/slaves
chown named:named /var/named/slaves
chmod 755 /var/named/slaves

Client Configuration #

Configure your clients to use your DNS server:

Linux #

Edit /etc/resolv.conf:

nameserver 203.0.113.10
nameserver 203.0.113.20
search example.com

Troubleshooting Common Issues #

DNS Not Resolving #

# Check if BIND9 is running
systemctl status named

# Check listening ports
netstat -tulnp | grep :53

# Test local resolution
dig @127.0.0.1 example.com NS

# Check firewall
firewall-cmd --list-ports