Configure Bind DNS Servers with Failover and Dynamic Updates on CentOS 7

We are going to set up a DNS failover using Master/Slave configuration and configure dynamic updates.

This article is part of the Homelab Project with KVM, Katello and Puppet series.

Homelab

We have two CentOS 7 (minimal) servers installed which we want to configure as follows:

admin1.hl.local (10.11.1.2) – will be configured as a DNS master server
admin2.hl.local (10.11.1.3) – will be configured as a DNS slave server

Both servers have SELinux set to enforcing mode.

See the image below to identify the homelab part this article applies to.

Software

Software used in this article:

  1. CentOS 7
  2. Bind 9.9

Configure Master DNS Server

Installation and Firewall

Install packages and ensure that the service is enabled:

[admin1]# yum install bind bind-utils
[admin1]# systemctl enable named

Configure firewall to allow inbount DNS traffic (we use iptables):

[admin1]# iptables -A INPUT -s 10.11.1.0/24 -p tcp -m state --state NEW --dport 53 -j ACCEPT
[admin1]# iptables -A INPUT -s 10.11.1.0/24 -p udp -m state --state NEW --dport 53 -j ACCEPT

Do the following if you use firewalld:

[admin1]# firewall-cmd --add-service=dns
[admin1]# firewall-cmd --add-service=dns --permanent

Logs Directory

Configure a custom logs directory:

[admin1]# mkdir -m0700 /var/log/named
[admin1]# chown named:named /var/log/named

RNDC Key Configuration

Do automatic rndc configuration, and use an authentication key of 512 bits. Note that the default key name is rndc-key.

[admin1]# rndc-confgen -a -b 512 -r /dev/urandom 
wrote key file "/etc/rndc.key"

Harden file ownership and permissions:

[admin1]# chown root:named /etc/rndc.key
[admin1]# chmod 0640 /etc/rndc.key

Master named.conf Configuration and Internal Zones

The content of the master configuration file /etc/named.conf can be seen below.

Note how the internal zone updates are only allowed for the servers that know the key.

include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";
include "/etc/rndc.key";

# Allow rndc management
controls {
	inet 127.0.0.1 port 953 allow { 127.0.0.1; } keys { "rndc-key"; };
};

# Limit access to local network and homelab LAN
acl "clients" {
	127.0.0.0/8;
	10.11.1.0/24;
};

options {
	listen-on port 53 { 127.0.0.1; 10.11.1.2; }; ## MASTER
	listen-on-v6 { none; };
	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";

	tcp-clients 50;

	# Disable built-in server information zones
	version none;
	hostname none;
	server-id none;

	recursion yes;
	recursive-clients 50;
	allow-recursion { clients; };
	allow-query { clients; };
	allow-transfer { localhost; 10.11.1.3; }; ## SLAVE

	auth-nxdomain no;
	notify no;
	dnssec-enable yes;
	dnssec-validation auto;
	dnssec-lookaside auto;

	bindkeys-file "/etc/named.iscdlv.key";
	managed-keys-directory "/var/named/dynamic";
	pid-file "/run/named/named.pid";
	session-keyfile "/run/named/session.key";
};

# Specifications of what to log, and where the log messages are sent
logging {
	channel "common_log" {
		file "/var/log/named/named.log" versions 10 size 5m;
		severity dynamic;
		print-category yes;
		print-severity yes;
		print-time yes;
	};
	category default { "common_log"; };
	category general { "common_log"; };
	category queries { "common_log"; };
	category client { "common_log"; };
	category security { "common_log"; };
	category query-errors { "common_log"; };
	category lame-servers { null; };
};

zone "." IN {
	type hint;
	file "named.ca";
};

# Internal zone definitions
zone "hl.local" {
	type master;
	file "data/db.hl.local";
	allow-update { key rndc-key; };
	notify yes;
};

zone "1.11.10.in-addr.arpa" {
	type master;
	file "data/db.1.11.10";
	allow-update { key rndc-key; };
	notify yes;
};

The content of the internal zone file /var/named/data/db.hl.local:

$TTL 86400	; 1 day
@			IN SOA	dns1.hl.local. root.hl.local. (
				2018010700 ; Serial
				3600       ; Refresh (1 hour)
				3600       ; Retry (1 hour)
				604800     ; Expire (1 week)
				3600       ; Minimum (1 hour)
)
@		NS	dns1.hl.local.
@		NS	dns2.hl.local.
@		A	10.11.1.2
@		A	10.11.1.3
dns1		A	10.11.1.2
dns2		A	10.11.1.3
admin1		A	10.11.1.2
admin2		A	10.11.1.3
katello		A	10.11.1.4
mikrotik	A	10.11.1.1
pve		A	10.11.1.5

The content of the internal reverse zone file /var/named/data/db.1.11.10:

$TTL 86400	; 1 day
@			IN SOA	dns1.hl.local. root.hl.local. (
				2018010700 ; Serial
				3600       ; Refresh (1 hour)
				3600       ; Retry (1 hour)
				604800     ; Expire (1 week)
				3600       ; Minimum (1 hour)
)
@		NS	dns1.hl.local.
@		NS	dns2.hl.local.
@		PTR	hl.local.
dns1		A	10.11.1.2
dns2		A	10.11.1.3
2		PTR	dns1.hl.local.
3		PTR	dns2.hl.local.

