Back to tutorials
Tutorial

BIND9 DNS Slave Setup Guide Tutorial (2026): Secondary DNS for Faster Failover on a VPS

BIND9 DNS slave setup guide tutorial (2026) for secondary DNS on a VPS: AXFR/TSIG, firewall, testing, and failover checks.

By Anurag Singh
Updated on Aug 12, 2026
Category: Tutorial
Share article
BIND9 DNS Slave Setup Guide Tutorial (2026): Secondary DNS for Faster Failover on a VPS

Secondary DNS is the kind of upgrade you don’t fully appreciate until something breaks. When your primary DNS VPS disappears, your sites and email don’t “mostly work.” They often stop resolving.

This BIND9 DNS slave setup guide tutorial shows how to run a clean primary/secondary pair on two Linux VPS servers. You’ll use TSIG-signed zone transfers, sensible firewall rules, and a repeatable set of checks to run after every change.

This setup matches real hosting operations. You can use it for client domains, reseller DNS, or your own infrastructure records (A/AAAA, MX, SPF, DKIM, DMARC).

If you’re deploying this alongside hosting services, start with a VPS you control end to end. A HostMyCode VPS works well for authoritative DNS because you manage the OS, the network, and the patch cadence.

What you’ll build (and why it matters)

You’ll deploy:

  • ns1 (primary/master): authoritative DNS that holds the writable zone files.
  • ns2 (secondary/slave): read-only authoritative DNS that pulls zones from ns1.

Both servers answer queries for your domains. You only edit ns1. ns2 stays in sync via zone transfers (AXFR/IXFR).

If ns1 becomes unreachable, ns2 continues answering queries. That’s the goal: fewer outages that turn into incidents.

Assumptions in this tutorial (adjust as needed):

  • OS: Ubuntu Server 24.04 LTS or Debian 12 (both common for DNS VPS in 2026).
  • BIND9 package from distro repos (stable and security-supported).
  • Two public IPv4 addresses (IPv6 strongly recommended too, but optional).
  • You’re hosting authoritative DNS (not a recursive resolver for end users).

Prerequisites checklist (do this before touching BIND)

  • Two VPS nodes in different locations/providers if possible (reduces correlated failures).
  • Hostnames set: ns1.example.net and ns2.example.net (or your naming scheme).
  • Time sync enabled (systemd-timesyncd or chrony). DNS signatures and logs are cleaner with correct time.
  • Firewall plan: allow UDP/TCP 53 to the world; allow TCP 53 zone transfers only between ns1 and ns2.

If the servers aren’t hardened yet, do that first. Use your normal baseline: SSH keys, minimal packages, unattended upgrades.

This pairs well with our Ubuntu hardening walkthrough: server hardening for hosting VPS.

Step 1: Install BIND9 on both servers

Run on ns1 and ns2:

sudo apt update
sudo apt install -y bind9 bind9-utils dnsutils
sudo systemctl enable --now bind9

Confirm the service is running:

sudo systemctl status bind9 --no-pager

On Ubuntu/Debian, BIND’s main config directory is:

  • /etc/bind/ for configs
  • /var/cache/bind/ for slave zone files (by default)
  • Logs usually go to journald unless you configure a file channel

Step 2: Lock down the firewall for authoritative DNS + transfers

Authoritative DNS must accept queries from anywhere on port 53 (UDP and TCP). Zone transfers should be limited to ns2 only. Transfers use TCP 53.

Example with UFW on both servers:

# Allow DNS queries from the world
sudo ufw allow 53/udp
sudo ufw allow 53/tcp

You’ll restrict zone transfers inside BIND with allow-transfer plus TSIG (coming up next). You can also add a network-layer restriction. For example, limit TCP/53 to ns2 while leaving UDP/53 open to everyone.

Don’t block TCP/53 entirely. Large answers, DNSSEC, and some resolver behavior depend on it.

If you run into firewall edge cases (SSH lockouts, Let’s Encrypt renewals, mail ports), this guide helps: UFW firewall troubleshooting (2026).

Step 3: Configure ns1 (master) for authoritative zones

On ns1, edit /etc/bind/named.conf.options. The goal is authoritative-only DNS. That means no recursion for random clients.

sudo nano /etc/bind/named.conf.options

Use something like this (keep it straightforward):

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

    // Authoritative DNS: do not provide recursion to the public Internet
    recursion no;
    allow-recursion { none; };

    // Listen on all interfaces (adjust if you bind to specific IPs)
    listen-on { any; };
    listen-on-v6 { any; };

    // Basic hardening
    version "";

    // If you have upstream resolvers for internal lookups, define them,
    // but for authoritative-only recursion is disabled.
};

Validate syntax:

sudo named-checkconf

Reload BIND:

sudo systemctl reload bind9

Step 4: Create a TSIG key for secure zone transfers

TSIG keeps “anything that can reach TCP/53” from requesting your full zone. Even if your records aren’t sensitive, AXFR makes enumeration trivial.

On ns1, generate a key:

