A simple Debian based DNS server

The setup of my homelab, especially the IPv6-only configuration I’m running right now, requires a DNS server. To me as a Unix guy it was obvious that this basic infrastructure service needs to be deployed in any case. But some discussions on Twitter and especially with William Lam and on his blog indicated that this may not be a no-brainer for those who are VMware followers, but not that familiar with DNS. William pointed out that DNS is not a hard requirement, and I appreciate he takes the time to describe how to run VMware products without a DNS server. I fully trust him that this is possible (if there’s one person I would trust on that than it’s him!). But for many reasons, including the official VMware vSphere documentation, I still suggest to deploy a DNS server even for small homelab or test environments. Particularly if you’re trying to get familiar with IPv6.Of course one could run a DNS service on a Windows system, either Win2kX Server built-in or a freeware like SANS on your Windows PC. But to really get familiar with the inner workings of DNS I would recommend to use bind on a Debian Linux system. So that’s what this post is about.

Basically there’s two ways to do this: either you’re running an all-in-one homelab (on a “fat” PC / Workstation using VMware Workstation or Fusion, with nested ESXi), or you’re one step further and have set up dedicated ESXi server hardware with their own network and storage infrastructure (like I did). In the first case you’d rather deploy a VM and you’re good. In the latter case you may go for a dedicated system to avoid Catch-22 situations (ESXi or vCenter Server VM relying on a DNS server, which is a VM itself). If a VM won’t do the job, a Raspberry Pi (“RPi”) may just be the perfect choice for that purpose. It’s amazingly cheap, it’s power efficient, it runs a Debian release called “Raspbian”, and besides the DNS server role it could do many other fascinating things. You should definitely check it out.
BTW: I’m not using my RPi for these tasks in my homelab since I’m running a purpose-built NAS based on Debian, which can easily take over the DNS, DHCP, TFTP, Kickstart etc roles. But if your NAS is rather proprietary (Synology, Qnap, Open-E – whatever) and cannot host a full-featured DNS server, you may want to go for a Raspberry Pi. Anyway the following tutorial will apply to Debian VMs as well as RPis running Raspbian.

First and most important thing: choose a static IP address for the new server. Check the network address and range used by your lab or home LAN, and the address range used for DHCP. I’ll use the following configuration, which you need to adjust to your setup:

Network 192.168.111.0/24
DHCP range .129 – .190 (.128/26)
Gateway (router) 192.168.111.1
Hostname of the new DNS server labsvc
DNS server IP address 192.168.111.3
Name of the private domain lab.local

I’m not going into the basics on how to install Debian Netinst or Raspbian – there’s lots of real good installation guides available for Debian and Raspbian. You should only make sure you use the static IP, hostname and domain name chosen before.

If you didn’t do this during the OS installation, you need to install bind now:

apt-get install bind9

The configuration files reside in /etc/bind. I’ve written a simple shell script which will modify or create them so that the following host name and IP configuration will be implemented:

Host Hostname IP
DNS server dns 192.168.111.3
ESXi 1 esxi01 192.168.111.11
ESXi 2 esxi02 192.168.111.12
ESXi 3 esxi03 192.168.111.13
ESXi 4 esxi04 192.168.111.14
vCenter Server vcenter 192.168.111.20
vSphere Management Assistant vma 192.168.111.21

Additionally an entry ntp.lab.local will be created pointing to the DNS server, which means we’re able to host a NTP server as well. I’ll get back to that topic at the end of this post.

The IP addresses may not suit your needs. Don’t worry, you can change them later, and I will explain how.

So here’s the script, also as a ZIP file containing Unix and Windows (.txt) file formats: bind_config_scripts

#!/bin/bash
#
# Simple script to generate a basic bind configuration for home/lab use
#

# Local config - adjust as required
OWNIP=192.168.111.3
NETWORK=192.168.111.0
NETMASK=/24
DNS1=192.168.111.1
DNS2=

# Advanced - should not be changed
DOMAIN=lab.local

# Internal - must not be changed
CONFDIR=/etc/bind

# Let's go - make sure we're in the right path
if [[ ! -d "${CONFDIR}" ]]
  then
        echo "ERROR: configuration path ${CONFDIR} does not exist, exiting"
        exit 1
  else
        echo "Configuration path ${CONFDIR}"
        cd $CONFDIR || exit 1
  fi

# Stop bind
echo "Stopping bind9 daemon..."
service bind9 stop

# Remove the root zone servers, we don't want to query these directly
[[ ! -f db.root.original ]] && mv db.root db.root.original
cat > db.root <<- EOF
\$TTL   30d
@       IN      SOA     localhost. root.localhost. (
                          1     ; Serial
                        30d     ; Refresh
                         1d     ; Retry
                        30d     ; Expire
                        30d     ; Negative Cache TTL
                        )
;
@       IN      NS      localhost.
EOF
echo "Created db.root"

