Good Ingredients

Bind Primary Nameserver

Recipes > Core Recipes > Bind Primary Nameserver
Pre-Requisites:None
Required Reading:None

In this article (based partly on this one) we'll set up Bind 9 in a chroot to serve as a the primary nameserver for a set of domains.

Whilst there are extensions (such as DLZ) that allow you to run BIND 9 from a MySQL database, for the number of domains I host it is less hassle to use the standard config files. After all, we're not trying to re-create ISPConfig.

Masters and Slaves (Primary and Secondary Nameservers)

There are two types of nameservers: primary nameservers (also called masters) and secondary nameservers. Whilst a small cluster can probably get away with one nameserver to serve DNS entries, most people choose to have at least two. This is useful for handling large amounts of trafic and in case one nameserver goes down. Some sites will have 5 or 6 nameservers or even more. To avoid having to update each nameserver every time there is a change to one of the DNS entries you make all but one of the nameservers secondary nameservers so that they take their configuration from the primary. Slaves can tell when information from a master has changed based on the serial numbers.

In our setup it makes sense to use PLP A as the primary and PLP B as the secondary.

One server can be the Start of Authority (SOA) for one zone, while providing secondary service for another zone, all the while providing caching services for hosts on the local LAN. So as you can imagaine you can have some fairly complex

Installation

Install BIND, and some tools to help with debugging:

$ sudo apt-get install bind9 dnsutils

You could also install bind9-doc if you want the documentation.

If you run netstat you'll see BIND (known as named) is running:

$ sudo netstat -tap | grep named
tcp        0      0 plpa.example.com:domain   *:*                     LISTEN      1028/named
tcp        0      0 localhost.locald:domain *:*                     LISTEN      1028/named
tcp        0      0 localhost.localdoma:953 *:*                     LISTEN      1028/named
tcp6       0      0 [::]:domain             [::]:*                  LISTEN      1028/named
tcp6       0      0 ip6-localhost:953       [::]:*                  LISTEN      1028/named

Before we can put it in a chroot jail we need to shut it down:

$ sudo /etc/init.d/bind9 stop
Stopping domain name service...: bind9 waiting for pid 1028 to die.

Create the chroot directories and devices:

sudo mkdir -p /var/chroot/bind9/{etc,dev,var/cache/bind,var/run/bind/run}
sudo chown -R bind:bind /var/chroot/bind9/var/*
sudo mknod /var/chroot/bind9/dev/null c 1 3
sudo mknod /var/chroot/bind9/dev/random c 1 8
sudo chmod 666 /var/chroot/bind9/dev/{null,random}

Move the BIND configuration directory and add a link so that any future Debian updates will still work:

sudo mv /etc/bind /var/chroot/bind9/etc
sudo ln -s /var/chroot/bind9/etc/bind /etc/bind

Configuring Logging

Even though BIND is now running in a chroot, we still want to see its log events. Create /etc/rsyslog.d/bind-chroot.conf and add the line below (this time you do need to add the $ character too):

$AddUnixListenSocket /var/chroot/bind9/dev/log

Restart syslogd:

$ sudo /etc/init.d/rsyslog restart
Restarting system log daemon: syslogd.

It should automatically create /dev/log inside the chroot:

$ ls -la /var/chroot/bind9/dev/log
srw-rw-rw- 1 root root 0 2009-05-25 14:38 /var/chroot/bind9/dev/log

Starting BIND 9

Edit /etc/default/bind9 and replace this:

OPTIONS="-u bind"

with this:

OPTIONS="-u bind -t /var/chroot/bind9"

This tells BIND to use the chroot. Now start BIND:

$ sudo /etc/init.d/bind9 start
Starting domain name service...: bind.

and make sure it is running on the same ports as before:

$ sudo netstat -tap | grep named
tcp        0      0 plpa.example.com:domain   *:*                     LISTEN      1152/named
tcp        0      0 localhost.locald:domain *:*                     LISTEN      1152/named
tcp        0      0 localhost.localdoma:953 *:*                     LISTEN      1152/named
tcp6       0      0 [::]:domain             [::]:*                  LISTEN      1152/named
tcp6       0      0 ip6-localhost:953       [::]:*                  LISTEN      1152/named

Finally check that the log messages get written to syslog:

$ sudo tail -f -n 40 /var/log/syslog
...
May 25 14:39:53 plpa named[1152]: starting BIND 9.5.1-P1 -u bind -t /var/chroot/bind9
May 25 14:39:53 plpa named[1152]: found 2 CPUs, using 2 worker threads
May 25 14:39:53 plpa named[1152]: using up to 4096 sockets
May 25 14:39:53 plpa named[1152]: loading configuration from '/etc/bind/named.conf'
May 25 14:39:53 plpa named[1152]: max open files (1024) is smaller than max sockets (4096)
May 25 14:39:53 plpa named[1152]: using default UDP/IPv4 port range: [1024, 65535]
May 25 14:39:53 plpa named[1152]: using default UDP/IPv6 port range: [1024, 65535]
May 25 14:39:53 plpa named[1152]: listening on IPv6 interfaces, port 53
May 25 14:39:53 plpa named[1152]: listening on IPv4 interface lo, 127.0.0.1#53
May 25 14:39:53 plpa named[1152]: listening on IPv4 interface venet0:0, 192.168.100.2#53
May 25 14:39:53 plpa named[1152]: automatic empty zone: 254.169.IN-ADDR.ARPA
May 25 14:39:53 plpa named[1152]: automatic empty zone: 2.0.192.IN-ADDR.ARPA
May 25 14:39:53 plpa named[1152]: automatic empty zone: 255.255.255.255.IN-ADDR.ARPA
May 25 14:39:53 plpa named[1152]: automatic empty zone: 0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA
May 25 14:39:53 plpa named[1152]: automatic empty zone: 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA
May 25 14:39:53 plpa named[1152]: automatic empty zone: D.F.IP6.ARPA
May 25 14:39:53 plpa named[1152]: automatic empty zone: 8.E.F.IP6.ARPA
May 25 14:39:53 plpa named[1152]: automatic empty zone: 9.E.F.IP6.ARPA
May 25 14:39:53 plpa named[1152]: automatic empty zone: A.E.F.IP6.ARPA
May 25 14:39:53 plpa named[1152]: automatic empty zone: B.E.F.IP6.ARPA
May 25 14:39:53 plpa named[1152]: command channel listening on 127.0.0.1#953
May 25 14:39:53 plpa named[1152]: command channel listening on ::1#953
May 25 14:39:53 plpa named[1152]: zone 0.in-addr.arpa/IN: loaded serial 1
May 25 14:39:53 plpa named[1152]: zone 127.in-addr.arpa/IN: loaded serial 1
May 25 14:39:53 plpa named[1152]: zone 255.in-addr.arpa/IN: loaded serial 1
May 25 14:39:53 plpa named[1152]: zone localhost/IN: loaded serial 2
May 25 14:39:53 plpa named[1152]: running

That's it. Now you need to set up zones.

Forward DNS, Reverse DNS and Zones

Before I explain how to configure BIND you need to know a bit of theory.

Tip

DNS for rocket scientists is a really excellent resource for learning about BIND and DNS. It contains all the details you need to if you get stuck I'd encourage you to look at it:

As you are no doubt aware, DNS is used to find an IP address from a domain name. This is called a forward DNS lookup and you might be used to configuring A records for this purpose. You can also find out a domain name from an IP address using a reverse DNS lookup. Reverse DNS entries rely on PTR records. For each domain name and IP address you are setting up you need to configure both the forward and reverse DNS entries.

A set of forward DNS entries for a particular domain is called a zone. A set of reverse DNS entries forms a reverse zone. You need a zone and reverse zone for every domain you wish to configure. The existence of the zones is specified in your named.conf.local file but the entries themselves (called resourse records are stored in two files, the forward entries go in db.domain where domain is replaced with the the domain name. For example db.example.com and the reverse DNS entries go into a file named based on the IP address associated with the domain. For example db.192.168.100.in-addr.arpa. For each A record you configure in db.example.com you need to create a PTR record in db.192.168.100.

Tip

Technically you can name the zones anything you like, but the convention above helps to keep everything clear

Any time you make a change to a zone or reverse zone file you need to update its serial number as this is what secondary nameservers use to determine whether they need to update their own configurations.

Configuring BIND

BIND configuration is stored in the config files you've just moved to /var/chroot/bind9/etc/bind but you can access them at /etc/bind because of the symlink you added.

Here are the files and a decription of what they are for:

named.conf

This is the main configuration file for BIND. It imports the options in named.conf.options and named.conf.local. It also sets up zones for localhost, 127.in-addr.arpa, 0.in-addr.arpa, 255.in-addr.arpa and for the root nameservers.

The idea is that you'll edit named.conf.options and named.conf.local to customise BIND and use this file alone

named.conf.options

This file contains settings controlling how the BIND server itself works

named.conf.local

This file contains definitions for all the zones you are setting up.

db.0, db.255, db.local, db.127, db.root

These files all contain the DNS entries used by the zones defined in named.conf. You should leave these files alone. The root servers change over time, so db.root must be maintained now and then but this is usually done automatically as updates to bind.

zones.rfc1918

This contains zone entries for the private networks described in http://www.faqs.org/rfcs/rfc1918.html.

  • 10.0.0.0 - 10.255.255.255 (10/8 prefix)
  • 172.16.0.0 - 172.31.255.255 (172.16/12 prefix)
  • 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)

These zones are not inluded by default because the line is commented out in named.conf.local.

db.empty
This contains empty reverse DNS entries used by the zones defined in zones.rfc1918
rndc.key
A key which allows the rndc commands to work

See also:

Updating named.conf.options

With the comments removed the /etc/bind/named.conf.options file starts out like this:

options {
        directory "/var/cache/bind";
        auth-nxdomain no;    # conform to RFC1035
        listen-on-v6 { any; };
};

It's a good idea to add a couple more options between the curly braces:

version none;
allow-transfer { none; };

The version none; option means BIND won't tell you its version when you ask. This is useful because it prevents a potential attacker easily working out which attack to use.

The allow-transfer { none; }; option prevents other servers from downloading zone information. You'll change this if you set up a slave nameserver.

Tip

BIND is rather fussy when it comes to syntax and won't start if you make a mistake. Every statement must end with a semi-colon which is why there are ; characters before and after the } characters in the options shown above.

Updating named.conf.local

This is the file where you add your zone entries. Let's set up the domains plpa.example.com, plpb.example.com and maila.example.com so that we can test the mail servers properly.

These domains are all part of the example.com zone.

Let's add an entry for example.com /etc/bind/named.conf.local:

zone "example.com" {
    type master;
    file "/etc/bind/db.example.com";
};

Let's also add an entry for the reverse zone:

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

Tip

Notice that we're creating the reverse zone for 122.168.192.in-addr.arpa rather than 2.122.168.192.in-addr.arpa. This allows us to put all the entries for the 192.168.100.*.

Note that if you are using a server where the subnet is owned by the hosting provider it is likely they will set up these reverse DNS entries instead since they will own the subnet. They will probably provide a web based interface to allow you to update the reverse DNS entries or their tech support people should be able to set them up.

Now we need to create the zone files themselves.

A Sample Forward Zone

Here's an example zone file for the example.com domain. I haven't used any of the shortcuts you might have seen so what I've written here is pretty much what you'll get in the Save it as /etc/bind/db.example.com:

$TTL 84924 ; zone default = 1 day or 84924 seconds
$ORIGIN example.com.
example.com.    IN      SOA   ns0.example.com. example.com. (
    2009052300 ; serial number
    3h         ; refresh =  3 hours
    15M        ; update retry = 15 minutes
    1W12h      ; expiry = 1 week + 12 hours
    2h20M      ; minimum = 2 hours + 20 minutes
)
example.com.       84924  IN  NS  ns0.example.com.
example.com.       84924  IN  NS  ns1.example.com.
example.com.       84924  IN  MX  10  plpa.example.com.
example.com.       84924  IN  MX  20  plpb.example.com.
example.com.       84924  IN  A   192.168.100.141
plpa.example.com.  84924  IN  A   192.168.100.2
plpb.example.com.  84924  IN  A   192.168.100.3
maila.example.com. 84924  IN  A   192.168.100.4
ns0.example.com.   84924  IN  A   192.168.100.2
ns1.example.com.   84924  IN  A   192.168.100.3
www.example.com.   84924  IN  A   192.168.100.2 ; Let's use DNS round robin for the www sub-domain
www.example.com.   84924  IN  A   192.168.100.3

For an explanation of these records you'll need to read these two guides:

A Sample Reverse Zone

Now a reverse DNS zone. Save it as /etc/bind/db.2.122.168.192.in-addr.arpa:

$TTL 84924 ; zone default = 1 day or 84924 seconds
$ORIGIN example.com.
2.122.168.192.in-addr.arpa.   IN      SOA   ns0.example.com. example.com. (
    2009052300 ; serial number
    3h         ; refresh =  3 hours
    15M        ; update retry = 15 minutes
    1W12h      ; expiry = 3 weeks + 12 hours
    2h20M      ; minimum = 2 hours + 20 minutes
)
                              IN    NS   ns0.example.com.
                              IN    NS   ns1.example.com.
141.122.168.192.in-addr.arpa. IN   PTR   example.com.
2.122.168.192.in-addr.arpa.   IN   PTR   plpb.example.com.
3.122.168.192.in-addr.arpa.   IN   PTR   plpa.example.com.
4.122.168.192.in-addr.arpa.   IN   PTR   maila.example.com.

Restarting Bind

Once all the changes are made restart bind.

$ sudo /etc/init.d/bind9 restart

Then check there haven't been any problems by looking syslog:

$ sudo tail -f /var/log/syslog

The output should include these lines:

May 25 19:49:57 plpa named[761]: zone 122.168.192.in-addr.arpa/IN: loaded serial 2009052300
May 25 19:49:57 plpa named[761]: zone example.com/IN: loaded serial 2009052300
May 25 19:49:57 plpa named[761]: zone localhost/IN: loaded serial 2
May 25 19:49:57 plpa named[761]: zone example.com/IN: sending notifies (serial 2009052300)
May 25 19:49:57 plpa named[761]: running

Testing DNS

If everything is working you should be able to dig the different domains set up:

$ dig @localhost www.example.com
; <<>> DiG 9.5.1-P1 <<>> @localhost www.example.com
; (1 server found)
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 13439
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 2, ADDITIONAL: 2

;; QUESTION SECTION:
;www.example.com.                   IN      A

;; ANSWER SECTION:
www.example.com.            84924   IN      A       192.168.100.2
www.example.com.            84924   IN      A       192.168.100.3

;; AUTHORITY SECTION:
example.com.                84924   IN      NS      ns0.example.com.
example.com.                84924   IN      NS      ns1.example.com.

;; ADDITIONAL SECTION:
ns0.example.com.            84924   IN      A       192.168.100.2
ns1.example.com.            84924   IN      A       192.168.100.3

;; Query time: 4 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Mon May 25 19:51:05 2009
;; MSG SIZE  rcvd: 131

You can check reverse DNS like this:

$ nslookup 192.168.100.4
Server:             192.168.100.1
Address:            192.168.100.1#53

4.122.168.192.in-addr.arpa      name = maila.example.com.

Setting up Domains with the Nameserver

Certain domain registrars will require that nameservers are registered with the registry before they can be used with their control panels. This means you will need to create what is known as a "Glue Record" (glue records are described here). Technically these are only really needed if the nameserver domain for a domain is under the domain itself because without a glue record specifying the IP address explicitly there would be no way to resolve the nameserver.

Making servers use this DNS server

Once you have made sure BIND is working correctly you change all of the machines on the network to use the new nameserver instead of the current one. This is very useful for testing as it means you can use internal IP addresses (as we've done in this article) and the machines will use these rather than the values live on the internet. You'd probably want to skip this for production use though.

To make the change edit the file /etc/resolv.conf and replace the content with:

nameserver 192.168.100.2

Once this change is made you won't need to add the @localhost to the dig commands because they will use localhost anyway.

Testing Round-Robin

You can now test the DNS round robin for the www subdomain like this:

$ ping www.example.com
PING www.example.com (192.168.100.2) 56(84) bytes of data.
64 bytes from plpa.example.com (192.168.100.2): icmp_seq=1 ttl=64 time=0.075 ms
64 bytes from plpa.example.com (192.168.100.2): icmp_seq=2 ttl=64 time=0.061 ms
^C
--- www.example.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.061/0.068/0.075/0.007 ms
$ ping www.example.com
PING www.example.com (192.168.100.3) 56(84) bytes of data.
64 bytes from plpb.example.com (192.168.100.3): icmp_seq=1 ttl=64 time=0.110 ms
64 bytes from plpb.example.com (192.168.100.3): icmp_seq=2 ttl=64 time=0.085 ms
^C
--- www.example.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.085/0.097/0.110/0.015 ms

Notice that the first ping went to 192.168.100.2 but the second went to 192.168.100.3.

One problem though is what happens if a you need to resolve a domain which isn't one of the domains in the BIND config files? Let's try it:

$ ping google.com
ping: unknown host google.com

The lookup fails. To get this to work you need to set up BIND as a caching nameserver.

(view source)