sudo tsig-keygen -a hmac-sha256 ns1-ns2-xfr | sudo tee /etc/bind/keys/ns1-ns2-xfr.key > /dev/null
sudo chown -R root:bind /etc/bind/keys
sudo chmod 750 /etc/bind/keys
sudo chmod 640 /etc/bind/keys/ns1-ns2-xfr.key

Create the directory if needed:

sudo install -d -m 750 -o root -g bind /etc/bind/keys

Now copy /etc/bind/keys/ns1-ns2-xfr.key to ns2 securely (use scp over SSH):

scp /etc/bind/keys/ns1-ns2-xfr.key root@NS2_IP:/etc/bind/keys/

On ns2, set the same permissions:

sudo chown -R root:bind /etc/bind/keys
sudo chmod 750 /etc/bind/keys
sudo chmod 640 /etc/bind/keys/ns1-ns2-xfr.key

Step 5: Define the zone on ns1 (master) and allow transfers only to ns2

We’ll use example.com as the zone. On ns1, edit /etc/bind/named.conf.local:

sudo nano /etc/bind/named.conf.local

Add:

include "/etc/bind/keys/ns1-ns2-xfr.key";

zone "example.com" {
    type master;
    file "/etc/bind/zones/db.example.com";

    // Only allow ns2 to transfer this zone, and only with TSIG
    allow-transfer { key ns1-ns2-xfr; };

    // Optional: allow ns2 to notify and pull changes fast
    also-notify { NS2_IP; };
    notify yes;
};

Create the zone directory and zone file:

sudo install -d -m 755 /etc/bind/zones
sudo nano /etc/bind/zones/db.example.com

Example minimal zone file (replace IPs/hostnames):

$TTL 300
@   IN  SOA ns1.example.net. hostmaster.example.com. (
        2026081201 ; serial (YYYYMMDDNN)
        3600       ; refresh
        900        ; retry
        1209600    ; expire
        300 )      ; minimum

    IN  NS  ns1.example.net.
    IN  NS  ns2.example.net.

; Authoritative nameserver addresses
ns1 IN  A   NS1_IP
ns2 IN  A   NS2_IP

; Web
@   IN  A   203.0.113.10
www IN  A   203.0.113.10

; Mail (example)
@   IN  MX  10 mail.example.com.
mail IN  A   203.0.113.20

Sanity-check the zone:

sudo named-checkzone example.com /etc/bind/zones/db.example.com
sudo named-checkconf

Reload BIND on ns1:

sudo systemctl reload bind9

Step 6: Configure ns2 (slave) to pull the zone from ns1

On ns2, edit /etc/bind/named.conf.local:

sudo nano /etc/bind/named.conf.local

Add:

include "/etc/bind/keys/ns1-ns2-xfr.key";

zone "example.com" {
    type slave;
    file "/var/cache/bind/db.example.com";

    masters {
        NS1_IP key ns1-ns2-xfr;
    };
};

Check config and reload:

sudo named-checkconf
sudo systemctl reload bind9

Within a few seconds, ns2 should pull the zone and write it to /var/cache/bind/db.example.com. Verify:

sudo ls -l /var/cache/bind/ | grep example.com

Step 7: Test queries against both nameservers (authoritative answers)

From your laptop or any third host, query each server directly:

dig @NS1_IP example.com SOA +noall +answer
dig @NS2_IP example.com SOA +noall +answer

You should see the same SOA serial on both. Then check an A record:

dig @NS1_IP www.example.com A +noall +answer
dig @NS2_IP www.example.com A +noall +answer

Confirm the response is authoritative by checking the flags line. You want aa:

dig @NS1_IP www.example.com A

Step 8: Prove zone transfers are restricted (and TSIG is working)

From a random machine that is not ns2, try an AXFR from ns1. It should fail:

dig @NS1_IP example.com AXFR

Now test with the TSIG key from ns2 (run on ns2):

dig @NS1_IP example.com AXFR -y ns1-ns2-xfr:$(awk '/secret/ {gsub(/"|;|secret/,"",$0); gsub(/ /,"",$0); print $0}' /etc/bind/keys/ns1-ns2-xfr.key)

If that one-liner is too opaque, skip it. Use rndc status checks and rely on BIND logs to confirm transfers.

What matters operationally: only ns2 can transfer, and only with TSIG.

Step 9: Add glue + nameserver records at your registrar (and avoid a common trap)

If your nameservers live inside the domain they serve (for example, ns1.example.com for example.com), you must set glue records at the registrar. Without glue, resolvers can’t reach ns1/ns2. They would need DNS for example.com first.

  1. At your domain registrar, create child nameservers:
    • ns1.example.com → NS1_IP
    • ns2.example.com → NS2_IP
  2. Set the domain’s authoritative nameservers to those two hosts.
  3. Wait for TLD propagation (often minutes, sometimes longer).

If you’re moving DNS from an old provider, treat it like a migration, not a flip. Lower TTLs ahead of time. Keep MX records intact. Verify results at multiple resolvers.

Follow this workflow: DNS migration without downtime or email breakage.

Step 10: Validate failover behavior (the test people skip)

