Create a DNS nameserver for yourself

When you buy a domain from a register, the registrar usually includes their nameserver service so you don't really get to understand what that means or how does it work. A nameserver is responsible for answering queries about the different records that exist for a domain. Look at the following zone configuration file for shirish.ca:

;
; BIND data file for shirish.ca
;
$TTL    604800
@       IN      SOA     shirish.ca. root.shirish.ca. (
                        2024012201      ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL
;
@       IN      NS      ns1.shirish.ca.
@       IN      NS      ns2.shirish.ca.
@       IN      A       159.203.53.70
www     IN      CNAME   @
ns1     IN      A       159.203.53.70
ns2     IN      A       159.203.18.137
_matrix._tcp    3600    IN      SRV     10      5 8448  @
_matrix-fed._tcp        3600    IN      SRV     10      5 8448  @

These records exist on the nameserver for the shirish.ca domain. When someone wants to resolve shirish.ca to an ip address, they first query the root, which provides the .ca top-level domain servers. Then, they query the TLD domain servers which provide the domain's authoritative nameservers which in this case are ns1.shirish.ca and ns2.shirish.ca. But, the nameservers exist within the domain itself. How would anyone know what is the address for ns1.shirish.ca? This is called a circular reference and it's where glue records come into play. Glue records are created at a domain's registrar and is served by the TLD nameserver, in this case the .ca nameserver.

I have two nameservers for my domain, but we are going to ignore the second one for the sake of simplicity.

Go to your registrar and add the glue records that resolve to your nameservers. I'm using namecheap and this is how I have done it:

Those domains can now resolve your custom nameserver's IP address. We still need to set our authoritative nameserver for our domain to point to our DNS server.

These changes take some time to reflect worldwide. You can check the propagation progress on this website.

Now, we need to actually setup a DNS server. I will be doing this on a Ubuntu 22.04 running on a DigitalOcean VPS.

Install the bind9 and dnsutils packages. Bind9 is the DNS server we will be using and dnsutils package comes with useful tools for testing and troubleshooting DNS issues.

# apt install bind9 dnsutils

Every command that starts with # should be executed as root.

The primary config file for bind9 is at /etc/bind/named.conf, which includes three other files with their respective purpose:

  • /etc/bind/named.conf.optinos: global DNS options
  • /etc/bind/named.conf.local: config for your zones
  • /etc/bind/named.conf.default-zones: default zones including localhost, its reverse, and root hints

The default configuration makes your server act as a DNS caching server. Edit /etc/bind/named.conf.options to set the IP addresses of the DNS servers you wish to use.

forwarders {
  1.1.1.1;
  8.8.8.8;
}

Now, we will add a DNS zone to our bind9 config. Add the following to your /etc/bind9/named.conf.loca file:

zone "shirish.ca" {
        type master;
        file "/etc/bind/db.shirish.ca";
};

Obviously replace shirish.ca with your domain in this entire tutorial. Check the validity of your config file with named-checkconf command. If it doesn't output anything, that means everything is fine. It is a good ideaa to run these checks everytime you change your config or zone file.

$ named-checkconf named.conf.local

Use an existing zone file as a template to create your zone file:

# cp /etc/bind/db.local /etc/bind/db.shirish.ca

Edit the zone file and change localhost. to the FQDN of your domain. Always leave an additional . at the end when writing DNS records. Change root.localhost to an email address you'd like to use, but replace @ with a .. Change the top comment to indicate the domain this file is for. I have created an A record for the base doman, a CNAME record for www, and A records for ns1 and ns2. I also have some SRV records for my matrix server. You may create any additional records you like, but A records for the base domain and your nameservers are essential.

;
; BIND data file for shirish.ca
;
$TTL    604800
@       IN      SOA     shirish.ca. root.shirish.ca. (
                        2024012201      ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL
;
@       IN      NS      ns1.shirish.ca.
@       IN      NS      ns2.shirish.ca.
@       IN      A       159.203.53.70
www     IN      CNAME   @
ns1     IN      A       159.203.53.70
ns2     IN      A       159.203.18.137
_matrix._tcp    3600    IN      SRV     10      5 8448  @
_matrix-fed._tcp        3600    IN      SRV     10      5 8448  @

This is how it should look like. You can use the named-checkzone command to check the validity of your zone file.

$ named-checkzone db.shirish.ca

You should increase the serial number every time you make any change to your zone file. It is a good practice to use the date you make the change as the serial number, like the one on my zone file, 2024012201 which is yyymmddss (ss is the serial number). Also, you should restart the bind9 service when you make a change.

# systemctl restart bind9

Check your dns records with the following command:

$ dig shirish.ca any

The output for my domain is:

shirish.ca.             78873   IN      A       159.203.53.70
shirish.ca.             86400   IN      NS      ns1.shirish.ca.
shirish.ca.             86400   IN      NS      ns2.shirish.ca.
shirish.ca.             86400   IN      SOA     ns1.shirish.ca. root.shirish.ca. 2024012401 1200 120 2419200 86300

;; ADDITIONAL SECTION:
ns1.shirish.ca.         78873   IN      A       159.203.53.70
ns2.shirish.ca.         78873   IN      A       159.203.18.137

As you can see, this matches the records I had set in my zone file.

Your forward zone is ready and can resolve domain names to their IP addresses. Now, we are going to setup a reverse zone to resolve IP addresses to domain names.

Edit /etc/bind/named.conf.local and add the following:

zone "53.203.159.in-addr.arpa" {
        type master;
        file "/etc/bind/db.159";
};

Replace 53.203.159 of the first three octets of the IP address you are setting up a reverse zone for.
Create the /etc/bind/db.159 (replace 159 with the first octet of your IP) with the /etc/bind/db.127 file as a template.

# cp /etc/bind/db.127 /etc/bind/db.159

Edit the file like this:

;
; BIND reverse data file for 159.203.53.XX
;
$TTL    604800
@       IN      SOA     localhost. root.localhost. (
                        2024011701      ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL
;
@       IN      NS      ns.
70      IN      PTR     ns1.shirish.ca.

If anyone does reverse lookup for the address 159.203.53.70, they will be pointed to the domain ns1.shirish.ca. Check for validity and restart bind9 to make this effective.

$ named-checkzone /etc/bind/db.159

If it doesn't output anything, everything is fine.

# systemctl restart bind9