Build your own DNS server on Linux

0

Learn how to use BIND to set up your own server for resolving domain names.

Image by: Geralt. CC0.

In the previous article in this two-part series, Introduction to the Domain Name System (DNS), I described how the DNS database is structured and how to configure name services on a client. I also listed and described some of the more common DNS records you are likely to encounter when building a name server or just trying to interpret the results of a dig command.

In this article, I show you how to build your own name server using BIND (Berkeley Internet Name Domain). It is not as difficult as you might think, especially because you can do it in two stages. We’ll start by learning how to create a caching name server, then move on and learn how to upgrade that to a complete primary1 domain name server for your network, complete with forward and reverse zone files.

But Why??

But first, why did I go to the trouble of creating my own name server? I had three reasons for doing this.

My primary consideration was that my network was growing and changing, so updating the /etc/hosts files on every computer became a challenge. The time required to make these changes was becoming greater as each new computer was added to my network.

Second, I wanted to learn about various network services including centralized network configuration so I set up a DHCPD server for my network; then it only made sense to also add a name server. I used BIND for my name server because it’s still the most common and well-known one there is.

Third, the name servers provided by my internet service providers (ISP) in those ancient days mostly sucked. DNA database updates were sporadic and slow when they did occur. I encountered instances where the IP address for domains were incorrect. There were even times when the ISPs name services were down for hours at a time.

For these reasons and more, I decided I would create my own name server. I’ve been pleased with the results and will never go back to using name services supplies by an ISP.

Setting up a DNS server using BIND

Setting up a name server using BIND is quite straightforward, so I’ll show you how to do so on any computer you might have available for experimentation. This little lab project will show you how to install and configure BIND on your computer as a caching name server, test it, then set it up as a primary name server with a zone file that you can use as a name resolver for your network or just for testing.

Setting up a name server on any GNU/Linux computer you have available is technically possible because it will not interfere with other hosts on the network or their operation. However, you should probably not do this on a computer that you do not own or have the right to modify unless you have explicit permission to do so.

My setup

You only need one computer to perform all but one of the tasks in this lab project. I use this setup on my laptops because the name servers provided by DHCP (Dynamic Host Configuration Protocol) when I connect to non-home networks using either wired or wireless connections can sometimes be unreliable. To show that almost any host can perform well as a name server, I long ago tested this project on a long defunct and recycled ASUS EeePC 900 netbook.

I will use a VM with a private IP address for this project but you should use the IP address of the host that you are using.

The hosts file

First, let’s take a look at the /etc/hosts file. In its default state, the hosts file should look like Figure 1, below. The uncommented lines are for IPV4 and IPV6. These two lines are required for a Linux computer to function correctly as they allow the host to perform necessary internal communications.

# Loopback entries; do not change.
# For historical reasons, localhost precedes localhost.localdomain:
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
# See hosts(5) for proper format and other examples:
# 192.168.1.10 foo.mydomain.org foo
# 192.168.1.13 bar.mydomain.org bar

Figure 1: You can maintain a simple hosts file to perform the function of a resolver in small networks.

Earlier versions of the file in Figure 1 have only the two active lines and none of the comments. I suggest using the command man 5 hosts for additional details. Although you can add hostnames and their respective IP Addresses for your local network as shown in Figure 2, this is not an optimal solution to name services, especially with larger networks or when traveling.

# Loopback entries; do not change.
# For historical reasons, localhost precedes localhost.localdomain:
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
# See hosts(5) for proper format and other examples:
# 192.168.1.10 foo.mydomain.org foo
# 192.168.1.13 bar.mydomain.org bar

# Lab hosts
192.168.0.1       server
192.168.0.21      host1
192.168.0.22      host2
192.168.0.23      host3
192.168.0.24      host4

Figure 2: An /etc/hosts file with local hostnames added.

