
How to Run Your Own DNS Server with BIND?
Most domains today are served by a managed DNS provider, and for good reason. But running your own nameserver is still the best way to truly understand DNS, and for some teams it is the right production choice: full control over every record, no per-query bills, internal zones that never leave your network, and no dependency on someone else's dashboard. BIND 9, maintained by ISC, is the reference implementation of DNS and still powers a large share of the internet's authoritative servers. If you are weighing self-hosting against a hosted service, read what managed DNS is and whether it is worth paying for first, then come back here.
This guide walks through installing BIND on Ubuntu or Debian, configuring an authoritative primary server for example.com, adding a secondary with authenticated zone transfers, enabling DNSSEC signing, running a private caching resolver safely, and the operational habits that keep a self-hosted DNS server out of trouble.
Decide What Kind of Server You Are Building
BIND can act as two very different things, and mixing them on one internet-facing server is a classic mistake:
- Authoritative server — answers questions about zones you own (
example.com) for anyone on the internet. It should never perform recursion for strangers. - Recursive resolver — looks up any name on behalf of your own clients and caches results. It should only answer your own networks.
The difference between authoritative and recursive servers post covers the theory. The practical rule is: public authoritative servers get recursion no, and resolvers get strict access lists. This guide builds an authoritative primary at 203.0.113.53, a secondary at 198.51.100.53, and shows a separate resolver configuration at the end.
Step 1: Install BIND
On Ubuntu or Debian:
sudo apt update
sudo apt install bind9 bind9-utils bind9-dnsutils
named -v
This installs the named daemon, the checking tools (named-checkconf, named-checkzone), and client tools such as dig. named -v prints the version; current supported releases are in the 9.18 and 9.20 series. On RHEL, Rocky, or AlmaLinux the package is bind with bind-utils, the main file is /etc/named.conf, and zone files usually live under /var/named.
Debian-family systems split the configuration into several files under /etc/bind/:
| File | Purpose |
|---|---|
named.conf | Top-level file that includes the others |
named.conf.options | Global options such as recursion, listening addresses, and ACLs |
named.conf.local | Your zone definitions |
named.conf.default-zones | Built-in zones like localhost and the root hints |
Step 2: Configure Global Options
Edit /etc/bind/named.conf.options for an authoritative-only server:
options {
directory "/var/cache/bind";
recursion no;
allow-query { any; };
allow-transfer { none; };
listen-on { any; };
listen-on-v6 { any; };
version "not disclosed";
minimal-responses yes;
};
recursion no makes the server refuse to look up names it is not authoritative for, which keeps it from being abused in DNS amplification attacks. allow-transfer { none; } blocks zone transfers globally; you will allow them per zone for the secondary only. version hides the BIND release from version.bind queries, and minimal-responses keeps answers small.
Step 3: Create the Zone File
Make a directory for your zones and create the file. If you are new to the format, what is a DNS zone file explains each piece in detail.
sudo mkdir -p /etc/bind/zones
sudo nano /etc/bind/zones/db.example.com
$TTL 3600
@ IN SOA ns1.example.com. hostmaster.example.com. (
2026100101 ; serial (YYYYMMDDnn)
3600 ; refresh
900 ; retry
1209600 ; expire
300 ) ; negative caching TTL
; Nameservers
IN NS ns1.example.com.
IN NS ns2.example.com.
; Nameserver addresses (these need glue at the registrar)
ns1 IN A 203.0.113.53
ns2 IN A 198.51.100.53
; Website
@ IN A 203.0.113.10
@ IN AAAA 2001:db8::10
www IN CNAME example.com.
; Email
@ IN MX 10 mail.example.com.
mail IN A 203.0.113.25
@ IN TXT "v=spf1 mx -all"
The SOA record names the primary server and a contact mailbox (the first dot in hostmaster.example.com. stands for @). The serial is what secondaries compare to decide whether to fetch a new copy, so you must increase it on every edit — the date-plus-counter format makes that easy. Note the trailing dots on fully qualified names; leaving one off appends the zone name and produces records like mail.example.com.example.com.
Step 4: Declare the Zone
Add the zone to /etc/bind/named.conf.local:
zone "example.com" {
type primary;
file "/etc/bind/zones/db.example.com";
allow-transfer { 198.51.100.53; };
also-notify { 198.51.100.53; };
notify yes;
};
type primary makes this server the source of truth for the zone. allow-transfer lets only the secondary pull the zone, and notify plus also-notify tell the secondary immediately when the serial changes instead of waiting for the refresh timer. Older tutorials use type master; BIND still accepts that, but primary is the current keyword.
Step 5: Check, Start, and Test
Always validate before reloading. A syntax error in a zone file means BIND will refuse to load that zone.
sudo named-checkconf
sudo named-checkzone example.com /etc/bind/zones/db.example.com
sudo systemctl enable --now named
sudo systemctl status named --no-pager
named-checkconf prints nothing when the configuration is valid. named-checkzone should end with OK and print the loaded serial. On recent Ubuntu and Debian releases the service is called named (with bind9 kept as an alias).
Open port 53 for both UDP and TCP. TCP is not optional — it is used for zone transfers and any response too large for UDP, as explained in why DNS uses port 53.
sudo ufw allow 53/udp
sudo ufw allow 53/tcp
Then test from another machine:
dig @203.0.113.53 example.com SOA +norecurse
dig @203.0.113.53 www.example.com A +norecurse
dig @203.0.113.53 google.com A
The first two queries should return answers with the aa (authoritative answer) flag. The third should return REFUSED with no answer, which proves recursion is disabled for outsiders.
Step 6: Add a Secondary Server with TSIG
Every public zone needs at least two nameservers on separate networks. Install BIND on the second machine the same way, then secure the transfer with a shared TSIG key so the secondary does not rely on IP addresses alone.
Generate the key on the primary:
sudo tsig-keygen -a hmac-sha256 transfer-key | sudo tee /etc/bind/transfer.key
sudo chown root:bind /etc/bind/transfer.key
sudo chmod 640 /etc/bind/transfer.key
This writes a key "transfer-key" block containing a random secret. Copy the same file to the secondary. On the primary, include the key and require it for transfers by updating the zone:
include "/etc/bind/transfer.key";
zone "example.com" {
type primary;
file "/etc/bind/zones/db.example.com";
allow-transfer { key transfer-key; };
also-notify { 198.51.100.53; };
notify yes;
};
On the secondary, in named.conf.local:
include "/etc/bind/transfer.key";
server 203.0.113.53 {
keys { transfer-key; };
};
zone "example.com" {
type secondary;
primaries { 203.0.113.53; };
file "/var/cache/bind/db.example.com";
};
The server block tells the secondary to sign every request to the primary with the key, and the zone pulls its data from the primaries list. The secondary stores its copy under /var/cache/bind because Debian's AppArmor profile does not let named write to /etc/bind. Reload both servers with sudo rndc reload, then check the secondary's log with sudo journalctl -u named --since "5 minutes ago" for a "Transfer completed" message. The zone transfer post explains AXFR and IXFR in more depth.
Step 7: Point Your Domain at Your Servers
Your nameservers are named inside the zone they serve (ns1.example.com serves example.com), so resolvers need glue records at the registry to find them. At your registrar:
- Register
ns1.example.comwith203.0.113.53andns2.example.comwith198.51.100.53as host records (often labelled "child nameservers" or "glue records"). - Change the domain's nameservers to
ns1.example.comandns2.example.com.
You can confirm the delegation by asking a TLD server directly:
dig @a.gtld-servers.net example.com NS +norecurse
The authority section should list your two nameservers and the additional section their glue addresses. Read what glue records are if the registrar's terminology is confusing.
Step 8: Sign the Zone with DNSSEC
Modern BIND handles DNSSEC key generation, signing, and rollovers automatically through dnssec-policy. Add two lines to the primary's zone block:
zone "example.com" {
type primary;
file "/etc/bind/zones/db.example.com";
dnssec-policy default;
inline-signing yes;
allow-transfer { key transfer-key; };
also-notify { 198.51.100.53; };
notify yes;
};
With inline-signing, BIND keeps your hand-edited file unsigned and maintains a signed copy alongside it, so you continue editing the zone exactly as before. After sudo rndc reload, check the status and generate the DS record to give your registrar:
sudo rndc dnssec -status example.com
dig @127.0.0.1 example.com DNSKEY | dnssec-dsfromkey -f - example.com
Submit only the DS record derived from the key-signing key (flags 257) — usually the SHA-256 line — at the registrar. Do not publish the DS until signed answers are being served from both nameservers, or validating resolvers will fail your domain.
Making Changes Safely
Your day-to-day workflow becomes:
sudo nano /etc/bind/zones/db.example.com # edit records and bump the serial
sudo named-checkzone example.com /etc/bind/zones/db.example.com
sudo rndc reload example.com
dig @198.51.100.53 example.com SOA +short # confirm the secondary has the new serial
Forgetting to increase the serial is the most common self-hosting mistake: the primary serves the new data, but the secondary keeps the old copy indefinitely, so users get different answers depending on which nameserver they hit.
Optional: A Private Caching Resolver
If you also want a recursive resolver for your office or home network, run it on a separate server (or at least a separate internal IP) and lock it down:
acl "trusted" {
192.168.1.0/24;
localhost;
localnets;
};
options {
directory "/var/cache/bind";
recursion yes;
allow-recursion { trusted; };
allow-query { trusted; };
allow-query-cache { trusted; };
dnssec-validation auto;
listen-on { 192.168.1.53; 127.0.0.1; };
};
allow-recursion and allow-query limit service to your LAN, and dnssec-validation auto validates answers using the built-in root trust anchor. Never set allow-recursion { any; } on a server reachable from the internet — open resolvers are found and abused within hours.
Operational Best Practices
- Monitor from the outside. Check that both nameservers answer, return the same serial, and that DNSSEC signatures are not close to expiry.
- Log queries only when debugging.
sudo rndc querylog ontoggles query logging; leaving it on fills disks quickly. - Rate-limit responses. Adding
rate-limit { responses-per-second 10; };tooptionsblunts reflection attacks against authoritative servers. - Patch promptly. Subscribe to ISC security announcements; BIND vulnerabilities are disclosed and fixed regularly.
- Keep zones in version control. A Git repository plus a deploy script that runs
named-checkzonebefore reloading prevents most outages. - Consider a hidden primary. Many operators keep the primary private and publish only secondaries (or a managed provider receiving transfers) as the public nameservers.
Running BIND FAQ
Yes. BIND 9 is actively maintained by ISC, supports every modern DNS feature including automated DNSSEC, and is the reference implementation. Alternatives like Knot DNS, NSD, and PowerDNS are also excellent, particularly for authoritative-only use.
At least two authoritative nameservers on different networks, ideally in different locations. Registries require two nameservers for most TLDs, and a single server is a single point of failure for your entire domain.
For authoritative servers, REFUSED usually means the zone failed to load, the query is for a zone you do not host while recursion is off, or an allow-query ACL is blocking the client. Check the logs with journalctl -u named.
They are the same. BIND introduced primary and secondary as the preferred keywords, and master and slave remain accepted for backward compatibility.
Most often the serial number was not increased. Other causes are a firewall blocking TCP port 53, a TSIG key mismatch, or allow-transfer not permitting the secondary. The secondary's logs will show the reason.
It can, but it is not recommended for internet-facing servers. Separating the roles avoids cache poisoning risks and accidental open resolvers. If you must combine them, restrict recursion to trusted networks with allow-recursion.
Use rndc reload to reload all zones or rndc reload example.com for one zone. The server keeps answering queries during the reload.
It is optional but recommended for domains where spoofing would be harmful. With dnssec-policy, BIND automates signing and key rollovers, so the main manual step is publishing the DS record at your registrar.
Conclusion
Running your own DNS with BIND comes down to a handful of decisions made carefully: keep authoritative and recursive roles separate, write a clean zone file with a serial you always remember to bump, run at least two servers with authenticated transfers between them, register glue at the registrar, and let dnssec-policy handle signing. The tools BIND ships with — named-checkconf, named-checkzone, rndc, and dig — catch nearly every mistake before it reaches the internet, as long as you make them part of every change.
Self-hosting is not free; you are taking on patching, monitoring, and attack exposure that a managed provider would otherwise absorb. But for internal zones, learning environments, or teams that want complete control, a well-run BIND setup is stable, fast, and remarkably low maintenance once it is in place.
Here are some useful references for going deeper on running BIND:
- ISC: BIND 9 Administrator Reference Manual — the official documentation for every configuration statement used in this guide.
- ISC: BIND 9 project page — release information, supported versions, and security advisories.
- RFC 1035: Domain Names - Implementation and Specification — the core DNS specification, including zone file syntax.
- RFC 8945: Secret Key Transaction Authentication for DNS (TSIG) — the standard behind the transfer keys used for the secondary.
- RFC 2182: Selection and Operation of Secondary DNS Servers — guidance on how many secondaries to run and where.