1		PTR	mikrotik.hl.local.
2		PTR	admin1.hl.local.
3		PTR	admin2.hl.local.
4		PTR	katello.hl.local.
5		PTR	pve.hl.local.

Ensure that file ownership is sane and SELinux file context applied.

[admin1]# chown named:named /var/named/data/db.hl.local /var/named/data/db.1.11.10
[admin1]# semanage fcontext -a -t named_zone_t /var/named/data/db.hl.local
[admin1]# semanage fcontext -a -t named_zone_t /var/named/data/db.1.11.10
[admin1]# restorecon -Rv /var/named/

Allow named to write master zones:

[admin1]# setsebool -P named_write_master_zones=1

Checks the syntax of the master configuration file:

[admin1]# named-checkconf /etc/named.conf

Check zone files:

[admin1]# named-checkzone hl.local /var/named/data/db.hl.local
zone hl.local/IN: loaded serial 2018010700
OK
[admin1]# named-checkzone hl.local /var/named/data/db.1.11.10 
zone hl.local/IN: loaded serial 2018010700
OK

The content of /etc/resolv.conf can be seen below:

nameserver 10.11.1.2

Restart the service:

[admin1]# systemctl restart named

Display status of the server:

[admin1]# rndc status
version: 9.9.4-RedHat-9.9.4-51.el7 (version.bind/txt/ch disabled) 
CPUs found: 1
worker threads: 1
UDP listeners per interface: 1
number of zones: 103
debug level: 0
xfers running: 0
xfers deferred: 0
soa queries in progress: 0
query logging is ON
recursive clients: 0/0/50
tcp clients: 0/50
server is up and runnin

Dig the zone:

[admin1]# dig @10.11.1.2 ns hl.local

; <<>> DiG 9.9.4-RedHat-9.9.4-51.el7 <<>> @10.11.1.2 ns hl.local
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22854
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 3

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;hl.local.			IN	NS

;; ANSWER SECTION:
hl.local.		86400	IN	NS	dns2.hl.local.
hl.local.		86400	IN	NS	dns1.hl.local.

;; ADDITIONAL SECTION:
dns1.hl.local.		86400	IN	A	10.11.1.2
dns2.hl.local.		86400	IN	A	10.11.1.3

;; Query time: 0 msec
;; SERVER: 10.11.1.2#53(10.11.1.2)
;; WHEN: Sun Jan 07 16:20:28 GMT 2018
;; MSG SIZE  rcvd: 107

Configure Slave DNS Server

Installation and Firewall

This part is the same as for the master server. Install packages:

[admin2]# yum install bind bind-utils
[admin2]# systemctl enable named

Configure firewall to allow inbount DNS traffic (we use iptables):

[admin2]# iptables -A INPUT -s 10.11.1.0/24 -p tcp -m state --state NEW --dport 53 -j ACCEPT
[admin2]# iptables -A INPUT -s 10.11.1.0/24 -p udp -m state --state NEW --dport 53 -j ACCEPT

Logs Directory

Configure a custom logs directory:

[admin2]# mkdir -m0700 /var/log/named
[admin2]# chown named:named /var/log/named

Slave named.conf Configuration

The content of the slave configuration file /etc/named.conf can be seen below.

include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";

acl "clients" {
	127.0.0.0/8;
	10.11.1.0/24;
};

options {
	listen-on port 53 { 127.0.0.1; 10.11.1.3; }; ## SLAVE
	listen-on-v6 { none; };
	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";

	tcp-clients 50;

	# Disable built-in server information zones
	version none;
	hostname none;
	server-id none;

	recursion yes;
	recursive-clients 50;
	allow-recursion { clients; };
	allow-query { clients; };
	allow-transfer { none; };

	auth-nxdomain no;
	notify no;
	dnssec-enable yes;
	dnssec-validation auto;
	dnssec-lookaside auto;

	bindkeys-file "/etc/named.iscdlv.key";
	managed-keys-directory "/var/named/dynamic";
	pid-file "/run/named/named.pid";
	session-keyfile "/run/named/session.key";
};

# Specifications of what to log, and where the log messages are sent
logging {
	channel "common_log" {
		file "/var/log/named/named.log" versions 10 size 5m;
		severity dynamic;
		print-category yes;
		print-severity yes;
		print-time yes;
	};
	category default { "common_log"; };
	category general { "common_log"; };
	category queries { "common_log"; };
	category client { "common_log"; };
	category security { "common_log"; };
	category query-errors { "common_log"; };
	category lame-servers { null; };
};

zone "." IN {
	type hint;
	file "named.ca";
};

# Internal zone definitions
zone "hl.local" {
	type slave;
	file "data/db.hl.local";
	masters { 10.11.1.2; };
	allow-notify { 10.11.1.2; };
};

zone "1.11.10.in-addr.arpa" {
	type slave;
	file "data/db.1.11.10";
	masters { 10.11.1.2; };
	allow-notify { 10.11.1.2; };
};