Most of you will not have any entries other than the default localhost lines. But if you’re already using the hosts file for name service on your network you’ll need to maintain the hosts file on every computer in your network. As your network grows that will eventually become a lot of work to maintain.

Preparation

A caching name server can’t replace your use of /etc/hosts to resolve hostnames on the internal network. However, compared to using an ISP or other public name server a caching name server can improve performance when resolving commonly used external names, such as www.cnn.com. The best part is that setting up a caching name server is quite easy.

Before starting, you should prepare by performing the following steps.

First, make backup copies of the files /etc/hosts, /etc/named.conf, resolv.conf, and your firewall configuration and rules. If they are not already installed, use your distribution’s package manager to install the following BIND RPMs: bind, bind-chroot, and bind-utils.

Configure resolv.conf

To enable your lab host to use the caching name server, you must add a name server line to point to your own host in /etc/resolv.conf. This used to be simple but has changed since NetworkManager and systemd-resolved.service replaced the old SystemV network service. I’ve also found problems with name resolution when using the systemd-resolved.service in conjunction with an in-network name server.

I wrote an article for my systemd series, systemd — #12: Fixing systemd-resolved name service failures using Ansible, that describes the problem with systemd-resolved.service and how to resolve it. I suggest you read that article before continuing with this one. Don’t let the title of the article fool you — the fix doesn’t require Ansible unless you’re applying it to a large number of Linux hosts. I won’t copy that entire article, but here are the steps you need to take for your name server to work correctly.

Let’s look at the default resolv.conf setup in Figure 3 before we change anything.

# ll /etc/resolv.conf 
lrwxrwxrwx. 1 root root 39 Apr 13  2023 /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf

# cat /etc/resolv.conf 
# This is /run/systemd/resolve/stub-resolv.conf managed by man:systemd-resolved(8).
# Do not edit.
#
# This file might be symlinked as /etc/resolv.conf. If you're looking at
# /etc/resolv.conf and seeing this text, you have followed the symlink.
#
# This is a dynamic resolv.conf file for connecting local clients to the
# internal DNS stub resolver of systemd-resolved. This file lists all
# configured search domains.
#
# Run "resolvectl status" to see details about the uplink DNS servers
# currently in use.
#
# Third party programs should typically not access this file directly, but only
# through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a
# different way, replace this symlink by a static file or a different symlink.
#
# See man:systemd-resolved.service(8) for details about the supported modes of
# operation for /etc/resolv.conf.

nameserver 127.0.0.53
options edns0 trust-ad
search both.org

Figure 3: The default /etc/resolv.conf file is a symbolic link that points to a “stub” file, /run/systemd/resolve/stub-resolv.conf. That file contains the comments and configuration shown here.

In the default resolver setup, /etc/resolv.conf points to a “stub” file, /run/systemd/resolve/stub-resolv.conf that contains the configuration used by systemd-resolved. The only solution that reliable host resolution for me is to stop and disable systemd-resolved.service. This causes name resolution to revert to the NSSwitch resolution.

First, I deleted the existing link and created a new /etc/resolv.conf file to replace it. I added the content in Figure 4. Be sure to use the correct IP addresses for your own internal network.

search both.org
nameserver 192.168.0.101
nameserver 8.8.8.8
nameserver 8.8.4.4

Figure 4: I created a new /etc/resolv.conf file with the content shown here.

Restart the NetworkManager service.

# systemctl restart NetworkManager

The NSS resolver takes over, and name services work as expected. Now attempt to ping a common public host that does not block ICMP (Internet Control Message Protocol) packets. I typically use one of three public domains intended explicitly for testing, example.com, example.net, and example.org.

# ping www.example.com

You should get an “unknown host” or “Name or service not known” error because you currently have no working DNS service or resolver defined in the resolv.conf file. Now use the dig command to see if name services is working.

# dig www.example.com

You should get the error, “Connection timed out; no servers could be reached.”

Set up a caching name server

