GUIDE: A DIY dynamic DNS server for Linux with BIND
With last month seeing a 100% increase in the number of computers I own, I found it useful to be able to determine the IP address of my various devices, with a lightweight Linux-compatible solution. My initial solution was to use Syncthing and interface with its dynamic IP address detection functionality, but this proved too slow and unreliable to be useful.
Many providers offer free dynamic DNS services, but the smallest TTL supported is around 30 seconds: 30 seconds too long for my liking. With my own VPS and domain name, this seemed like a good opportunity to finally get around to setting up a DNS server. As it turns out, this is well-supported with the popular DNS server BIND.
Guide
-
Install bind. On Arch Linux, this is accomplished by a simple
pacman -S bind
. -
Set up a zone file for the domain. I wished to delegate only a particular subdomain to my dynamic DNS server, leaving the remainder of the domains to the professionals, so I created
/var/named/dynamic.example.com.zone
with contents:
$TTL 2 ; 2 seconds
dynamic.example.com. IN SOA ns1.example.com. postmaster.example.com. (
2017010101 ; serial
28800 ; refresh (8 hours)
1800 ; retry (30 minutes)
604800 ; expire (1 week)
86400 ; minimum (1 day)
)
NS ns1.example.com.
I have set a TTL of 2 seconds, as Google Public DNS reduces the TTL by 1 second when it caches the result (giving a TTL of 1), and a 0 TTL causes some DNS servers to cache the result forever: not what we want.
- Enable the new zone by adding to
/etc/named.conf
:
zone "dynamic.example.com" IN {
type master;
file "dynamic.example.com.zone";
allow-update { 127.0.0.1; };
notify no;
};
-
Enable and start the DNS server. On Arch Linux, this is accomplished by
systemctl enable named
andsystemctl start named
. -
Set the server as the DNS server for the (sub)domain. On Namecheap, this was accomplished by adding an
NS
record fordynamic
pointing tons1.example.com
, and adding anA
record forns1
pointing to the IP address of the server. -
If all goes well,
dig dynamic.example.com NS
should return your DNS server. -
Now run
nsupdate
, typeserver 127.0.0.1
followed by enter, then typeupdate add test.dynamic.example.com. 2 A 1.2.3.4
followed by two enters. -
If all goes well, the command should exit without errors, and
dig test.dynamic.example.com
should return1.2.3.4
with a TTL of 1 or 2! -
Now all that remains is to allow the DNS records to be updated from the web. I cooked up a simple PHP script:
<?php
header('Content-Type: text/plain');
$subdomain = $_GET['subdomain'];
$address = $_GET['address'];
$descriptorspec = array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w')
);
$process = proc_open('nsupdate', $descriptorspec, $pipes, NULL, NULL);
fwrite($pipes[0], "server 127.0.0.1\n");
fwrite($pipes[0], "update delete $subdomain.dynamic.example.com. A\n");
fwrite($pipes[0], "update add $subdomain.dynamic.example.com. 2 A $address\n\n");
fclose($pipes[0]);
echo stream_get_contents($pipes[1]);
proc_close($process);
?>
Access to the script should be controlled, for example by a .htaccess file.
-
Call the script:
curl -v -u username:password 'https://example.com/dns.php?subdomain=test&address=8.8.8.8'
-
If all goes well, no error should be reported, and
dig test.dynamic.example.com
should now return8.8.8.8
! -
Automatic updating of the IP address can be facilitated by a NetworkManager script. Create executable
/etc/NetworkManager/dispatcher.d/dns.sh
with contents:
#!/bin/bash
IF=$1
STATUS=$2
if [ "$IF" == "eno1" ]; then
case "$STATUS" in
up)
logger -s 'Connected to ethernet, updating DNS'
IP=$(ip addr show dev $IF | grep 'inet ' | awk '{split($2,a,"/");print a[1];}')
curl -v -u username:password "https://example.com/dns.php?subdomain=test&address=$IP"
;;
*)
;;
esac
fi
Replace eno1
with the desired network inferface, and replace test
with the desired subdomain.
- Now, whenever the network interface is connected, the device should be reachable at
test.dynamic.example.com
.