Checks the syntax of the slave configuration file:

[admin2]# named-checkconf /etc/named.conf

The content of /etc/resolv.conf can be seen below:

nameserver 10.11.1.3
nameserver 10.11.1.2

Allow named to write master zones:

[admin2]# setsebool -P named_write_master_zones=1

Restart the service:

[admin2]# systemctl restart named

Dig the zone:

[admin2]# dig @10.11.1.3 ns hl.local

; <<>> DiG 9.9.4-RedHat-9.9.4-51.el7 <<>> @10.11.1.3 ns hl.local
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37134
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 3

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;hl.local.			IN	NS

;; ANSWER SECTION:
hl.local.		86400	IN	NS	dns2.hl.local.
hl.local.		86400	IN	NS	dns1.hl.local.

;; ADDITIONAL SECTION:
dns1.hl.local.		86400	IN	A	10.11.1.2
dns2.hl.local.		86400	IN	A	10.11.1.3

;; Query time: 0 msec
;; SERVER: 10.11.1.3#53(10.11.1.3)
;; WHEN: Sun Jan 07 16:20:07 GMT 2018
;; MSG SIZE  rcvd: 107

How to Edit Dynamic DNS

Dynamic DNS editor, nsupdate, is used to make edits on a dynamic DNS without the need to edit zone files and restart the DNS server. Because we have declared a zone dynamic, this is the way that we should be making edits.

For example, to delete all records of any type attached to a domain name, we can do:

# nsupdate -k /etc/rndc.key
> update delete example.hl.local
> send
> quit

Note that rndc won’t allow us to reload a dynamic zone:

# rndc reload hl.local
rndc: 'reload' failed: dynamic zone

To do that, we need to temporarily stop allowing dynamic updates:

# rndc freeze hl.local

Now we can edit the zone file if required. When done, we can allow dynamic updates again:

# rndc reload hl.local
# rndc thaw hl.local

15 thoughts on “Configure Bind DNS Servers with Failover and Dynamic Updates on CentOS 7

  1. Hello,

    Thanks for the great guide! I have a question though.
    Is the assumption here that the servers have two nics? (One NAT and the other one in the 10.11.1.0 range?)
    If so, is there any configuring involved to only let the service be active for a particular interface?

    Thanks!

    • All servers have one NIC and are one the same LAN 10.11.1.0/24.

      If you have multiple NICs and multiple IPs, then you can bind services on specific IPs that you need them listening on.

    • Thanks for the quick answer.
      I’m asking because I’m using my own computer with virt-manager and thus using a virtual network. Should I just create a virtual (isolated) network and put all the servers in there? Or, coming back to the first question, give them each 2 nics, one NAT for internet access and one for the 10.11.1.0 LAN?

    • I have some KVM hosts that I manage with virt-manager/virsh, but they all are on a bridged network (standard libvirt installation provides NAT based connectivity – I don’t use that).

      I’m not sure I understand what you want to achieve here. You can use 2 NICs if you want to, and then you can bind services to specific IPs if you want them isolated.

    • If I just bridge those to my home network, wouldn’t I get issues with the DHCP service colliding on my home router and the one I’m configuring here?

    • You can have more than one DHCP server issuing the same range of network addresses out to your clients. When a client broadcasts a discovery request, the first DHCP server to respond with an IP offer is used.

      Your home router will have a pool of addresses that it can issue to clients. If you have more than one DHCP server offering addresses to the same subnet, then they should have different IP pools (or ranges) that don’t overlap, e.g. 10.11.1.40-10.11.1.59 and 10.11.1.60-10.11.1.90.

    • Sorry for the late response. Only now found the time to continue this project. I understand now and will go ahead to try this. Thank you for the help!

  2. Bonjour,

    Merci pour votre article.

    Je me trompe peut-être, mais l’idée d’une IP Failover n’est pas qu’un slave bascule en master en cas de panne de ce dernier ? En quoi la configuration présentée ici permet l’IP Failover ? C’est uniquement la configuration d’un DNS secondaire.

    Merci pour vos éclaircissements

    • Hi Tarwan, perhaps failover isn’t the best word to describe it. Master-slave replication would be more appropriate. You still benefit from higher availability because if your master is down, the slave has all the records and can provide the service. I hope this clarifies things.

  3. Hello and tks. for a great resource!

    For starters, please take my question with a grain of salt, I’m at the beginning with iptables…

    Can you, please, explain, why you only mention the NEW ip_tables ACCEPT INPUT chain entries for port 53?
    What about the continuation of the session? Am I missing something here?

    • Hi, thanks. That’s a good question. In most cases you almost always have a rule at the end of your iptables ruleset to allow all related and established traffic, before you reject or drop everyhing else.

      For example, you will normally see the following entries:

      -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
      -A INPUT -j REJECT --reject-with icmp-port-unreachable

      With this in mind, creating rules that allow NEW sessions is sufficient.

  4. Hello,

    Thank you for this write up and it has been very helpful. I am trying to set up DHCP server with Dynamic DNS with the config above and cannot get the db.h1.local file to dynamically update when DHCP gives out an IP lease.

Leave a Reply

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