Type something to search...
How to Run Your Own DNS Server with BIND?

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/:

FilePurpose
named.confTop-level file that includes the others
named.conf.optionsGlobal options such as recursion, listening addresses, and ACLs
named.conf.localYour zone definitions
named.conf.default-zonesBuilt-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:

  1. Register ns1.example.com with 203.0.113.53 and ns2.example.com with 198.51.100.53 as host records (often labelled "child nameservers" or "glue records").
  2. Change the domain's nameservers to ns1.example.com and ns2.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 on toggles query logging; leaving it on fills disks quickly.
  • Rate-limit responses. Adding rate-limit { responses-per-second 10; }; to options blunts 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-checkzone before 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:

  1. ISC: BIND 9 Administrator Reference Manual — the official documentation for every configuration statement used in this guide.
  2. ISC: BIND 9 project page — release information, supported versions, and security advisories.
  3. RFC 1035: Domain Names - Implementation and Specification — the core DNS specification, including zone file syntax.
  4. RFC 8945: Secret Key Transaction Authentication for DNS (TSIG) — the standard behind the transfer keys used for the secondary.
  5. RFC 2182: Selection and Operation of Secondary DNS Servers — guidance on how many secondaries to run and where.
Tags :
Share :

Related Posts

What Is the Difference Between Authoritative and Recursive DNS Servers?

What Is the Difference Between Authoritative and Recursive DNS Servers?

When someone says "the DNS server," they could mean two completely different machines doing two completely different jobs. One kind of server holds t

Continue Reading
Can DNS settings affect website speed?

Can DNS settings affect website speed?

Yes, DNS settings can significantly affect the speed at which a website loads for its users. DNS, or Domain Name System, is often likened to the inte

Continue Reading
Can You Use a CNAME Record on the Root Domain?

Can You Use a CNAME Record on the Root Domain?

It is one of the most common DNS questions there is. Your hosting platform says "add a CNAME pointing to myapp.example-cdn.net," it works perfectly

Continue Reading