A caching name server is not an authoritative source for any domain. It simply caches the results of all name resolver requests from the network that it serves to speed up responses to future requests for the same remote host.

Note: The named.conf file is very particular about syntax and especially punctuation. Semicolons are used to delineate the end of an entry and the end of a stanza as well as the end of a line. Be sure to add them in correctly as shown in the samples.

For the initial setup of the caching name server making a couple modifications to the default /etc/named.conf file is necessary, so edit that file using your favorite editor. First, add the IP address of your local test host to the “listen-on port 53” line as shown in Listing 2, below. This enables named to listen on the external IP Address of your host, so that other computers can use it as a name server as well.

By default, BIND refers to the Internet’s root name servers to locate the authoritative name servers for a domain. It is possible to specify other servers that are called “Forwarders” to which the local instance of BIND will send requests instead of the root servers. This does increase the possibility of DNS hijacking.

Add a “forwarders” line as shown below. This tells your caching DNS server where to obtain IP Addresses when they are not already cached locally. The IP Addresses in the listing below is for the Google public DNS servers You could use your local ISP or OpenDNS or some other public name server as your forwarder. It is not necessary to define any forwarders and, in that case, BIND would use the Internet root servers as defined in the file /var/named/named.ca to locate the authoritative name servers for domains if no forwarders are defined. But for this exercise, please define the forwarders as I have in Figure 5.

Comment out the IPV6 line because we are not using IPV6 in the lab environment. Note that the “//” two forward slashes denote comments in the named.conf file.

//
// named.conf
// Provided by Red Hat bind package to configure the ISC BIND named(8) DNS
// server as a caching only name server (as a localhost DNS resolver only).
// See /usr/share/doc/bind*/sample/ for example named configuration files.
//
//

options {
        listen-on port 53 { 127.0.0.1; 192.168.0.203; };
//      listen-on-v6 port 53 { ::1; };
        forwarders { 8.8.8.8; 8.8.4.4; };
        directory       "/var/named";
        dump-file       "/var/named/data/cache_dump.db";
        statistics-file "/var/named/data/named_stats.txt";
        memstatistics-file "/var/named/data/named_mem_stats.txt";
        allow-query     { localhost; 192.168.0.0/24; };
        recursion yes;

        dnssec-enable yes;
        dnssec-validation yes;
        dnssec-lookaside auto;

        /* Path to ISC DLV key */
        bindkeys-file "/etc/named.iscdlv.key";

        managed-keys-directory "/var/named/dynamic";
};
logging {
        channel default_debug {
                file "data/named.run";
                severity dynamic;
        };
};
zone "." IN {
        type hint;
        file "named.ca";
};
include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";

Fiogure 5: The /etc/named.conf file provides the simple configuration required to set up a caching name server. The lines that need to be added or changed are highlighted in bold.

Add the local network address, 192.168.0.0/24, to the allow-query line. This line specifies the network(s) from which DNS queries will be accepted by this DNS server.

Start the named service

Now start the named service and configure the named service to start at every boot. I use the systemctl command on my Fedora host, but the command may be different on your host, depending upon the distribution you are using. Note that the name of the BIND resolver service is named.

# systemctl enable --now named

The first test you can perform to ensure that your caching name server is working is to use dig to locate the DNS database information for wally2.both.org. To further test your caching name server, use the dig command to obtain the IP Address(es) for some common Internet websites, such as www.opensource.com, CNN, Wired, and any others you like. The results should now show your host as the responding server.

At this point your caching name server will correctly resolve hosts on the Internet and that is because those DNS requests for public hosts are forwarded to the Google public name servers—refer to the “forwarders” line in named.conf. However, you are still dependent upon the /etc/hosts file for internal name services. Creating a primary name server can solve that problem.

Creating a primary name server

Once you create a caching name server, converting it into a full-fledged primary name server is not too difficult. A primary name server is the authoritative source for the domain it represents.

