Building My Own BIND9 Authoritative DNS Server



Setting up your own DNS server has always been one of those tasks that sounds way more complicated than it actually is. I recently decided to build a complete BIND9 authoritative DNS infrastructure from scratch, and honestly, it turned out to be one of those projects where you learn way more than you expected.

I'm going to walk you through exactly how I built this setup, complete with all the mistakes I made (so you don't have to!), the configurations that actually work, and the testing that proved everything was reliable.

Why I Decided to Build My Own DNS Server

Before jumping into the technical bits, let me explain why I went down this rabbit hole. As someone who's been working with networks for a while, I was tired of depending on external DNS providers for my internal infrastructure. Sure, Google's 8.8.8.8 and Cloudflare's 1.1.1.1 are great, but I wanted:

  • Complete control over my DNS resolution
  • Internal network management that just works
  • Privacy - no third parties logging my queries
  • Redundancy with primary and secondary servers
  • Learning experience - because DNS is fundamental to everything we do!

The Infrastructure I Built

Here's what I ended up with - a DNS setup that handles multiple VLANs and provides both internal and external resolution:

  • Primary DNS Server: 192.28.1.213 (dns1.example.com)
  • Secondary DNS Server: 192.24.1.214 (dns2.example.com)
  • Domain: example.com
  • VLANs: 192.28.1.0/24 and 192.24.1.0/24
  • Security features: Comprehensive logging, rate limiting, and information hiding

The best part? This setup gives me complete DNS resolution for both forward and reverse lookups across multiple network segments.

Step 1: Getting BIND9 Up and Running

Let's start with the basics. I'm using Ubuntu/Debian, so the installation was straightforward:



sudo apt update
sudo apt install bind9 bind9utils -y


You know what tripped me up for the longest time when I first started working with BIND9? The service isn't actually called "bind9" - it's called "named."

So whenever you're managing your DNS server, remember these commands:



systemctl status named
systemctl start named
systemctl enable --now named


Initial Configuration

Before implementing advanced DNS security features, it's crucial to establish proper foundational configurations that prevent common deployment issues

sudo vi /etc/default/named

Add these lines:



RESOLVCONF=no
OPTIONS="-u bind -4"  # If using IPv4 only


The RESOLVCONF=no basically tells BIND "hey, don't touch the system's DNS resolution - you're just the DNS server, not the client." And if you're not ready to deal with IPv6 yet, the -4 flag keeps everything IPv4-only.

Directory Structure and Permissions

