DNS with bind9 and DHCP on Ubuntu 16.04

This post will show you how to install DNS with bind9 and DHCP on Ubuntu 16.04. We will end up with a full blown bind9 DNS with an integrated DHCP server. If you are a home user and your network has grown such that you are tired of using all static IP addresses and having to configure the /etc/hosts files by hand, then use the great tool dnsmasq. See my How to set up dnsmasq. Dnsmasq is so much simpler to setup and maintain than bind9, you should think about using it.

DNS with bind9 and DHCP on Ubuntu 16.04 also works on a Raspberry PI 3 running the Ubuntu Image.

If you are determined to use bind9 and isc-dhcp server then carry on. This is what I do.

I start off with a minimal Ubuntu server install. This is all tried and tested out in a test Networking Lab with VirtualBox.

Backup your system before you go any further! Do not do this on a production server.

This is what we will be installing with this howto.

  • DNS Server:
    • Name: lab-dns-dhcp.dragon.lab
    • Server Type: Authoritative.
    • Forward Lookup Zone: dragon.lab.
    • Reverse Lookup Zone: 200.1.10.in-addr.arpa.
  • Network:
    • Subnet:
    • DNS/DHCP Server Address:
    • DNS/DHCP Server Name: lab-dns-dhcp
    • Gateway Address:
    • Broadcast Address:
    • Netmask:
  • DHCP Server:
    • Dynamic Pool: to
    • With Updates to DNS (bind9)

The interfaces file should have something like this:

auto eth0
iface eth0 inet static

Update your system with the latest patches and security fixes.

sudo apt-get update
sudo apt-get dist-upgrade

Let’s pretend it’s Windows and reboot as we installed some updates. That gives you a minute or so to grab a very swift coffee πŸ™‚

sudo reboot

Installing the software is painless both modules should be available from the usual Ubuntu repos.

sudo apt-get install isc-dhcp-server bind9

DNS Configuration

The configuration files for bind9 are cryptic and not particularly intuitive. It is very easy to break a working setup, let alone fail to get it working. You can do this by missing off a single semi-colon or full stop. Talking about full stops, if your system fails to work it is most likely a missing full stop that is stopping it from working. Look at the log output in /var/log/syslog.

The official documentation for bind9 which is rather extensive and very well written, so go there first, there is no need to go anywhere else for help πŸ™‚ Get it from https://www.isc.org it is also installed with the bind9-doc package.

So you insist on following my howto, let us begin. The first configuration file to look at, is one you should not change. Add your changes to the files which are included or to those included further down the branches. Let’s have a look at it anyway.

cd /etc/bind
less /etc/bind/named.conf
// This is the primary configuration file for the BIND DNS server named.
// Please read /usr/share/doc/bind9/README.Debian.gz for information on the 
// structure of BIND configuration files in Debian, *BEFORE* you customize 
// this configuration file.
// If you are just adding zones, please do that in /etc/bind/named.conf.local
include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";
include "/etc/bind/named.conf.default-zones";

Key File

The installation process creates a crypto file needed when our new DHCP server talks back to this DNS server. The command below creates or recreates a file /etc/bind/rndc.key replacing the one generated by the install process, so it’s not strictly necessary to run this at all but it will not do any harm to run it again at this time. The -b 512 means the secret part will be created with 512 bits rather than the default 128 bits. In this case bigger is better.

sudo /usr/sbin/rndc-confgen -a -b 512

It should look something like this, do not include the secret string in any log posts. πŸ™‚ The one here has been mangled from the original. The text in quotes “rncd.key” can be changed to anything you like, within reason.
Just remember that if you do change the text, also update the text where it is accessed in both /etc/bind/named.conf* and /etc/dhcp/dhcpd.conf