You need to change named.conf again and create a couple new files. You’ll create a domain called Example.com, which is a domain name reserved for example purposes in documents like this one. The Example.com domain does have an IP address on the Internet and a very sparse website, but you can use the name in the rest of your lab project without causing problems for anyone. You’ll use the Example.com domain as the internal domain name for the rest of this exercise.

The two new files you’ll create are the forward and reverse zone files, which you’ll place in the /var/named directory. This location is specified by the “directory” directive in the named.conf configuration file.

Create the forward zone file

The forward zone file contains “A” records that pair the names of the hosts in the zone, aka domain, with their respective IP addresses. It may also contain CNAME records, which are aliases for the real hostnames in the A records, and MX records for mail servers.

Create a basic forward zone file, /var/named/example.com.zone, and add the following lines to it. Your zone file should look like the sample zone file in Figure 6 when you’re finished.

; Authoritative data for example.com zone
;
$TTL 1D
@   IN SOA  epc.example.com   root.epc.example.com. (
                                       2017031301      ; serial
                                       1D              ; refresh
                                       1H              ; retry
                                       1W              ; expire
                                       3H )            ; minimum

$ORIGIN         example.com.
example.com.            IN      NS      epc.example.com.
epc                     IN      A       127.0.0.1
server                  IN      A       192.168.25.1
www                     IN      CNAME   server
mail                    IN      CNAME   server
test1                   IN      A       192.168.25.21
t1                      IN      CNAME   test1
test2                   IN      A       192.168.25.22
test3                   IN      A       192.168.25.23
test4                   IN      A       192.168.25.24

; Mail server MX record
example.com.            IN      MX      10      mail.example.com.

Figure 6: The forward zone file for the Example.com domain contains the hostnames and their IP addresses for this domain.

The first non-comment line in Figure 6 is the Time To Live specifier, which in this case is one day for all records that are not otherwise specified. D stands for Day. The specifiers in the SOA (Start of Authority) line are just as obvious. Details of the parameters in the SOA record are described in some detail here.

The NS record must have the FQDN (Fully Qualified Domain Name) of the host on which you are performing this lab project. There must also be an A record in the file with a valid IP address for the host. In this case, you should use the localhost IP address of 127.0.0.1.

The entries shown above will give you a few hostnames with which to experiment.

Be sure to use today’s date and append a counter starting at 01 for the serial number. The serial number above is the first change of March 4, 2017. The serial number is incremented whenever the zone file is changed. If there were secondary name servers that used this one for a primary, they would not be updated unless the serial number is incremented.

Add the forward zone files to named.conf

Before your DNS server will work, however, you need to create an entry in /etc/named.conf that will point to your new zone file. Add the lines sown in Figure 7 to create the entry for the top-level hints zone but before the “include” lines.

zone "example.com" IN {
        type master;
        file "example.com.zone";
};

Figure 7: Add these lines to the named.conf file to add the Example.com zone file to the resolver configuration.

Now restart named to make these changes take effect. Test your name server by using the dig and nsloookup commands to obtain the IP Addresses for the hosts you have configured in the forward zone file. Note that the host does not have to exist on the network for the dig and nslookup commands to return an IP Address.

# dig test1.example.com
# dig t1.example.com
# dig mx example.com
# dig mail.example.com
# nslookup test3.example.com
# dig www.amazon.com

Be aware that using the FQDN for these commands is necessary except for the nslookup command as long as the domain and search entries of Example.com are provided in the /etc/resolv.conf file. In this case, they probably are not, so just use the FQDNs for all testing in this project.

Using the root name servers

Notice that the root name servers are given as the authoritative servers for the Amazon.com lookup. But remember you’re using the Google public name servers as forwarders. Now comment out the forwarders line in named.conf and restart named. Run the above commands again to compare the results that are returned. The results should look similar to those in Figure 8.

# dig www.amazon.com     

