Running your own DNS server gives you full control over your domain’s records, TTLs, and resolution behavior. This is useful if you’re a reseller managing many domains, need split-horizon DNS, or just want to stop depending on third-party DNS panels.

This guide uses BIND9, the most widely used DNS server on the internet.

Install BIND9

Check that it’s running:

  sudo systemctl status named       # Rocky/Alma
sudo systemctl status bind9       # Ubuntu/Debian

# Test with a query
dig @localhost version.bind txt chaos
  

How DNS Works (Quick Version)

When someone visits yourdomain.com:

  1. Their browser asks a recursive resolver (like 1.1.1.1 or 8.8.8.8) for the IP
  2. The resolver asks the .com TLD servers “who handles yourdomain.com?”
  3. The TLD server says “ask ns1.yourdomain.com” (your authoritative server)
  4. The resolver asks your BIND server for the A record
  5. Your server returns the IP, the resolver caches it, the browser connects

You’re setting up step 4 - the authoritative server that answers queries about your domain.

Configure BIND as an Authoritative Server

Main Configuration

recursion no; is critical - an open recursive DNS server will get abused for amplification attacks within hours.

Add Your Zone

Create a Zone File

This is where your actual DNS records live.

  $TTL 3600
@   IN  SOA ns1.yourdomain.com. admin.yourdomain.com. (
            2026041301  ; Serial (YYYYMMDDNN - increment on every change)
            3600        ; Refresh (1 hour)
            900         ; Retry (15 minutes)
            1209600     ; Expire (2 weeks)
            300         ; Negative cache TTL (5 minutes)
        )

; Nameservers
@       IN  NS      ns1.yourdomain.com.
@       IN  NS      ns2.yourdomain.com.

; Nameserver A records (glue records)
ns1     IN  A       203.0.113.10
ns2     IN  A       198.51.100.10

; Main domain
@       IN  A       203.0.113.10
@       IN  AAAA    2001:db8::1

; Subdomains
www     IN  A       203.0.113.10
www     IN  AAAA    2001:db8::1
mail    IN  A       203.0.113.10
staging IN  A       203.0.113.20
api     IN  A       203.0.113.30

; Mail
@       IN  MX  10  mail.yourdomain.com.

; SPF - allow only your mail server to send
@       IN  TXT     "v=spf1 mx ip4:203.0.113.10 -all"

; DKIM (paste your DKIM key here)
default._domainkey  IN  TXT  "v=DKIM1; k=rsa; p=YOUR_DKIM_PUBLIC_KEY"

; DMARC
_dmarc  IN  TXT     "v=DMARC1; p=quarantine; rua=mailto:admin@yourdomain.com"

; CAA - only Let's Encrypt can issue certs for this domain
@       IN  CAA 0 issue "letsencrypt.org"
  

Record Types Explained

TypePurposeExample
AMaps name to IPv4 address@ IN A 203.0.113.10
AAAAMaps name to IPv6 address@ IN AAAA 2001:db8::1
CNAMEAlias to another nameblog IN CNAME yourdomain.com.
MXMail server (with priority)@ IN MX 10 mail.yourdomain.com.
TXTText data (SPF, DKIM, verification)@ IN TXT "v=spf1 mx -all"
NSNameserver for this zone@ IN NS ns1.yourdomain.com.
SOAStart of Authority (zone metadata)Required, one per zone
CAAWhich CAs can issue SSL certs@ IN CAA 0 issue "letsencrypt.org"
SRVService discoveryUsed by some apps, VoIP, etc.

The Serial Number Matters

The SOA serial number tells secondary DNS servers whether the zone has changed. You must increment it every time you edit the zone file. The convention is YYYYMMDDNN where NN starts at 01 and increments for each change that day.

Check and Apply

  # Validate your zone file
named-checkzone yourdomain.com /etc/bind/zones/db.yourdomain.com    # Ubuntu/Debian
named-checkzone yourdomain.com /var/named/db.yourdomain.com          # Rocky/Alma

# Validate the main config
named-checkconf

# Reload BIND
sudo systemctl reload bind9     # Ubuntu/Debian
sudo systemctl reload named     # Rocky/Alma
  

If named-checkzone shows errors, fix them before reloading. A syntax error can take down DNS for all your domains.

Test Your DNS

  # Query your server directly
dig @localhost yourdomain.com A
dig @localhost yourdomain.com MX
dig @localhost yourdomain.com TXT

# From another machine (replace with your server's IP)
dig @203.0.113.10 yourdomain.com A

# Check all record types
dig yourdomain.com ANY @localhost
  

Set Up a Secondary (Slave) DNS

You need at least two nameservers for most domain registrars. Set up a secondary on a different server:

On the secondary server, install BIND and add:

  zone "yourdomain.com" {
    type slave;
    file "/var/cache/bind/db.yourdomain.com";
    masters { 203.0.113.10; };  // primary DNS IP
};
  

On the primary server, update allow-transfer in the zone config:

  allow-transfer { 198.51.100.10; };  // secondary DNS IP
  

Reload both:

  sudo systemctl reload bind9    # or named
  

The secondary pulls zone data from the primary automatically and stays in sync based on the SOA refresh interval.

Reverse DNS (PTR Records)

Reverse DNS maps an IP back to a hostname. Mail servers check this - if your PTR record doesn’t match, your emails may get flagged as spam.

If you do manage your own IP block, here’s the reverse zone:

  sudo nano /etc/bind/zones/db.113.0.203
  
  $TTL 3600
@   IN  SOA ns1.yourdomain.com. admin.yourdomain.com. (
            2026041301
            3600
            900
            1209600
            300
        )

@   IN  NS  ns1.yourdomain.com.
@   IN  NS  ns2.yourdomain.com.

10  IN  PTR yourdomain.com.
10  IN  PTR mail.yourdomain.com.
  

Open the Firewall

DNS uses port 53 on both TCP and UDP:

  # UFW
sudo ufw allow 53/tcp
sudo ufw allow 53/udp

# firewalld
sudo firewall-cmd --permanent --add-service=dns
sudo firewall-cmd --reload
  

Troubleshooting

ProblemFix
named-checkzone shows “file not found”Check the file path in your zone declaration. Ubuntu uses /etc/bind/zones/, Rocky uses /var/named/.
BIND won’t start - “permission denied”On Rocky/AlmaLinux, SELinux may block access. Check: sudo ausearch -m avc -ts recent. Fix: sudo restorecon -Rv /var/named/.
Zone transfers failCheck allow-transfer on primary and masters on secondary. Firewall must allow port 53.
Changes don’t propagateDid you increment the serial number? Secondary servers only pull updates when the serial increases.
dig works locally but not remotelyFirewall is blocking port 53. Check with sudo ufw status or sudo firewall-cmd --list-all.
“SERVFAIL” responsesSyntax error in zone file. Run named-checkzone to find it.
High CPU from BINDYou may have recursion yes and the internet is hammering you with queries. Set recursion no for authoritative-only servers.

Last updated 21 Apr 2026, 08:08 +0300. history

Was this page helpful?