# Set bind options and upstream DNS servers
[[ ! -f named.conf.options.original ]] && mv named.conf.options named.conf.options.original
cat > named.conf.options <<- EOF
options {
        directory "/var/cache/bind";
        auth-nxdomain no;
        listen-on { any; };
        listen-on-v6 { any; };
        allow-recursion { 127.0.0.1; ${NETWORK}${NETMASK}; };
EOF
printf "\tforwarders { ${DNS1}" >> named.conf.options
[[ -n "${DNS2}" ]] && printf "; ${DNS2}" >> named.conf.options
printf "; };\n};\n" >> named.conf.options
echo "Created named.conf.options"

# Configure the local domain
[[ ! -f named.conf.local.original ]] && mv named.conf.local named.conf.local.original
REVADDR=$(for FIELD in 3 2 1; do printf "$(echo ${NETWORK} | cut -d '.' -f $FIELD)."; done)
cat > named.conf.local <<- EOF
zone "${DOMAIN}" {
        type master;
        notify no;
        file "${CONFDIR}/db.${DOMAIN}";
};
zone "${REVADDR}in-addr.arpa" {
        type master;
        notify no;
        file "${CONFDIR}/db.${REVADDR}in-addr.arpa";
};
include "${CONFDIR}/zones.rfc1918";
EOF
echo "Created named.conf.local"

# Populate the forward zone
SERIAL="$(date '+%Y%m%d')01"
NET="$(echo ${NETWORK} | cut -d '.' -f 1-3)"
cat > db.${DOMAIN} <<- EOF
\$ORIGIN ${DOMAIN}.
\$TTL   1d
@       IN      SOA     localhost. root.localhost. (
                        ${SERIAL}       ; Serial
                        1d              ; Refresh
                        2h              ; Retry
                        1w              ; Expire
                        2d              ; Negative Cache TTL
                        )
        IN      NS      dns.${DOMAIN}.
;
dns             IN      A       ${OWNIP}
ntp             IN      CNAME   dns.${DOMAIN}.
esxi01          IN      A       ${NET}.11
esxi02          IN      A       ${NET}.12
esxi03          IN      A       ${NET}.13
esxi04          IN      A       ${NET}.14
;
vcenter         IN      A       ${NET}.20
vma             IN      A       ${NET}.21
EOF
echo "Populated forward zone file db.${DOMAIN} for ${DOMAIN}"

# Populate the reverse zone
OWNH="$(echo ${OWNIP} | cut -d '.' -f 4)"
cat > db.${REVADDR}in-addr.arpa <<- EOF
\$ORIGIN ${REVADDR}in-addr.arpa.
\$TTL   1d
@       IN      SOA     localhost. root.localhost. (
                        ${SERIAL}       ; Serial
                        1d              ; Refresh
                        2h              ; Retry
                        1w              ; Expire
                        2d              ; Negative Cache TTL
                        )
        IN      NS      dns.${DOMAIN}.
;
${OWNH} IN      PTR     dns.${DOMAIN}.
;
11      IN      PTR     esxi01.${DOMAIN}.
12      IN      PTR     esxi02.${DOMAIN}.
13      IN      PTR     esxi03.${DOMAIN}.
14      IN      PTR     esxi04.${DOMAIN}.
;
20      IN      PTR     vcenter.${DOMAIN}.
21      IN      PTR     vma.${DOMAIN}.
EOF
echo "Populated reverse zone file db.${REVADDR}in-addr.arpa for ${NET}"

# Enable local DNS server
[[ ! -f /etc/resolv.conf.original ]] && mv /etc/resolv.conf /etc/resolv.conf.original
cat > /etc/resolv.conf <<- EOF
domain ${DOMAIN}
search ${DOMAIN}
nameserver ${OWNIP}
EOF
echo "Enabled local DNS server in /etc/resolv.conf"

# Start bind
echo "Starting bind9 daemon..."
service bind9 start

# Done
echo "Done."

At the beginning of the script some parameters are defined which may be adjusted as needed:

OWNIP IP address of the new DNS server
NETWORK Local network
NETMASK Corresponding netmask, usually /24 = 255.255.255.0
DNS1 and DNS2 The regular or upstream DNS server(s) of the local network, usually the IP of your WAN router if it comes with an integrated DNS proxy. In that case leave the DNS2 value blank. It could also be set to the DNS server IP address(es) provided by your ISP. Well, even if this is a beginner’s guide – you should be able to determine the right values. If not, use Google’s DNS servers 8.8.8.8 and 8.8.4.4.

Transfer the script to your server (1) or copy it to the clipboard.

nano /etc/bind/configure_bind (2)

Paste the clipboard, change if needed, and write out the file.

chmod 700 /etc/bind/configure_bind
/etc/bind/configure_bind

You’ll see some status output. If the configration path /etc/bind doesn’t exist something is fundamentally wrong, like different OS (Red Hat / CentOS is not Debian!) or missing bind installation. Go back to the top of this post. No worries if the line “Stopping domain name service” shows a failure – the script tries to stop an already running bind without any checks, which fails if the daemon is not running. If everything else goes well you’ll see something like this:

Configuration path /etc/bind
Stopping bind9 daemon...
[....] Stopping domain name service...: bind9waiting for pid 4242 to die
. ok
Created db.root
Created named.conf.options
Created named.conf.local
Populated forward zone file db.lab.local for lab.local
Populated reverse zone file db.111.168.192.in-addr.arpa for 192.168.111
Enabled local DNS server in /etc/resolv.conf
Starting bind9 daemon...
[ ok ] Starting domain name service...: bind9.
Done.

Now check if everything is working as expected using a host lookup with the command host (or use nslookup instead):

root@labsvc:/etc/bind# host esxi01
esxi01.lab.local has address 192.168.111.11

root@labsvc:/etc/bind# host 192.168.111.20
20.111.168.192.in-addr.arpa domain name pointer vcenter.lab.local.

root@labsvc:/etc/bind# host vmware.com
vmware.com has address 208.91.0.132

Got the same results? Congratulations! Your own private DNS server is up & running.

If the last command failed, you need to check the configuration for your upstream DNS server. The backup file /etc/resolv.conf.original created by the script may be a good place to start since it contains the old settings, most likely retrieved via DHCP. Adjust the “forwarders” line in named.conf.local accordingly. Make sure you don’t forget / delete the semicolon after the IP address(es). If at least your basic network setup is fine, the following line should provide a working global name resolution:

forwarders { 8.8.8.8; };

Restart the bind daemon after changing the file:

service bind9 restart

If one of the first two commands failed, you need to check the local name resolution setup. Debian will use the /etc/resolv.conf file that the script created – unless you install the resolvconf package (don’t!).

Back to the likely case that my basic configuration is not what you need for your lab setup. I assume that you adjusted the configurable parameters at the top of the script as desired. If you’d like to use a different domain name, you could change the the DOMAIN parameter in the script where it says “should not be changed”. 😉 But DO NOT use any official domain name (which means your personal / mail domain or the like) unless you exactly know what you’re doing – and this isn’t the case if you find this tutorial helpful. Stick to “something.local” to be on the safe side. It’s perfectly OK to change the script and run it again. You may just end up with some obsolete files (for old IP ranges or domain names) which you should delete manually. They won’t do any harm besides messing up the directory.

The list of hostnames and IP addresses are stored in two separate files, the forward (hostname -> IP address) lookup zone file db.lab.local and the reverse (IP -> hostname) lookup zone file db.111.168.192.in-addr.arpa – if you kept the defaults, otherwise check the script output. You always need to change both files consistently. Just have a look at the initial configuration after you ran the script and you’ll get the idea.

So if you for example want to name your vCenter Server “vcsa01.lab.local” with IP address 192.168.111.254, you would do the following changes:

In the forward file /etc/bind/db.lab.local change

vcenter         IN      A       192.168.111.20

to

vcsa01          IN      A       192.168.111.254

and in the reverse file /etc/bind/db.111.168.192.in-addr.arpa change

20      IN      PTR     vcenter.lab.local.

to

254     IN      PTR     vcsa01.lab.local.

Again: keep these two files consistent! Really nasty things may happen if you don’t, and bind itself will not warn you about it. It helps a lot to keep the files sorted by IP address.
In this setup it is not required to update the Serial number in the file headers (unless you have other DNS server connecting to this one), but since it is required in more complex real world setups you should try to make this a habit. The serial has the format YYYYDDMMnn, where nn can be incremented if you do several changes on the same day. Remember to restart the bind daemon (see above) after you changed the files.

Well… that’s it. Quite simple, right?

Now for the bonus NTP server. Personally I consider it to be much more important than just a bonus and a working local NTP setup to be as essential as the DNS. It’s a mystery to me that a lot of router and NAS vendors implement a NTP client but not a server option, especially if the device is Linux-based anyway. Maybe because the internal clock is crappy, which seems to be the case at least with Synology devices.
Fortunately it just takes a minute to install the additional NTP daemon:

apt-get install ntp

The configuration file /etc/ntp.conf is pre-configured with servers from a special Debian related pool at ntp.org. You could change these to the NTP servers of your ISP or to the geographically nearest pool of ntp.org servers and restart the ntp service afterwards. Finally check if your own server found suitable masters from the pool:

root@labsvc:~# ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
*ntp1.m-online.n 212.18.1.106     2 u   36   64  377   40.392   26.475  12.462
+thw23.de        52.239.121.49    3 u   34   64  377   31.220   25.888  12.223
+212.227.158.163 131.188.3.222    2 u   37   64  377   39.622   22.705  12.540
+quartus.dv-team 192.53.103.104   2 u   69   64  377   48.368   27.362  23.254

If you see a star and plus signs at the beginning of each line in the table everything works fine. From now on you can configure your hosts, servers and VMs to use ntp.lab.local as a time server.

If you run into any problems please let me – and other readers – know.

 

(1) You should install PuTTY on your Windows PC, in case you never used a Windows SSH client. Install the complete package and use pscp to copy the file, or install putty.exe only and start a SSH session to use copy & paste into the editor later.

(2) If your familiar with Linux you’ll use vi as the editor, like I do for years and still prefer. But in that case you won’t be reading this tutorial anyway, I suppose. So, to make life easier, I’ll use the more user-friendly “nano” editor. You may want to read through this or this documentation to get familiar with it.

One thought on “A simple Debian based DNS server

Leave a Reply

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

*

code