Install your own DNS Server

I want to do a series on "rolling your own" and how you can use open-source software for just about all your needs. In this way, you can learn about how some software architectures work under the hood, how to configure, how to research and understand what is needed in order to get something like this setup and running. If you're like me, you're probably on a time-budget, so I won't delay, let's get right into it.

Install bind9

Let's start with the software required. If you are starting from a bare headless linux install like a Docker container or a fresh image, there may be a few things to install like your favorite text editor since we will be editing files. If you don't know of a text editor in Linux, you can use nano (yes, I probably just infuriated a third of you -- I can already see you in the comments section). I like it because it's simple and key-combination based, rather than arcane spell based as far as the navigation is concerned, but remember: it's all about preference and what works for you.

You can install bind9 with the following command (as of Debian Bookworm / Devuan Daedalus):

sudo apt-get install bind9 bind9-host bind9-utils bind9-dnsutils


Once you have the service installed, it will be ready for you to configure. Let's checkout /etc/bind/:

$ tree -Ca /etc/bind 
|-- bind.keys
|-- db.0
|-- db.127
|-- db.255
|-- db.empty
|-- db.local
|-- named.conf
|-- named.conf.default-zones
|-- named.conf.local
|-- named.conf.options
|-- rndc.key
`-- zones.rfc1918

0 directories, 12 files

In this directory, we see the configuration for named (I pronounce it "name-dee") which is the underlying name of the service. There's the root configuration, which is just an include statement to the other configuration files as named.conf.options, named.conf.local and named.conf.default-zones. Let's checkout named.conf.options:

$ cat /etc/bind/named.conf.options 
options {
	directory "/var/cache/bind";

	// If there is a firewall between you and nameservers you want
	// to talk to, you may need to fix the firewall to allow multiple
	// ports to talk.  See

	// If your ISP provided one or more IP addresses for stable 
	// nameservers, you probably want to use them as forwarders.  
	// Uncomment the following block, and insert the addresses replacing 
	// the all-0's placeholder.

	// forwarders {
	// };

	// If BIND logs error messages about the root key being expired,
	// you will need to update your keys.  See
	dnssec-validation auto;

	listen-on-v6 { any; };

In this file, I would just uncomment the forwarders{} section and replace with a DNS server I can trust to send my queries against. You can include multiple addresses here, one to each their own line and separated by a semicolon. Here's the above updated the way I would configure as such:

$ cat /etc/bind/named.conf.options 
options {
	directory "/var/cache/bind";

	forwarders {;;

	dnssec-validation auto;

	listen-on-v6 { any; };

In this way, this named instance will use those IP addresses for DNS queries it cannot answer. Next, let's checkout named.conf.local:

// Do any local configuration here

// Consider adding the 1918 zones here, if they are not used in your
// organization
//include "/etc/bind/zones.rfc1918";

So, in this file, I would just scrap the comments and create my custom zone as I wanted to control it like so:

zone "" {
  type master;
  file "/etc/bind/";
  allow-query { any; };

You can put whatever zone you want here. It won't be recognized by the broader Internet, but in your local network, it works great! All devices you point to this DNS server will get the addresses you control and set here! This configuration will create a zone we control in our DNS server and the target file in /etc/bind/ we will create that will contain the zone records for this domain. If you find yourself managing multiple domains, I find it easier to place these configurations in a subdirectory.

Domain Configuration

Now that we have our base server configuration in place, it's time to configure our domain! Let's setup a few essential records in /etc/bind/

; ##### Markizano.NET
$TTL    600
@       IN      SOA (
                     2024021517 ; Serial
                         604800 ; Refresh
                          86400 ; Retry
                        2419200 ; Expire
                           600) ; Negative Cache TTL

; ### Main Root DB and A records
@                           IN  A
@                           IN  AAAA    fe80::01

@                           IN  NS      ns1
@                           IN  NS      ns2

ns1							IN	A
ns2							IN	A

The initial $TTL 600 tells the default timeout for the root SOA or "Source Of Address". A Zone must have a root A record, so that is provided as It also helps to have NS or "Name Space" records, so those are defined as "ns1" and "ns1" for "" and "".

Fun tip: Domain records that end with a trailing dot "." are the actual TLD or "Top-Level Domain", so you might sometimes see

Check Configuration

Before we restart the service, we can check the syntax of our configuration to ensure it's all correct. This avoids failures on restart. Let's use the named-checkconf command to check the configuration. This command should output nothing if there is no issues and return 0 in its exit code. If there are errors with your configuration, it should print where there is an error and you can correct that.

You can check the zones configuration file with named-checkzone like so:

# named-checkzone /etc/bind/
zone loaded serial 2024021517

Once you get the OK from these commands, you should be good to proceed!

Restart Service

The next step after all the configuration is in place is to restart the service so the configuration is loaded into the daemon to be served up! Once the service is restarted, you can query against it to find out if your records are working!

$ sudo service named restart

Querying against the DNS server is as easy as nslookup, host, or dig. Let's use host:

$ host -d
Trying ""
Using domain server:

;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 27155
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;			IN	A


Received 47 bytes from in 0 ms
Trying ""
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57057
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0


;; ANSWER SECTION:		600	IN	AAAA	fe80::1

Received 59 bytes from in 0 ms
Trying ""
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41693
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0

;			IN	MX

;; AUTHORITY SECTION:		600	IN	SOA 2024021517 604800 86400 2419200 600

Received 72 bytes from in 0 ms

and Boom! You now have a fully functional DNS server that you control! Congratulations! If you want your system to use this by default, simply update /etc/resolv.conf, make sure this entry is first in this file:


Or whatever the IP address is of the host running your DNS service in your environment. That's how you roll your own 😎

If you like tutorials on CloudOps/DevOps stuff, feel free to subscribe to this or any of my socials! I have a lot of work to catch up on!!!
Drop a comment if you would like another post on how to secure and harden your configuration to have a DNS server face the public.


Popular posts from this blog

Setup and Install Monero(d) -- p2pool -- xmrig

Build xmrig on Linux

Perl Net::SSH2::SFTP Example