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.
/var/log/named
gives you a dedicated spot for DNS logs (trust me, you'll need to read these when things go wrong)/etc/bind/zones
keeps all your zone files organized in one place instead of scattered around- 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:
- recursion no - This server only answers for zones it's authoritative for, it won't go chase down answers for random domains
- allow-query { trusted-networks; } - Only my trusted networks can even ask questions
- allow-transfer { none; } - By default, nobody gets zone transfers (I'll enable this selectively later with TSIG)
- The "Not Disclosed" settings hide server version information from attackers
- 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
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):
- General logs (Server startup, shutdown, general operations):
/var/log/named/general.log
- Configuration logs (Config parsing errors and warnings): /var/log/named/config.log
- Notification logs (Primary-to-secondary notifications):
/var/log/named/notify.log
- 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:
- 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.
- Restart the primary server first, then the secondary. The secondary needs to fetch updated data from the primary, so order matters.
- Use
rndc reload
instead of restarting the service for zone file changes. It's faster and doesn't interrupt service. - Firewall rules matter! Make sure port 53 (both TCP and UDP) is open between your DNS servers.
- 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!