; <<>> DiG 9.18.26 <<>> www.amazon.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61868
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: 77e0056307150851010000006634e53bdde434a5a1bfe961 (good)
;; QUESTION SECTION:
;www.amazon.com.                        IN      A

;; ANSWER SECTION:
www.amazon.com.                      1800    IN      CNAME   tp.47cf2c8c9-frontier.amazon.com.
tp.47cf2c8c9-frontier.amazon.com.      60    IN      CNAME   d3ag4hukkh62yn.cloudfront.net.
d3ag4hukkh62yn.cloudfront.net.         60    IN      A       18.161.75.84

;; Query time: 133 msec
;; SERVER: 192.168.0.52#53(192.168.0.52) (UDP)
;; WHEN: Fri May 03 09:23:07 EDT 2024
;; MSG SIZE  rcvd: 176

Figure 8: The results of a lookup on www.amazon.com have some interesting information including times to live for the various record types.

When I did this, the first call to resolve the external address for Amazon took 133ms while the data was located and returned. Subsequent results to perform the same query was 1ms, which shows the advantage of caching resolver results locally. Notice the numbers 1800, 300, and 60 in the answer section lines. Those are TTL (Times To Live) in seconds. If you perform the lookup multiple times, these numbers will change, showing the amount of time that the records have remaining to live in local cache.

Creating the reverse zone file

A reverse zone for your domain will provide the ability to do reverse lookups. Many organizations do not do these internally, but reverse lookups can be helpful in doing problem determination. Many spam fighting configurations, such as SpamAssassin, look for reverse lookups to verify valid email servers.

Create the reverse zone file, /var/named/example.com.rev and add the contents in Figure 9. Be sure to use an appropriate serial number.

; Authoritative data for example.com  reverse zone
;
$TTL 1D
@   IN SOA  test1.example.com   root.test1.example.com. (
                                        2017031501      ; serial
                                        1D              ; refresh
                                        1H              ; retry
                                        1W              ; expire
                                        3H )            ; minimum

@       IN      NS      epc.example.com.
example.com.    IN      NS      epc.example.com.
1               IN      PTR     mail.example.com.
1               IN      PTR     server.example.com.
21              IN      PTR     test1.example.com.
22              IN      PTR     test2.example.com.
23              IN      PTR     test3.example.com.
24              IN      PTR     test4.example.com

Figure 9: Use this reverse zone file, example.com.rev, for your name server.

You could also name your reverse zone file /var/named/25.168.192.in-addr.arpa, which follows older conventions. You can actually name it anything you want because you will point to it explicitly in the named.conf file, but using one of the two conventions will make it easier for others to follow your work.

Add the reverse zone stanza in Figure 10 to named.conf:

zone    "25.168.192.in-addr.arpa" IN {
       type master;
       file "example.com.rev";
};

Figure 10: Adding this stanza to the named.conf file enables reverse lookups.

Add the stanza in Figure 10 to the /etc/named.conf file to point to the new reverse zone. Now reload named and test your reverse zone using the commands in Figure 11. Your results should look similar to those below.

​# systemctl reload named
# dig -x 192.168.25.23

; <<>> DiG 9.10.4-P6-RedHat-9.10.4-4.P6.fc25 <<>> -x 192.168.25.23
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48607
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;23.25.168.192.in-addr.arpa.    IN      PTR

;; ANSWER SECTION:
23.25.168.192.in-addr.arpa. 86400 IN    PTR     test3.example.com.

;; AUTHORITY SECTION:
25.168.192.in-addr.arpa. 86400  IN      NS      epc.example.com.

;; Query time: 21 msec
;; SERVER: 192.168.0.203#53(192.168.0.203)
;; WHEN: Wed Mar 15 16:18:59 EDT 2017
;; MSG SIZE  rcvd: 112

Figure 11: After restarting named you should see results similar to these when you do a reverse lookup on an IP address in the reverse zone.

