# Building a Simple DNS using BIND9

Alright I did my theoretical basics and blogged about [DNS fundamentals](https://nailthecode.com/dns-fundamentals), so now it is time to put in more practical work. Get some of those muscles working...

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752969997853/8af4137e-ee0f-4982-a997-4874bd0c3a76.gif align="center")

For today's mini project I will be setting up my very own DNS using BIND9. After installing and starting the Ubuntu VM I installed BIND9 and all related dependencies using this command:

```plaintext
sudo apt install bind9 bind9utils bind9-doc -y
```

Once that is done I moved on to editing the zone file. A ***zone file*** in BIND9 is a text file that contains the DNS records for a specific domain. It is essentially your very own "phonebook" for the small part of the internet (the zone) your BIND server is authoritative for.

Let's say our BIND server is responsible for `example.com`, the zone would include everything under that such as:

* [www.example.com](http://www.example.com)
    
* mail.example.com
    
* blog.example.com  
    ...you get the idea.
    

All this info will be kept in your zone file, which BIND9 can read and understand.

I then used nano to edit the config file with the following command:

```plaintext
sudo nano /etc/bind/named.conf.local
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752970345776/ce82ed9f-351e-4cb1-9201-cfb51561447d.png align="center")

The file has no configuration in it yet.

I then added the following line

```plaintext
zone "nailthecode.test"{
	type master;
	file "/etc/bind/zones/db.nailthecode.test";
};
```

So by doing this I have defined a zone and pointed it to a corresponding zone file path. The `type master` means that this server is the authoritative source for DNS information about the `nailthecode.test` zone.

## Types

Apart from the master type there are three more types. They are:

1. Slave  
    This server copies zonal data from a master server via a zone transfer. It serves as a read-only replica of the master server. It can also respond to DNS queries authoritatively, just like a master server.
    
2. Stub  
    This server copies data from the master server, but only the NS records and optional A/AAAA records. It doesn't serve DNS records directly to the client. It points to specific name servers so that the answer to the request is achieved faster.
    
    ```plaintext
    "dev.nailthecode.test" {
    	type stub;
    	file "/etc/bind/zones/stub.dev.nailthecode.test";
    	masters { 10.0.0.5; }; // the DNS server for dev.nailthecode.test
    };
    ```
    
3. Forward  
    This server just forwards a query and waits for an answer. It can also cache answers locally for faster responses on queries made in the future.
    

I then created the zones directory in `/etc/bind/` and added the `db.nailthecode.test` file. Here is what my final code looked like.

```plaintext
$TTL 640000 ;Time To Live of all the records in this zone file
$ORIGIN nailthecode.test. ; origin domain

    @    IN  SOA  ns1.nailthecode.test. admin.nailthecode.test.(
;└──┬──┘ └┬┘ └┬┘ └──────┬──────────────┘
; name  class type     value	

; sync instructions for secodary (slave) servers
		2025071901 ; serial
		64000 ;refresh
		6000 ;retry
		64000 ;expire
		3600 ;negative TTL
)

@ IN NS ns1.nailthecode.test. ;main authoritative name server
@ IN NS ns2.nailthecode.test. ;optional ns for redundancy

ns1 IN A 10.0.0.2 ;ns1 ip address ipv4 (use AAAA for ipv6)
ns2 IN A 10.0.0.3 ;ns2 ip address

www IN A 10.0.0.10 ; www.nailthecode.test lives at 10.0.0.10
test IN A 10.0.0.20 ; test.nailthecode.test lives at 10.0.0.20

mail IN A 10.0.0.30 ; mailing ip
mail IN MX 10 mail.nailthecode.test. ; mail is sent to mail.nailthecode.test
```

*NOTE: I added some* ***explanatory comments*** *here, please use the version below for copy pasting*

```plaintext
$TTL 640000
$ORIGIN nailthecode.test.

@   IN  SOA ns1.nailthecode.test. admin.nailthecode.test. (
        2025071901 ; serial
        64000      ; refresh
        6000       ; retry
        64000      ; expire
        3600       ; negative TTL
)

@   IN NS ns1.nailthecode.test.
@   IN NS ns2.nailthecode.test.

ns1 IN A 10.0.0.2
ns2 IN A 10.0.0.3

www  IN A 10.0.0.10
test IN A 10.0.0.20

mail IN A 10.0.0.30
mail IN MX 10 mail.nailthecode.test.
```

`IN` stands for Internet and is almost always the type of class used in the DNS. Originally the DNS was designed to handle multiple network types, which these days, aren't popular or in use anymore.

Now we can test if our DNS is working.

## TESTS TESTS TESTS

I tried restarting the bind9 server and I have already run into my first problem.

```plaintext
Job for named.service failed because the control process exited with error code.
See "systemctl status named.service" and "journalctl -xeu named.service" for details.
```

After a status check using `systemctl status bind9` …

```plaintext
Warning: The unit file, source configuration file or drop-ins of named.service changed on disk. Run 'systemctl daemon-reload' to reload units.
× named.service - BIND Domain Name Server
     Loaded: loaded (/usr/lib/systemd/system/named.service; enabled; preset: enabled)
     Active: failed (Result: exit-code) since Sun 2025-07-20 00:56:00 CAT; 25s ago
       Docs: man:named(8)
    Process: 5131 ExecStart=/usr/sbin/named -f $OPTIONS (code=exited, status=1/FAILURE)
   Main PID: 5131 (code=exited, status=1/FAILURE)
        CPU: 76ms
```

Ok let's check the config files... `sudo named-checkconf` ahaaaa!!!

```plaintext
/etc/bind/named.conf.test:12: missing ';' before '}'
/etc/bind/named.conf:11: missing ';' before 'include'
```

I have fixed this in the code snippet above so you probably won't face the same issues.

once everything is fixed when running `sudo systemctl status bind9` you will see something like this:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752970768536/4fea6b08-0719-4147-9d17-389a2d811543.png align="center")

congratulations the DNS is active and running!

Now let's check the zones.

`sudo named-checkzone nailthecode.test /etc/bind/zones/db.nailthecode.test`

....... another error :(

```plaintext
/etc/bind/zones/db.nailthecode.test:4: no current owner name
zone nailthecode.test/IN: loading from master file /etc/bind/zones/db.nailthecode.test failed: no owner
zone nailthecode.test/IN: not loaded due to errors.
```

<s>fixed. I think.</s>

<s>The issue was that I was copy pasting from a windows device to a linux vm, which caused file format errors.</s>

<s>The fix was installing </s> `dos2unix`

```plaintext
sudo apt isntall dos2unix
```

<s>and running this command from root</s>

```plaintext
sudo dos2unix /etc/bind/zones/db.nailthecode.test
```

actually nope that wasn't it. It was all the comments I added in the first version of the zone file above.

***NOTE***\*: Fixed code available for use above.\*

run this again...

`sudo named-checkzone nailthecode.test /etc/bind/zones/db.nailthecode.test`

and you will get

```plaintext
zone nailthecode.test/IN: loaded serial 2025071901
OK
```

perfect let's move on.

Run this `sudo named-checkconf` -- there shouldn’t be any output.

Restart the bind9 server `sudo systemctl restart bind9` and check the status `sudo systemctl status bind9`

Now we can use dig to test if our nailthecode.local zone is responding properly.

ERRORR!

*NOTE to SELF: Don't use .local for testing purposes*

Ok so the problem here was that before this blog was edited I was using `nailthecode.local` as the main domain name. However .local is already reserved in the system so I ran into an error. You probably won't face this issue, but hey, I like to document my failures. Not to prevent them, of course :) just so Future Me has somewhere to go back to when everything catches fire again.

NOW FOR THE FINAL TESTS

TESTING USING DIG

```plaintext
dig @localhost nailthecode.test
dig @localhost www.nailthecode.test
dig @localhost mail.nailthecode.test MX
```

You will get something like this for the first one

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752971082156/f38bf74a-f8a8-4aa8-87c5-01ed9ec9599f.png align="center")

Second command result

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752971140496/51bc7266-7604-4be6-9a31-65358ea1dae9.png align="center")

and finally…

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752971173044/9440e45d-f086-4d61-85b0-61e5fba17f84.png align="center")

All Good!

You can try challenges such as this and more [here](https://app.codecrafters.io/r/innocent-eel-222063).

Cover Photo by [Jake Walker](https://unsplash.com/@jakewalker?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) on [Unsplash](https://unsplash.com/photos/black-flat-screen-computer-monitor-MPKQiDpMyqU?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash)
