User Tools

Site Tools


Configuring nsd3 or bind9 as Automatic Nameserver Slaves

We all have various reasons for wanting to set up our own nameservers. Some prefer the control it offers, others do it for experience or fun, and many because BuyVM's free DNS cannot cover their needs, be it SRV or AAAA records or DNSSEC support. This guide is not designed to be a complete “How To Set Up DNSd”, but instead how to get automated replication of all domains working. This configuration is designed to automatically propagate domains added to or deleted from the dns master server to all of its slaves for easier management. The zones will automatically be slaved to the dns master server.

Software Prerequisites:

  • NS master:
    • BIND
    • nginx
    • cron (vixiecron, or equiv)
    • gawk (for gensub)
  • One or more NS slaves:
    • BIND or NSD3
    • curl
    • cron
    • awk

On debian, getting them installed is as simple as:

nsmaster# apt-get install bind9 nginx cron gawk
nsslaves# apt-get install nsd3 curl cron gawk

Master configuration

These scripts make the assumption that only the domains you want to have slaved are in the named.conf.local file. If that's not the case, you should split out those domains into their own config file and include them appropriately. The same should be done if one DNSd is slaving to multiple masters.

This script is used to generate a master domains file whenever a domain is added or removed.



if [ -e "$PAUSE_UPDATES" ] ; then
  exit 0;

awk 'BEGIN{IGNORECASE = 1;} /^\s*zone.*in/{domain = tolower(gensub(/"/,"","g",$2))} /^.*type.*master/{print domain}' "$NAMED_LOCAL" | sort -u > "$MASTER_TMP"

# if tmp is different from master file, update master file.
if ! diff "$MASTER_TMP" "$MASTER_FILE" > /dev/null ; then

rm -f "$MASTER_TMP"

The $MASTER_FILE variable should be changed to match your configuration. If your hostname is different, it should be changed to match.

This script can be set to run at whatever interval you feel is appropriate. The following cron script shows how to run it every 5 minutes.

# m h dom mon dow user  command
3-58/5 * * * * root /etc/bind/

Next, add a vhost to nginx. On debian, this would go in /etc/nginx/sites-available, symlinked into /etc/nginx/sites-enabled/

