Two authoritative nameservers let a resolver try another server when one is unavailable. To make that useful, the servers must have the same zone, a working delegation, and enough independence to survive the failure you care about.
This guide uses a BIND 9 primary in one VPS location and a secondary in another. The primary owns edits and signing; the secondary receives zone transfers and answers queries independently. No BGP session or anycast prefix is required.
Two locations on one provider still share some dependencies. For stronger independence, put an additional secondary on another network. Choose the design around your failure requirements, not just the number of nameserver names.
Prepare two dedicated DNS servers
Use fresh Ubuntu or Debian servers with a supported BIND 9 package. The configuration below uses syntax available in BIND 9.18 and 9.20. On both:
sudo apt update
sudo apt install bind9 bind9-utils dnsutils
sudo ss -lntup '( sport = :53 )'
Check for an existing DNS service before changing listeners. This is an authoritative service, not a public recursive resolver. Keep administration access restricted, but permit public queries over both UDP and TCP port 53, including IPv6 if you publish IPv6 nameserver addresses.
All addresses and the domain below are documentation examples. Replace them before deploying:
| Role | Name | Example IPv4 address |
|---|---|---|
| Primary | ns1.example.com | 203.0.113.10 |
| Secondary | ns2.example.com | 198.51.100.20 |
In the existing options block in /etc/bind/named.conf.options, retain the package's directory setting and configure:
recursion no;
allow-query { any; };
allow-query-cache { none; };
allow-transfer { none; };
listen-on { any; };
listen-on-v6 { any; };
Replace existing directives of the same name rather than duplicating them. These are entries inside options, not a second options block. See BIND's authoritative-server configuration for the separation between answering for a zone and providing recursion.
Authenticate transfers with a shared key
Generate one TSIG key on the primary:
sudo sh -c 'umask 077; tsig-keygen -a hmac-sha256 zone-transfer > /etc/bind/zone-transfer.key'
sudo chown root:bind /etc/bind/zone-transfer.key
sudo chmod 640 /etc/bind/zone-transfer.key
Copy that file to the same path on the secondary using an authenticated SSH/SFTP connection, then set the same owner and permissions there. Do not generate a second key: both servers need the same secret. Keep it out of source control.
TSIG authenticates transfers and notifications; it does not encrypt the zone contents. Ordinary public DNS records are still public. BIND documents TSIG key distribution and authentication separately from DNSSEC.
Add this to /etc/bind/named.conf.local on the primary:
include "/etc/bind/zone-transfer.key";
zone "example.com" {
type primary;
file "/var/lib/bind/db.example.com";
allow-update { none; };
allow-transfer { key "zone-transfer"; };
notify explicit;
also-notify { 198.51.100.20 key "zone-transfer"; };
};
On the secondary, add:
include "/etc/bind/zone-transfer.key";
zone "example.com" {
type secondary;
file "/var/cache/bind/db.example.com";
primaries { 203.0.113.10 key "zone-transfer"; };
allow-notify { key "zone-transfer"; };
allow-transfer { none; };
};
Keep the key requirement as the transfer ACL. Adding a plain IP address beside the key in that list would also authorise that address without the key. The configuration reference defines these access controls.
Create and validate the zone
On the primary, create /var/lib/bind/db.example.com:
$ORIGIN example.com.
$TTL 300
@ IN SOA ns1.example.com. hostmaster.example.com. (
2026102501 ; serial: increase for each edit
3600 ; refresh
600 ; retry
604800 ; expire
300 ; negative-cache TTL
)
IN NS ns1.example.com.
IN NS ns2.example.com.
ns1 IN A 203.0.113.10
ns2 IN A 198.51.100.20
@ IN A 203.0.113.80
www IN A 203.0.113.80
Replace the web address too, and add the real domain's other records before any migration. The SOA contact represents hostmaster@example.com. Add nameserver AAAA records only once each IPv6 listener and firewall path has passed direct tests.
Set ownership and validate on the primary:
sudo chown root:bind /var/lib/bind/db.example.com
sudo chmod 640 /var/lib/bind/db.example.com
sudo named-checkzone example.com /var/lib/bind/db.example.com
sudo named-checkconf
Run sudo named-checkconf on the secondary too. Only after validation succeeds, start or reload BIND on each server:
sudo systemctl enable --now named
sudo systemctl reload named
sudo journalctl -u named -n 50 --no-pager
These packages use the named service; some distributions also expose a bind9 alias. Check logs for a successful transfer, then query from outside both servers:
dig @203.0.113.10 example.com SOA +norecurse
dig @198.51.100.20 example.com SOA +norecurse
dig @198.51.100.20 example.com SOA +norecurse +tcp
dig @198.51.100.20 www.example.com A +norecurse
dig @203.0.113.10 example.com AXFR
The SOA serials should match and direct zone answers should carry the aa flag. The unauthenticated AXFR should fail. Also test TCP on the primary. Query an unrelated domain with recursion requested and verify the server does not perform recursive resolution for the public client.
For a transfer test, add a temporary TXT record on the primary, increase the serial, validate, and reload the zone with sudo rndc reload example.com. Confirm it appears on the secondary. Remove the test record with another serial increase. Never hand-edit the secondary's transferred copy.
Delegate only after both servers answer
At the registrar, register ns1.example.com and ns2.example.com as nameserver hosts with their addresses. These in-domain names need parent-side glue to avoid a circular lookup. Then set the domain's nameservers to those two names. Keep parent glue and the zone's A/AAAA records consistent.
Inspect the delegation:
dig +trace example.com NS
dig @1.1.1.1 www.example.com A
dig @8.8.8.8 www.example.com A
If this is an existing domain, inventory its DNSSEC DS records before moving it. An old DS record pointing to a key the new servers do not serve can break validation. Follow a coordinated signing migration, and keep the old authoritative service answering through the previous delegation and record TTLs. See the DNS cutover guide.
Sign the zone, then publish the DS
For a new, currently unsigned delegation, add these entries to the primary's zone block:
dnssec-policy default;
inline-signing yes;
key-directory "/var/lib/bind";
Ensure the bind user can write the zone and key directory under the distribution's security policy. Validate and reload. Leave signing off on the secondary: it transfers the signed zone from the primary.
Check both servers before touching the parent:
dig @203.0.113.10 example.com DNSKEY +dnssec
dig @198.51.100.20 example.com DNSKEY +dnssec
dig @198.51.100.20 example.com SOA +dnssec
sudo rndc dnssec -status example.com
On the primary, identify the active generated public key file. Use dnssec-dsfromkey -2 with that file to derive the DS. The BIND utility reference describes its output. Submit the resulting DS through the registrar only after the signed zone is available on both servers.
Verify the parent publishes the correct DS and validate with delv example.com SOA. If parental checks are not configured, confirm DS publication to BIND with sudo rndc dnssec -checkds published example.com only after checking the parent. Preserve the private keys and policy state in encrypted backups. See the BIND DNSSEC guide for signing and subsequent key maintenance.
Rehearse losing a nameserver
Monitor each server directly for UDP and TCP answers, authoritative status, expected records, serial freshness, and signature expiry. Also monitor through a validating recursive resolver. A running process does not prove the delegation works.
During a planned test, stop BIND on the primary while leaving the secondary running. Query the secondary directly, then use an uncached name through recursive resolvers to exercise discovery rather than only their caches. Resolver retry timing varies, so record delays as well as eventual success.
Restore the primary, make a serial-incrementing change, and confirm transfers resume. Repeat the exercise with the secondary unavailable. Avoid restarting both together.
The secondary does not hold a usable zone forever without its primary. The SOA expiry limits how long it can continue after failed refreshes; DNSSEC signatures can become unusable sooner. Alert on transfer failures and remaining signature validity well before either deadline. Keep the zone source, configuration, and signing keys in your recovery plan.