key "rndc-key" {
  algorithm hmac-md5;
  secret "kOvv6eBVbAkwHjbBzeC3hezzTQWT4cZ/HellOTHeReThisiSManGledW0f4aGT775Nhb6E4MdzsRX5dZFDhpMQ==";

The file contents should be kept secret. The default permissions are correct, owner:group = bind:bind with permissions of 0640. These settings will not work with DHCP as the owner/group is wrong. If you change the permissions, each time bind is updated you will need to change them back again. It gets annoying, see comments the below. The answer proposed by the upstream developers is that when you are using DHCP with DDNS you copy the rndc.key file from /etc/bind into /etc/dhcp/ddns-keys. The permissions also need to be changed on the new file.

sudo cp /etc/bind/rndc.key /etc/dhcp/ddns-keys
sudo chown root:root /etc/dhcp/ddns-keys/rndc.key
sudo chmod 640 /etc/dhcp/ddns-keys/rndc.key

If you rebuild the file with rndc-confgen, remember to copy it over to ddns-keys again.


To the first config file we will edit.

sudo nano named.conf.options
acl internals {
    // lo adapter;
    // CIDR for your local networks;
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 https://www.kb.cert.org/vuls/id/800113
   // 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 {
       // DNS on the Internet you could also add 
       // the DNS servers from your ISP;
       // Google's DNS
       // Open DNS 
   allow-query {
   allow-query-cache {
   // enables recursive queries but on from our local nets and local hosts
   // Do not allow externals to do recursive queries.
   recursion yes;
   allow-recursion {
   allow-transfer {
   // If BIND logs error messages about the root key being expired,
   // you will need to update your keys.  See https://www.isc.org/bind-keys
   // turn off zone encryption. The auto flag still generates 
   // warnings in the log file
   dnssec-enable no;
   // dnssec-validation auto;
   listen-on-v6 { none; };
   auth-nxdomain no;    # conform to RFC1035

Add the section for “acl internals {}”. This limits access to your server and LAN only when applied to options. It will stop the scum bags in China using your DNS. Not sure why the guys in China are so interested in hacking my servers or using my DNS. You can now use the name internals in the sections for allow-query, allow-query-cache, allow-recursion and allow-transfer. These are now limited to our own internal network, LAN.

The forwarders section, this is where you place the DNS from your ISP. I’m using which in the one on my lab-router. You can also use Google‘s DNS, or OpenDNS, This DNS will use these IP addresses to forward queries it cannot answer directly.

We turn on recursive queries but restrict them to be from our local clients only. Do not allow externals to perform recursive queries on your DNS.
Towards the end you will see the option listen-on-v6 I have set this to none as I do not use IPv6 on my network and there is no need to listen or answer queries for IPV6.

Adding DNS Zones

Let’s get on with defining our first zones. Before that include the rndc.key file created above into the named.conf.local file. We will use it again later. The first zone is the forward looking zone for dragon.lab the second one is the reverse lookup zone definition.

The files which the zones point to can be called anything, make them meaningful. They must be placed in /var/lib/bind and should have permissions 644 and be owned:group by bind:bind. More of that a little later.


sudo nano /etc/bind/named.conf.local
// Do any local configuration here
include "/etc/bind/rndc.key";
zone "dragon.lab" {
     type master;
     file "/var/lib/bind/dragon.lab.zone";
     allow-update { key rndc-key; };
# This is the zone definition for reverse DNS. replace 200.1.10 with your network
# address in reverse notation - e.g my network address is 10.1.200
zone "200.1.10.in-addr.arpa" {
     type master;
     file "/var/lib/bind/dragon.lab.rev.zone";
     allow-update { key rndc-key; };
// Consider adding the 1918 zones here, if they are not used in your
// organization
//include "/etc/bind/zones.rfc1918";

The string rndc-key; must be the same string you used in the rndc.key file as mentioned above. Also take a look at the commented out include file zones.rfc1918. You may decide to use it after amending it to your own environment.

Dummy Zones

I have seen some people add dummy zones like this:

zone "facebook.com" {
  type master;
  file "/var/lib/bind/block-domains";

These zones are designed to stop unwanted advertising, doubleclick.net, or other commonly used junk on web pages like icons to Facebook and Twitter. I believe this type of zone is unwarranted, the user can always install and configure an ad-blocker. It is not up to the network guy to block this type of content.

Configuring the Zones

Zones files go in the directory /var/lib/bind and should be owned:group by root:bind and shoud have permissions of 0775.

sudo nano /var/lib/bind/dragon.lab.zone
$TTL 907200	; 1 week 3 days 12 hours
dragon.lab		IN SOA	ns1.dragon.lab. admin.dragon.lab. (
				2014071403 ; serial
				28800      ; refresh (8 hours)
				3600       ; retry (1 hour)
				604800     ; expire (1 week)
				38400      ; minimum (10 hours 40 minutes)
			NS	ns1.dragon.lab.
$ORIGIN dragon.lab.
lab-router1		A
ns1                     A
lab-dns-dhcp            CNAME   ns1   ; the name of the server we are building

Make sure you get the syntax correct. Note the trailing full stop ‘.’ on the hostname ns1.dragon.lab., (two places) and the email address admin.dragon.lab.. Get these wrong and your DNS will not work.

To fully explain what each bit of this file is doing, read the official documentation it is rather good. A quick run through may help out a little here though.

  • $ORIGIN Sets the domain name that will be appended to any unqualified records.
  • $TTL The time-to-live of the RR field is a 32-bit integer represented in units of seconds. The TTL describes how long a RR can be cached before it should be discarded.
    Rather than using seconds you can add a suffix for

    • m minutes
    • h hours
    • d days
    • w weeks
  • SOA Start of Authority record, contains general administrative and control information about the domain. It has the following format
    • Name dragon.lab
    • Class IN
    • Type SOA
    • Name-Server ns1.dragon.lab. (Note: the trailing full stop)
    • Email-Address admin.dragon.lab. (Note: the @ is replaced with a full stop and there is a trailing one)
    • Serial-No Usually the date as YYYYMMDDSS where SS is a number counting up, used by slave DNS servers.
    • Refresh How often slave DNS server should check back
    • Retry If the above fails retry after this long
    • Expiry Remove data this old when retries fail.
    • Minimum-TTL Time to remember that NXDOMAIN, not found, was returned by the master DNS
  • All the NS, MX, A and CNAME Records for the zone.

The reverse lookup zone file looks like this.

sudo nano /var/lib/bind/dragon.lab.rev.zone

Here is the complete reverse lookup. The in-addr.arpa domain is a legacy domain from the early days of the Internet. We also use PRT records not MX, A or CNAME records here. Watch out for trailing full stops!

$TTL 907200	; 1 week 3 days 12 hours
200.1.10.in-addr.arpa IN SOA	ns1.dragon.lab. admin.dragon.lab. (
				2014071402 ; serial
				28800      ; refresh (8 hours)
				604800     ; retry (1 week)
				604800     ; expire (1 week)
				86400      ; minimum (1 day)
			NS	ns1.dragon.lab.
$ORIGIN 200.1.10.in-addr.arpa.
1			PTR	lab-router1.dragon.lab.
3                       PTR     lab-dns-dhcp.dragon.lab.
			PTR	dragon.lab.

Change the permissions on the two new zone files we created, so they are in the bind group and read and writable by the owner and the group.

sudo chown bind:bind /var/lib/bind/*zone
sudo chmod 664 /var/lib/bind/*zone

If you want to add some more host names with static IP addresses make the entries in /var/lib/bind/dragon.lab.zone, for lookups and /var/lib/bind/dragon.lab.rev.zone for the reverse lookups. Say we have a machine called desktop1 which has an IP address of we can use the following:

desktop1              A

And add desktop1 to the reverse lookup zone file.

20                       PTR     desktop1.dragon.lab.

Check Bind Config Syntax

You can check the syntax of your bind9 config files with the following command. If there are no errors you will see no output and will be returned to a new prompt. If there are errors they will be highlighted. They will give the file and line number where the mistake was detected. If by some chance you have no errors try creating one yourself, just to see the output.

sudo named-checkconf

We can also check the zone files for errors. See the command lines for the forward and reverse zones.

sudo named-checkzone dragon.lab /var/lib/bind/dragon.lab.zone
sudo named-checkzone 200.1.10.in-addr.arpa /var/lib/bind/dragon.lab.rev.zone

We should be able to start or restart our DNS server now. Remember to check the error/warning output in your /var/log/syslog. It will point out where you missed off those pesky full stops. πŸ™‚ Use the tail command below in a new terminal window and the restart in your existing terminal. You can see the log lines as they are written.

sudo tail -fn10 /var/log/syslog
sudo service bind9 restart

If you see any messages like the one below they usually mean you forget to change the permissions above.

managed-keys-zone: journal file is out of date: removing journal file

Now we have a very basic zone file setup. The NIC on the DNS server should be setup to look only at localhost, change the the line for dns-nameservers, the interfaces file should now have something like this in it. Remember we used the Google DNS or OpenDNS in the named.conf.local file as our forwarder so your lab-dns-dhcp server will look there if it cannot find an answer from bind.

auto eth0
iface eth0 inet static

Reload the new settings in the interfaces file by bouncing eth0 or rebooting the server.

sudo ifdown eth0 ; sudo ifup eth0

Now for a quick test to see something is working, lookup the lab-dns-dhcp server.

dig lab-dns-dhcp@dragon.lab
; <<>> DiG 9.9.5-3ubuntu0.5-Ubuntu <<>> lab-dns-dhcp dragon.lab
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49518
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
;lab-dns-dhcp.			IN	A
;; Query time: 1 msec
;; WHEN: Sun Sep 27 11:36:33 BST 2015
;; MSG SIZE  rcvd: 30
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 5992
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
; EDNS: version: 0, flags:; udp: 4096
;dragon.lab.			IN	A
.			1799	IN	SOA	a.root-servers.net. nstld.verisign-grs.com. 2015092700 1800 900 604800 86400
;; Query time: 40 msec
;; WHEN: Sun Sep 27 11:36:33 BST 2015
;; MSG SIZE  rcvd: 114

You can use the following tools to debug your DNS server. The messages in syslog are usually pretty informative.

  • tail -f /var/log/syslog
  • dig [hostname]
  • host [hostname]
  • ping [hostname]

The man pages for all of them are a good source of information and their command line switches.

Let us do a quick check that the reverse lookup is also working. This time lookup the IP address that is my lab-router, you may need to use a different IP depending on what you placed in your dragon.lab.rev.zone file.

dig -x lab-router1
; <<>> DiG 9.9.5-3ubuntu0.5-Ubuntu <<>> -x
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1124
;; flags: qr aa rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; ANSWER SECTION: 0	IN	PTR	lab-router1.dragon.lab.
;; Query time: 2 msec
;; WHEN: Sun Sep 27 11:39:41 BST 2015
;; MSG SIZE  rcvd: 77
Do not continue until your DNS is working.

If all is working then you now has a basic DNS for your local LAN working. now would be a good time to take a backup.

DHCP Configuration

The config for DHCP is all in one file and a little less verbose. Let’s dive right in and edit the file.
You should be able to work out what the options do, if not look in man dhcp.conf

sudo nano /etc/dhcp/dhcpd.conf
ddns-updates on;
ddns-update-style standard;
# This option points to the copy rndc.key we created for bind9.
include "/etc/dhcp/ddns-keys/rndc.key";
allow unknown-clients;
use-host-decl-names on;
default-lease-time 86400; #24  hours
max-lease-time 86400;
log-facility local7;
# dragon.lab DNS zones
zone dragon.lab. {
  primary; # This server is the primary DNS server for the zone
  key rndc-key;       # Use the key we defined earlier for dynamic updates
zone 200.1.10.in-addr.arpa. {
  primary; # This server is the primary reverse DNS for the zone
  key rndc-key;       # Use the key we defined earlier for dynamic updates
# dragon.lab LAN range
subnet netmask {
  option subnet-mask;
  option routers;
  option domain-name-servers;
  option domain-name "dragon.lab";
  ddns-domainname "dragon.lab.";
  ddns-rev-domainname "200.1.10.in-addr.arpa.";
  ## To PXE Boot from another server ##
#  filename "pxelinux.0";
#  next-server 10.1200.5; 
# Static hosts give an IP addr by MAC address
#group {
#  # Fist static host
#  host static1.dragon.lab {
#      hardware ethernet 01:23:45:67:89:ab;
#      fixed-address;
#      ddns-hostname "static1";
#  }

Testing That Lot

Reboot the server to get our services, DNS, DHCP & apparmor, restarted and along with all their dependencies. Take a look at the syslog file for any errors or warnings.

Now boot up another machine which will use the DHCP service we have created. As it boots the output in syslog should look similar to the following:

Jul 20 14:54:18 lab-dns-dhcp dhcpd: DHCPDISCOVER from 08:00:27:d3:5e:8a via eth0
Jul 20 14:54:19 lab-dns-dhcp dhcpd: DHCPOFFER on to 08:00:27:d3:5e:8a (lab-desktop) via eth0
Jul 20 14:54:19 lab-dns-dhcp named[2190]: client rndc-key: signer "rndc-key" approved
Jul 20 14:54:19 lab-dns-dhcp named[2190]: client rndc-key: updating zone 'dragon.lab/IN': adding an RR at 'lab-desktop.dragon.lab' A
Jul 20 14:54:19 lab-dns-dhcp named[2190]: client rndc-key: updating zone 'dragon.lab/IN': adding an RR at 'lab-desktop.dragon.lab' TXT
Jul 20 14:54:19 lab-dns-dhcp dhcpd: DHCPREQUEST for ( from 08:00:27:d3:5e:8a (lab-desktop) via eth0
Jul 20 14:54:19 lab-dns-dhcp dhcpd: DHCPACK on to 08:00:27:d3:5e:8a (lab-desktop) via eth0
Jul 20 14:54:19 lab-dns-dhcp dhcpd: Added new forward map from lab-desktop.dragon.lab. to
Jul 20 14:54:19 lab-dns-dhcp named[2190]: client rndc-key: signer "rndc-key" approved
Jul 20 14:54:19 lab-dns-dhcp named[2190]: client rndc-key: updating zone '200.1.10.in-addr.arpa/IN': deleting rrset at '' PTR
Jul 20 14:54:19 lab-dns-dhcp named[2190]: client rndc-key: updating zone '200.1.10.in-addr.arpa/IN': adding an RR at '' PTR
Jul 20 14:54:19 lab-dns-dhcp dhcpd: Added reverse map from to lab-desktop.dragon.lab.

You should see, in order, DHCPDISCOVER, DHCPOFFER followed by a line saying it is “updating zone ‘dragon…” and then some lines with DHCPREQUEST, DHCPACK and “Added new forward map” If you do, you are all done!

Try using dig to find the IP of your dhcp enabled machine and then do a reverse lookup on that IP. You should see the answer lines similar to these.

lab-desktop.dragon.lab.	3600	IN	A 3600	IN	PTR	lab-desktop.dragon.lab.

If not you missed something out, I found it was usually missing semicolons or permissions not being set correctly or a pesky missing full stop. The syslog is a great help finding the problem.

Testing with nsupdate

You can also add and remove codes from the DNS with nsupdate, here is a quick example. Read through the man page for the command to find out more. After executing the command line you will be given a ‘>’ prompt. While running through the commands also run a tail on your syslog in another terminal to see some extra logging.

nsupdate -k /etc/bind/rndc.key
> server localhost
> zone dragon.lab
> update delete test.dragon.lab. A
> update add test.dragon.lab. 86400 IN A
> show
Outgoing update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:      0
;; flags:; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
;dragon.lab.			IN	SOA
test.dragon.lab.	0	ANY	ANY	
test.dragon.lab.	86400	IN	A

In another window use dig to examine the records for test.dragon.lab. You should see that you get answers for the A record.

> send
> update delete test.dragon.lab. A
> show
Outgoing update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:      0
;; flags:; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
;dragon.lab.			IN	SOA
st.dragon.lab.	0	ANY	ANY	
st.dragon.lab.	0	ANY	A	
> send
> quit

That was rather long. I Bet you are glad you grabbed that swift coffee right at the beginning. πŸ™‚

NTP Server

If you are setting up a DNS and DHCP server you will also need a NTP server to synchronise the clocks on the PC’s connected to your LAN. An NTP server uses very little in resources, it will not need a dedicated machine to run on. We can add one to the DNS/DHCP server. It only takes a few edits from a default install and you will be done.

To install and configure an NTP server follow my HOWTO

Updates To The DNS root Zone

The root name server list is updated occasionally. To keep yours up to date, download the latest with:

sudo mv /etc/bind/db.root /etc/bind/db.root_old
sudo wget -q -O /etc/bind/db.root https://www.internic.net/zones/named.root
sudo service bind9 reload

You might want to set up a crontab job to do this for you, frequently and on a regular basis.

Trouble shooting

If anything is not working check the /var/log/syslog output first. You can also look in the DHCP leases file /var/lib/dhcp/dhcpd.leases.

Unable to add forward map from host.dragon.lab

You may see this when testing a DHCP client. The client will get an IP address but the DNS will not get updated. Check that the zones in dhcp.conf and named.conf.local are the same. In dhcp.conf the name ends in a full stop.

Also check the primary lines in dhcp.conf actually point to your DNS server.

If you see something like “insecurity proof failed”

If you see something like the following in syslog

error (chase DS servers) resolving β€˜dragon.lab/DS/IN’:
named[4321]: error (insecurity proof failed) resolving β€˜com/NS/IN’:

You need to comment out the following line in /etc/bind/named.conf.options, it appears to be set by default.

dnssec-validation auto;

4 thoughts on “DNS with bind9 and DHCP on Ubuntu 16.04

  1. carlos

    DNS Server:
    Name: dns-server.dragon.lab I imagine this is the hostname?

    DNS/DHCP Server Name: lab-dns-dhcp What is this name for?

    Cause you have two different names its confusing me?

    1. Richard Post author

      Ah, you are right lab-dns-dhcp.dragon.lab is the name of the DNS/DHCP server this post creates. Just change all the occurrences of dns-server to lab-dns-dhcp and it should work without the confusion. I have updated the post accordingly. Ta!

  2. Dirk Hedlund

    Nice, concise, well-written article.

    I do have a question, though. The ownership on rndc.key keeps getting reset to root:root when updates are installed. Any idea why? Or more to the point, how do I stop that from happening?

    1. Richard Post author

      Good question and since the weather is grey, cold and damp outside, I decided to fix this. It has also been annoying me too! I found there is a better way. The way I was using was going against the system expected by the developers. Never a good thing to do. If you copy the rndc.key file into /etc/dhcp/ddns-key, there is no need to change the permissions of the bind copy or update the apparmor config for DHCP.
      I have updated the post to reflect the necessary changes.

      I have also added some commands to test the config files for bind9 out before trying to start the service. They make find those missing full stops and semi-colons so much simpler.


Leave a Reply

Your email address will not be published. Required fields are marked *