server {
        listen   80; ## listen for ipv4

        root    /var/www/;

        access_log  /var/log/nginx/;

        location / {
                # these are your two nameservers, only allow them.
                allow slave1.ip.addr.ess;
		allow slave2.ip.addr.ess;
                deny all;
                index bind_master.lst;

The root entry should be set to the same directory as the $MASTER_FILE variable in the first script (without /master_bind.lst), and the allow commands should be set to your slave nameserver IPs.

Finally, update named.conf.options and make sure the following options are set.

acl world {; ::0/0; };
acl slaves { slave1.ip.addr.ess; slave2.ip.addr.ess; };
options {
    allow-transfer { slaves; };
    allow-recursion { none; };
    allow-query { world; }; // OPTIONAL, MAY WANT none;.
    also-notify { slave1.ip.addr.ess; slave2.ip.addr.ess; };

Same thing with slave1/slave2 ip addresses in this file. You might think to put the slaves acl entry in also-notify, but unfortunately, that command cannot accept the same type of list.

Slave configuration

Now we need to set up the slaves to automatically retrieve the zones from the master. If you're using bind9 instead of nsd3 for your slaves, change the directories to /etc/bind instead of /etc/nsd3



# set -x

reload_cfg () {
/etc/init.d/nsd3 reload
#/etc/init.d/bind9 reload

lastupdate="`curl -i -X HEAD --http1.0 \"$FETCHURL\" 2> /dev/null | awk 'BEGIN{stat200=0} /HTTP.*200.*OK/{stat200=1} {if (stat200 != 1) { exit 1 }; } /Last-Modified:/{ gsub(/^Last-Modified:[ \t]*/, \"\"); print}'`"

if [ -z "$lastupdate" ] ; then
  echo "Problem fetching from ns0";
  exit 1;

touch -d "$lastupdate" "$ZONECOMP"

if [ ! -r "$SLAVE_FILE" ] || [ "$ZONECOMP" -nt "$SLAVE_FILE" ] ; then
  if ! curl "$FETCHURL" 2>/dev/null > "$ZONELIST" ; then
    echo "could not fetch zone list";
    exit 2;

  rm -f "$SLAVE_FILE"

  cat "$ZONELIST" | xargs -i@ awk -v zn="@" -f "$ZONEAWK" "$ZONETEMPLATE" >> "$SLAVE_FILE"


  rm -f "$ZONELIST"

rm -f "$ZONECOMP"

In this script, you must change at least $FETCHURL. If you are using bind9, you'll also have to change $NSDIR, $ZONETEMPLATE, and switch the comment in reload_cfg.


{ gsub(/%ZONENAME%/,zn); print }

A simple enough awk file, replace all instances of %ZONENAME% with the argument variable zn.

I set this script to run at the same interval as my master update. The following cron script shows how to run it every 5 minutes, one minute after the master should have run.

# m h dom mon dow user  command
4-59/5 * * * * root /etc/nsd3/

For convenience, if you wish to force a reload from the master, use the following script.



#set -x


touch -d 0 "$ZONEFILE"


The following two files are templates, one for each DNSd type which is generated for each zone that is added. You should update the template appropriately with your nameserver ip addresses, as appropriate.


	name: "%ZONENAME%"
	zonefile: "s/"

	# master 1 -
	allow-notify: ns0.ip.addr.ess NOKEY
	request-xfr: ns0.ip.addr.ess NOKEY

	# master 2 -
	allow-notify: otherslave.ip.addr.ess NOKEY
	request-xfr: otherslave.ip.addr.ess NOKEY

	# Allow AXFR fallback if the master does not support IXFR. Default
	# is yes.
	allow-axfr-fallback: "yes"


zone "%ZONENAME%" IN {
        type slave;
        file "s/%ZONENAME%";
        masters { ns0.ip.addr.ess; };
        allow-update { none; };

If running bind9, you should take the time to make the zone directory and make sure it's writable by bind:

mkdir /var/cache/bind/s
chgrp bind /var/cache/bind/s
chmod 2775 /var/cache/bind/s

It's not strictly necessary to set the directory as group-sticky, but this mirrors how bind's master directory is configured.

For nsd3, at the end of nsd.conf, add a line to add the new domains file.

include: "/etc/nsd3/autozones.conf"

For bind9, add the same line to the end of named.conf.local /etc/bind/named.conf.local:

include: "/etc/bind/autozones.conf

Additionally for bind9, make sure the following options are in named.conf.options.

acl world {; ::0/0; };
acl friends { ns0.ip.addr.ess; otherslave.ip.addr.ess; };
options {
    allow-transfer { friends; };
    allow-recursion { none; };
    allow-query { world; };

Testing your setup

Log back into the DNS master and create a new zone file in the master zone cache. Then add it to /etc/bind/named.conf.local before running rndc reload.

cat > /var/cache/bind/m/ << "EOF"
$TTL 86400      ; 1 day               IN SOA (
                                2013042501 ; serial
                                7200       ; refresh (2 hours)
                                7200       ; retry (2 hours)
                                2419200    ; expire (4 weeks)
                                86400      ; minimum (1 day)
$TTL 14400      ; 4 hours
                        MX      10
ns1                     A
ns2                     A

# Wouldn't want updates to happen while we're writing
touch /etc/bind/pause_updates

cat >> /etc/bind/named.conf.local << "EOF"

zone "" IN {
        type master;
        file "m/";
        allow-update { none; };

rm /etc/bind/pause_updates

rndc reload

Now the zone should autoload on your slave dns servers within a couple minutes without any further configuration.

configure_slave_nameservers.txt · Last modified: 2013/08/16 01:45 by Andrew Domaszek