This is an old revision of the document!
bind9dns
This tutorial is for users of Debian GNU/Linux to set up an authoritative DNS server using bind9. An authoritative DNS server serves DNS records about other hosts … that is, you use an authoritative server to serve domain.com's A, AAAA, DMARC, SPF, etc., records. These records can then be queried by a recursive DNS resolver. Bind9 can also do recursion, however, it's far more commonly used as an authoritative DNS server. Unbound, on the other hand, is designed primarily for recursive DNS. If you are just looking to protect against leaks and guard DNS privacy, you should instead head over unbound-dns instead. In this tutorial, we will:
This tutorial presumes you already have a working and sufficiently hardened VM/VPS with a LAMP stack and access to PTR for three different external IPs. If you don't know what some or all of that is, take a step back and start with Apache Survival before proceeding. If you feel comfortable so far, and you have three different VMs/VPSs setup and ready, well then carry on.
Make sure that you set your full hostname with hostnamectl set-hostname nsX.haacksnetworking.com and/or equivalent for your use-case on each node. Of course, install bind9 with apt install bind9 bind9-utils bind9-dnsutils bind9-doc -y. After that, ensure that each host has local DNS resolution via /etc/hosts/ that informs each node about itself, its alias, and those of every other node in its cluster. Something like this will suffice (on each node):
127.0.0.1 localhost 127.0.1.1 ns1.haacksnetworking.com ns1 8.28.86.113 ns1.haacksnetworking.com ns1 8.28.86.114 ns2.haacksnetworking.com ns2 8.28.86.115 ns3.haacksnetworking.com ns3 2604:fa40:0:10::11 ns1.haacksnetworking.com ns1 2604:fa40:0:10::12 ns2.haacksnetworking.com ns2 2604:fa40:0:10::13 ns3.haacksnetworking.com ns3 # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters
A firewall is not strictly necessary so long as fail2ban is running, which ensures that heinous and/or repetitive queries get dropped. If you choose to go without a firewall, ensure you are strictly configuring all services and that you can recite every service listed in ss -tulpn by heart and why they need to be there. It is also okay to use a firewall so long as you do not use it as an excuse for mis-configuring your server(s). In my case, I use ufw which is simply a command line skin for iptables. It makes the configuration a little easier. Here are example configurations for each node:
Here is ns1.haacksnetworking.com:
ufw reset ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp ufw allow 53/tcp ufw allow 53/udp ufw allow 80/tcp ufw allow 443/tcp ufw enable
Here is ns2.haacksnetworking.com:
ufw reset ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp ufw allow 53/tcp ufw allow 53/udp ufw allow from 8.28.86.113 to any port 80 proto tcp ufw allow from 8.28.86.113 to any port 443 proto tcp ufw allow from 8.28.86.113 to any port 10000:10010 proto tcp ufw allow from 2604:fa40:0:10::11 to any port 80 proto tcp ufw allow from 2604:fa40:0:10::11 to any port 443 proto tcp ufw allow from 2604:fa40:0:10::11 to any port 10000:10010 proto tcp ufw enable
Here is ns3.haacksnetworking.com:
ufw reset ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp ufw allow 53/tcp ufw allow 53/udp ufw allow from 8.28.86.113 to any port 80 proto tcp ufw allow from 8.28.86.113 to any port 443 proto tcp ufw allow from 8.28.86.113 to any port 10000:10010 proto tcp ufw allow from 2604:fa40:0:10::11 to any port 80 proto tcp ufw allow from 2604:fa40:0:10::11 to any port 443 proto tcp ufw allow from 2604:fa40:0:10::11 to any port 10000:10010 proto tcp ufw enable
As you can see, ns2 and ns3 restrict all access on 80/443 to ns1. This is because there is no need to access these nodes directly, as both bind9 and later webmin will directly instruct these slaves with their configurations. Ns1, on the other hand, should be publicly accessible by design. We will secure it later with a strong password (25 characters or more), a reverse proxy, and fail2ban. One can additionally, if they so choose, add source-IP rules to the master node, but I think this is overkill. In my case, I want the master node to be accessible to me everywhere. The 10000-10010 range is for the webmin clustering features. We can now establish the root zone on the master (ns1) and then tie the two slaves to it. Let's open /etc/bind/named.conf.options and enter the following for the global configuration:
options { directory "/var/cache/bind"; recursion no; allow-query { any; }; allow-transfer { none; }; listen-on { any; }; listen-on-v6 { any; }; minimal-responses yes; };
It's a good practice to check the configuration and restart the service after major changes. Let's do that as well as provide proper ownership and perms to our zone configuration directory:
systemctl restart bind9 systemctl enable bind9 chown bind:bind /var/cache/bind/db.* chmod 644 /var/cache/bind/db.* named-checkconf systemctl reload bind9
Now, let's establish the haacksnetworking.com zone, required for the GLUE records and proper functioning of the authoritative part of the authoritative DNS server. Let's open /etc/bind/named.conf.local and enter the following:
zone "haacksnetworking.com" { type master; file "/etc/bind/db.haacksnetworking.com"; allow-transfer { 8.28.86.114; 8.28.86.115; 2604:fa40:0:10::12; 2604:fa40:0:10::13; }; also-notify { 8.28.86.114; 8.28.86.115; 2604:fa40:0:10::12; 2604:fa40:0:10::13; }; };
Now that we've created the base server entry and created the master zone, we should restart the service and add a few items to the zone record file it creates for us. Let's do systemctl restart bind9 and then edit /etc/bind/db.haacksnetworking.com. I can't remember exactly what the zone record was missing, but I believe it picked everything up from the configuration above, except for the SOA and nameserver (ns) records, but be careful and check it against mine. You should have something similar to the following in your zone record file:
$TTL 86400 @ IN SOA ns1.haacksnetworking.com. hostmaster.haacksnetworking.com. ( 2025122401 3600 1800 604800 86400 ) @ IN NS ns1.haacksnetworking.com. @ IN NS ns2.haacksnetworking.com. @ IN NS ns3.haacksnetworking.com. ns1 IN A 8.28.86.113 ns2 IN A 8.28.86.114 ns3 IN A 8.28.86.115 ns1 IN AAAA 2604:fa40:0:10::11 ns2 IN AAAA 2604:fa40:0:10::12 ns3 IN AAAA 2604:fa40:0:10::13 @ IN A 8.28.86.113 @ IN AAAA 2604:fa40:0:10::11
In my case, I had previously been using ISP Config for another hobbyist domain (for the kids and cats). So, I also configured that zone at the CLI, also in /etc/bind/named.conf.local, as follows:
zone "felinefantasy.club" { type master; file "/var/cache/bind/db.felinefantasy.club"; allow-transfer { 8.28.86.114; 8.28.86.115; 2604:fa40:0:10::12; 2604:fa40:0:10::13; }; also-notify { 8.28.86.114; 8.28.86.115; 2604:fa40:0:10::12; 2604:fa40:0:10::13; }; };
This way, once I configured the slaves, the family's cat and fun website would also come back up. Once you have your desired amount of zones added, it's time to configure ns2 and ns3 with corresponding entries. Remember, eventually, we will cluster our instances using webmin and these changes will populate automatically across nodes. At present, however, we are still covering how to do this manually using the CLI. Accordingly, here is the CLI stuffs we need to do on ns2 and ns3:
— oemb1905 2025/12/27 22:19