The other thing that'll save you time is setting up proper directories from the start. BIND needs places to put logs and zone files and the default setup is... let's just say it's not ideal.

  1. /var/log/named gives you a dedicated spot for DNS logs (trust me, you'll need to read these when things go wrong)
  2. /etc/bind/zones keeps all your zone files organized in one place instead of scattered around
  3. The chown commands ensure the BIND process can actually write to these directories



sudo mkdir -p /var/log/named
sudo mkdir -p /etc/bind/zones
sudo chown bind:bind /var/log/named
sudo chown bind:bind /etc/bind/zones


Step 2: Configuring Security and Access Control

The first thing you need to understand is that DNS servers are magnets for abuse. Without proper access controls, your server becomes a target for DNS amplification attacks, unauthorized zone transfers, and general internet nastiness.

The Main Configuration File

Edit /etc/bind/named.conf.options and here's my complete configuration with detailed explanations:

// ACL Definitions - Define trusted networks and servers
acl "trusted-networks" {
    127.0.0.1;
    192.28.1.0/24; //VLAN 228
    192.24.1.0/24; //VLAN 224
};

acl "secondary-servers" {
    192.24.1.214;
};

// Comprehensive Logging Configuration
logging {
    // General logs (startup, shutdown, errors)
    channel general_log {
        file "/var/log/named/general.log" versions 3 size 5m;
        severity info;
        print-time yes;
        print-severity yes;
        print-category yes;
    };
    
    // Config parsing errors or warnings
    channel config_log {
        file "/var/log/named/config.log" versions 3 size 2m;
        severity warning;
        print-time yes;
        print-severity yes;
        print-category yes;
    };
    
    // Notifications (for master to notify slaves)
    channel notify_log {
        file "/var/log/named/notify.log" versions 3 size 5m;
        severity info;
        print-time yes;
        print-severity yes;
        print-category yes;
    };
    
    // Zone transfers
    channel transfer_log {
        file "/var/log/named/transfer.log" versions 3 size 5m;
        severity info;
        print-time yes;
        print-severity yes;
        print-category yes;
    };
    
    // Assign channels to categories
    category default        { general_log; };
    category general        { general_log; };
    category config         { config_log; };
    category notify         { notify_log; };
    category xfer-in        { transfer_log; };
    category xfer-out       { transfer_log; };
};

options {
    directory "/var/cache/bind";
    recursion no; // for authoritative server
    allow-query { trusted-networks; };
    listen-on { 127.0.0.1; 192.28.1.213; };
    allow-transfer { none; };  # Disable transfers by default
    
    // Security settings - hide server information
    version "Not Disclosed";
    hostname "Not Disclosed";
    server-id "Not Disclosed";
    
    listen-on-v6 { none; };  // disallow ipv6
    
    // Rate limiting to prevent DNS amplification attacks
    rate-limit {
        responses-per-second 10;
        window 5;
    };
};


Access Control Lists (ACLs) are your first line of defense. Instead of hardcoding IP addresses everywhere, I defined my trusted networks upfront. The trusted-networks ACL includes my local subnets - basically saying "these are the only networks I trust to make DNS queries."

The logging configuration might seem excessive, but trust me on this - when something goes wrong with DNS, you need detailed logs. I learned this after spending hours trying to figure out why zone transfers were failing, only to realize I had no logs to tell me what was happening. Each log type gets its own file, with automatic rotation so they don't fill up your disk.

The options section is where the real security happens:

  1. recursion no - This server only answers for zones it's authoritative for, it won't go chase down answers for random domains
  2. allow-query { trusted-networks; } - Only my trusted networks can even ask questions
  3. allow-transfer { none; } - By default, nobody gets zone transfers (I'll enable this selectively later with TSIG)
  4. The "Not Disclosed" settings hide server version information from attackers
  5. Rate limiting prevents DNS amplification attacks where attackers use your server to flood other targets

Before you save this configuration and restart BIND, there's one crucial step that'll save you from a world of pain:

named-checkconf /etc/bind/named.conf.options

BIND's configuration parser is incredibly picky, and even a missing semicolon will break everything. Any syntax errors get displayed clearly, so fix them before proceeding.

Step 3: Defining Your DNS Zones

Now I was about to tell BIND what domains it was responsible for and where to find the answers.

Defining the Zones

The zone definitions go in /etc/bind/named.conf.local, and this is where you essentially tell BIND "these are the domains I own, and here's where you'll find the data about them."

Edit /etc/bind/named.conf.local:

// Forward zones
zone "example.com" {
    type primary;
    file "/etc/bind/zones/db.example.com";
    allow-transfer { secondary-servers; };
};

zone "1.28.192.in-addr.arpa" {
    type primary;
    file "/etc/bind/zones/db.192.28";
    allow-transfer { secondary-servers; };
};

zone "1.24.192.in-addr.arpa" {
    type primary;
    file "/etc/bind/zones/db.192.24";
    allow-transfer { secondary-servers; };
};

What's happening here: I'm defining one forward zone for example.com  (the "normal" DNS that turns names into IP addresses) and two reverse zones for my IP ranges (the "backwards" DNS that turns IP addresses back into names). 

The allow-transfer directive ensures only my secondary server can copy these zones.

Step 4: Creating the Zone Files

This is where you define what domains resolve to which IP addresses. It's basically the phone book of your DNS server.

The Forward Zone File

Create /etc/bind/zones/db.example.com:

;
; BIND data file for example.com domain
;
$TTL    604800        ; Default TTL for all records 7 days
@   IN  SOA dns1.example.com. admin.example.com. (
     2025072108     ; Serial number - increment when making changes YYYYMMDDNN
         604800     ; Refresh interval - how often slaves check for updates  
          86400     ; Retry interval - retry failed transfers after this time
        2419200     ; Expire time - slaves stop answering after this period
         604800 )   ; Negative cache TTL - cache NXDOMAIN responses

;
; ===== NAME SERVER RECORDS =====
; Define authoritative DNS servers for this zone
@       IN      NS      dns1.example.com.
@       IN      NS      dns2.example.com.

; A record for the domain root (example.com)
@       IN      A       192.28.1.213

; A records for hosts
dns1    IN      A       192.28.1.213
dns2    IN      A       192.24.1.214
pc1     IN      A       192.24.1.221

Critical note about serial numbers: That serial number (2025072108) follows the format YYYYMMDDNN. You MUST increment this every time you make changes, or your secondary server won't know there are updates! I learned this the hard way after spending an hour wondering why my changes weren't propagating.

The Reverse Zone Files

For reverse DNS lookups, create /etc/bind/zones/db.192.28:

;
; BIND reverse data file for 192.28.1.x
;
$TTL    604800
@   IN  SOA dns1.example.com. admin.example.com. (
     2025072108     ; Serial  ;YYYYMMDDNN
         604800     ; Refresh
          86400     ; Retry
        2419200     ; Expire
         604800 )   ; Negative Cache TTL

; NS records for this zone
@    IN    NS    dns1.example.com.
@    IN    NS    dns2.example.com.

; PTR records
213    IN    PTR    dns1.example.com. ; 192.28.1.213

And create /etc/bind/zones/db.192.24:

;
; BIND reverse data file for 192.24.1.x
;
$TTL    604800
@   IN  SOA dns1.example.com. admin.example.com. (
     2025072108     ; Serial  ;YYYYMMDDNN
         604800     ; Refresh
          86400     ; Retry
        2419200     ; Expire
         604800 )   ; Negative Cache TTL

; NS records for this zone
@    IN    NS    dns1.example.com.
@    IN    NS    dns2.example.com.

; PTR records for 192.24.1.0/24
214    IN    PTR    dns2.example.com. ; 192.24.1.214
221    IN    PTR    pc1.example.com. ; 192.24.1.221

Validating Your Zone Files

Here's something I wish someone had emphasized more when I was learning: always validate your zone files before starting the service. BIND's zone file syntax is extremely picky, and a single typo can break everything.

# Check forward zone
named-checkzone example.com /etc/bind/zones/db.example.com

# Check reverse zones
named-checkzone 1.28.192.in-addr.arpa /etc/bind/zones/db.192.28
named-checkzone 1.24.192.in-addr.arpa /etc/bind/zones/db.192.24

If there are syntax errors, fix them before proceeding!

Step 5: Setting Up the Secondary DNS Server

I had a working primary DNS server, but anyone who's worked in IT knows that single points of failure are disasters waiting to happen. What if my primary server crashes? What if there's a network issue? My entire lab would lose DNS resolution.

Secondary Server Configuration

The best thing about secondary DNS servers is that they're much simpler to configure than the primary. They don't need zone files or complex configurations - they just need to know where to get their data from.

Use the same /etc/bind/named.conf.options as the primary, but change the listen-on directive to match the secondary server's IP.

listen-on { 127.0.0.1; 192.24.1.214; };

For /etc/bind/named.conf.local on the secondary server:

// Forward zones
zone "example.com" {
    type secondary;
    file "db.example.com";
    primaries { 192.28.1.213; };
};

zone "1.28.192.in-addr.arpa" {
    type secondary;
    file "db.192.28";
    primaries { 192.28.1.213; };
};

zone "1.24.192.in-addr.arpa" {
    type secondary;
    file "db.192.24";
    primaries { 192.28.1.213; };
};

What happens: The secondary server automatically downloads zone files from the primary and stores them in /var/cache/bind/. Pretty cool, right? I didn't have to manually copy zone files or maintain them separately.

Step 6: Configuring System DNS

You'll want your DNS servers to actually use themselves for DNS resolution. I went with the systemd-resolved approach.

Using systemd-resolved

sudo vi /etc/systemd/resolved.conf

Add in the [Resolve] section:

DNS=127.0.0.1

Then restart and verify:



systemctl restart systemd-resolved
systemd-resolve --status


Step 7:  Starting Everything Up

Time to fire everything up and see if it works:

systemctl restart bind9

If there are no errors in the logs, congratulations! Your DNS server should be running.

Testing: The Best Part!

This is where I got really excited - watching my DNS server actually resolve queries!

Forward DNS Resolution Tests

# Test against primary server
dig example.com @192.28.1.213
dig dns1.example.com @192.28.1.213
dig pc1.example.com @192.28.1.213

# Test against secondary server
dig example.com @192.28.1.214
dig dns2.example.com @192.24.1.214

Reverse DNS Resolution Tests

# Test against primary server
dig -x 192.28.1.213 @192.28.1.213
dig -x 192.24.1.221 @192.28.1.213

# Test against secondary server  
dig -x 192.24.1.214 @192.24.1.214

Security Configuration Test

Remember those security settings? Let's verify they're working:



dig @192.28.1.213 version.bind chaos txt
dig @192.28.1.213 id.server chaos txt
dig @192.28.1.213 hostname.bind chaos txt


All of these should return "Not Disclosed" - if they do, your security configuration is working perfectly!

Step 8: Monitoring and Maintenance

The logging setup I configured earlier generates incredibly useful information that becomes invaluable when things go wrong (and they will):

  1. General logs (Server startup, shutdown, general operations):   /var/log/named/general.log
  2. Configuration logs (Config parsing errors and warnings):     /var/log/named/config.log
  3. Notification logs (Primary-to-secondary notifications):         
  4. /var/log/named/notify.log
  5. Transfer logs (Zone transfer activity):                                   /var/log/named/transfer.log

Useful Management Commands



# Check BIND status
systemctl status bind9

# Reload configuration without restart
rndc reload

# View recent logs
tail -f /var/log/named/general.log

# Test zone transfers
dig @192.28.1.213 example.com AXFR


Lessons I Learned the Hard Way

Let me share some painful lessons so you don't have to experience them:

  1. Always increment the serial number when making changes to zone files. I spent 2 hours wondering why my secondary server wasn't  updating until I realized I forgot this! The secondary server uses the serial number to determine if zone data has changed.
  2. Restart the primary server first, then the secondary. The secondary needs to fetch updated data from the primary, so order matters.
  3. Use rndc reload instead of restarting the service for zone file changes. It's faster and doesn't interrupt service.
  4. Firewall rules matter! Make sure port 53 (both TCP and UDP) is open between your DNS servers.
  5. Test everything thoroughly before going to production. DNS issues can bring down your entire infrastructure.

Troubleshooting Common Issues

Problem: dig queries time out

Solution: Check if BIND is listening on the correct interface and verify firewall rules.

Problem: Zone transfers aren't working

Solution: Verify the secondary server is in the allow-transfer list and can reach the primary.

Problem: Changes aren't taking effect

Solution: Did you increment the serial number? BIND uses this to determine if zone data has changed.

Security Features That Actually Matter

The security configuration I implemented includes:

  • Hidden server information - External queries won't reveal BIND version or server details
  • Access control - Only trusted networks can query the server
  • Limited zone transfers - Only authorized secondary servers can request zone data
  • Rate limiting - Protection against DNS amplification attacks
  • Comprehensive logging - Complete audit trail of all DNS operations

The Final Result

After going through this entire process, I ended up with a robust DNS infrastructure that:

  • Handles both forward and reverse DNS resolution
  • Provides redundancy with automatic failover between primary and secondary servers
  • Includes comprehensive security features
  • Generates detailed logs for monitoring and troubleshooting
  • Scales easily for additional zones and servers

The best part? This setup gives me complete control over my DNS infrastructure while maintaining reliability and security.

Building your own DNS server might seem daunting at first, but once you understand the core concepts and have working configurations, it becomes incredibly empowering. You're no longer dependent on external providers, and you have complete visibility into how name resolution works in your environment.

DNS is one of those foundational technologies that just works silently in the background until it doesn't. Having the skills to build, configure, and troubleshoot your own DNS infrastructure is invaluable for any serious system administrator.

The monitoring and maintenance phase taught me that running DNS isn't just about configuration - it's about understanding the operational aspects that keep your infrastructure reliable over time. Those log files, management commands, and troubleshooting techniques became essential tools that turned my DNS server from a one-time project into a production-ready service.

Happy DNS-ing, and don't hesitate to experiment in your lab environment!


Have you built your own DNS server? What challenges did you face during the setup? Share your experiences in the comments below - I'd love to hear about your DNS adventures!

Post a Comment

Previous Post Next Post