Be sure to test some of the other reverse entries in your network and also try the following as well as other reverse lookups you want to experiment with. The -x option means reverse lookup.

# dig -x 192.168.25.23# dig -x 192.168.25.1

Note that not all hosts that have entries in the forward zone need to have entries in the reverse zone, but it does make for more consistent results if they do.

At this point, you have a working name server using BIND. However, external hosts can’t yet use this name server because the firewall should not yet be configured to allow DNS requests.

Configuring IPTables for DNS

You can do this step if you want other hosts on your local network to use your host as their name server. The firewall on your test host probably blocks access to your host for name services. IPTables must be configured to allow UDP (User Datagram Protocol) packets inbound on your name server in order for other hosts to use it for name resolution. Use the following commands to add the required entries and save them.

Add a rule to your firewall with firewalld or IPTables that allows incoming packets on port 53 (domain) for UDP and save the new ruleset.

If you’re still using IPTables, be sure to insert the new rule after the -A INPUT -m state –state RELATED,ESTABLISHED -j ACCEPT line, so you will have to count the number of INPUT lines in the filter table in order to do that. The number 7 in the following command means that this rule will be inserted in position number 7 in the existing INPUT rules.

# iptables -t filter -I INPUT 7 -p udp -m conntrack --ctstate NEW -m udp --dport 53 -j ACCEPT

If you’re using firewalld you should use the following commands to add the new rule to the internal zone and to make it permanent. Firewalld manages the details of connection tracking for us.

# firewall-cmd --add-service=dns --zone=internal --permanent
success
# firewall-cmd --add-service=dns --zone=internal
success
# firewall-cmd --list-services --zone=internal
dhcpv6-client dns mdns ntp ssh
# firewall-cmd --list-services --zone=internal --permanent
dhcpv6-client dns mdns ntp ssh

Test this from one of your other hosts using the command in Figure 12. The @epc argument tells the dig command to use the specified name server with the hostname epc. You should substitute either the IP address of the DNS server you have just created, or a resolvable hostname on your network that points to your new name server. ANother option would be to add that hostname with its IP address to the /etc/hosts file of the host you are using for the remote test.

# dig @epc test1.example.com

; <<>> DiG 9.10.4-P6-RedHat-9.10.4-4.P6.fc25 <<>> @epc test1.example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 27957
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;test1.example.com.             IN      A

;; ANSWER SECTION:
test1.example.com.      86400   IN      A       192.168.25.21

;; AUTHORITY SECTION:
example.com.            86400   IN      NS      epc.both.org.

;; Query time: 0 msec
;; SERVER: 192.168.0.203#53(192.168.0.203)
;; WHEN: Mon Mar 13 08:45:34 EDT 2017
;; MSG SIZE  rcvd: 92

Figure 12: Testing the name resolver you have created from a different host on the same network.

Cleanup

For cleanup, you should perform the following tasks using the tools appropriate for your distribution. However, you may wish to keep this name server for your network if you don’t already have one.

  1. Restore the original /etc/hosts file.
  2. Stop named on the resolver host used for this lab project.
  3. Disable the named service.
  4. Delete the zone files.
  5. Restore the original named.conf file.
  6. Restore the original resolv.conf file.

Final thoughts

The functioning of name services seemed very obscure to me until I actually created a name server for my network using BIND. It is quite straightforward and can improve DNS lookup performance. Having your own name server can also prevent many of the relatively minor yet annoying name service interruptions caused by poorly maintained ISP name servers.

Note that, even though my little EeePC is running with 100% CPU usage for Seti@Home, it responds extremely quickly to resolver requests. You should be able to try this project on any Linux host you have available with minuscule impact. I hope that many of you will try to set up your own name server and experiment with it. The specifics of your name server installation will depend upon the details of your host and network.


Resources


  1. “Primary” and “Secondary” are better terms for server relationships than the racist terms previously used. Many organizations are in the process of making that change but it does take time. ↩︎