Failover isn’t instantaneous. It’s also not fully under your control.

Recursive resolvers cache aggressively. They also don’t all pick the same NS in the same order. Still, you can run a simple test that catches obvious gaps.

  1. From a third system, resolve through a public resolver and record the NS set:
    dig example.com NS +noall +answer
    
  2. Stop BIND on ns1:
    sudo systemctl stop bind9
    
  3. Query ns2 directly (should still answer):
    dig @NS2_IP www.example.com A +noall +answer
    
  4. Optionally query via a recursive resolver you control and watch which NS it chooses over time.
  5. Start BIND again on ns1:
    sudo systemctl start bind9
    

Keep TTLs modest (e.g., 300–900 seconds) for records you may need to change during an incident. Avoid setting everything to 60 seconds “just in case.” It increases query volume and makes troubleshooting louder.

Operational workflow: updating records without breaking transfers

On ns1, every zone change needs a serial bump. If you forget, ns2 won’t pull the update.

Pick a serial format you can read quickly during an incident:

  • YYYYMMDDNN (recommended): 2026081201, then 2026081202 for the second change that day.

After editing the zone on ns1:

sudo named-checkzone example.com /etc/bind/zones/db.example.com
sudo systemctl reload bind9

Then confirm ns2 is current:

dig @NS2_IP example.com SOA +noall +answer

Quick diagnostics: the 6 failures you’ll actually see

  • ns2 never pulls the zone: check TCP/53 reachability from ns2 to ns1 and verify TSIG key name matches exactly.
  • “not authoritative” answers: you’re querying a recursive resolver, not your nameserver. Use dig @NS1_IP directly and look for the aa flag.
  • Zone loads on ns1 fail: run named-checkzone; look for missing dots on FQDNs (e.g., mail.example.com. needs the trailing dot in zone files).
  • Transfers denied: confirm include "/etc/bind/keys/ns1-ns2-xfr.key"; exists on both sides and permissions allow the bind user/group to read it.
  • Registrar glue misconfigured: dig +trace example.com NS helps you see where resolution breaks.
  • Propagation confusion: compare authoritative vs cached views. This pairs well with DNS propagation troubleshooting.

Hardening ideas that stay practical for hosting

You don’t need an overbuilt security project to run DNS safely. A few small choices reduce risk without adding much operational overhead:

  • Keep recursion off on authoritative nodes (already done). It reduces abuse potential.
  • Rate-limit abusive query patterns at the network edge if you see floods (many providers can help).
  • Patch predictably: DNS is critical infrastructure. Don’t let it drift.
  • Log enough to debug: if you’re diagnosing transfers, enable a dedicated query/transfer channel temporarily, then disable it again.

If DNS runs on the same VPS as web hosting, plan to split the roles later. Authoritative DNS is lightweight, but combining services increases your blast radius.

Many shops start with one box. They move DNS to a small, separate VPS once client count grows.

Where HostMyCode fits in this setup

Authoritative DNS benefits from predictable networking and straightforward reverse DNS control, especially if you host mail.

For PTR and reverse DNS workflows, see: PTR record setup to fix email rejections.

If you want secondary DNS you control (and not a shared DNS setup with surprise limits), run ns1 and ns2 on two small VPS nodes. Start with a HostMyCode VPS for ns1, then add a second node for ns2 in another region. If you’d rather not own the patching and security upkeep, managed VPS hosting keeps the platform maintained while you focus on zones and client changes.

FAQ: Secondary DNS with BIND9

Do I need TCP 53 open to the public?

Yes. Even if most queries use UDP, TCP is required for large responses, DNSSEC-related cases, and reliability. Don’t block TCP/53 globally.

Should the slave be hidden (not listed as NS)?

For most hosting use cases, list both nameservers publicly. “Hidden primary” designs can be useful, but they add moving parts and don’t help basic resilience.

How often does the slave update?

It depends on SOA refresh/retry and notifications. With notify yes and also-notify, updates typically propagate to ns2 within seconds of a reload.

What’s the fastest way to confirm ns2 has the latest zone?

Compare SOA serials: dig @NS1_IP example.com SOA +short and dig @NS2_IP example.com SOA +short. They should match.

Can I use this for reseller hosting DNS?

Yes. Agencies often run authoritative DNS on separate VPS nodes, then point client domains to those NS records.

Just be disciplined about change control and backups of your zone files.

Summary: a repeatable secondary DNS build you can operate

You now have a primary BIND9 server and a secondary that pulls zones using TSIG. You also have a firewall posture that matches day-to-day hosting work.

Keep it reliable by doing two things consistently: bump SOA serials every time, and test both nameservers directly after changes.

If you’re ready to run this in production, deploy the pair on a dependable platform. Keep them in different failure domains.

A HostMyCode VPS setup gives you the control you need for authoritative DNS while staying cost-effective under the HostMyCode tagline: Affordable & Reliable Hosting.

BIND9 DNS Slave Setup Guide Tutorial (2026): Secondary DNS for Faster Failover on a VPS | HostMyCode