dynamic DNS with bind9, sftdyn and NGINX on a VPS
The goal here is to get an Internet router with dynamic external IP addresses register itself as a host under a specific domain name.
I'm already controlling the lastpixel.tv domain and I want my router to be
resolved from some address like muppet.lastpixel.tv.
The domain itself sits on a server with ns services managed by the server operator. Such operators generally offer online forms and maybe even APIs for updating DNS records, but there's just too much variance from provider to provider, so I'm describing here a more uniform approach.
The idea is to setup a slave DNS to the master domain name server and then setup some automation to ensure the updates.
Prerequisites
- A Linux server with shell access.
- Bind9
- Python3 and sftdyn
- NGINX
- letsencrypt
The Environment
Setup a relatively recent Linux and have a Python 3 environment.
python3 -m venv /opt/sftdyn
. /opt/sftdyn/bin/activate
pip install wheel aiohttp
pip install sftdynwheel might complain about package yarl which is an aiohttp requirement.
There shouldn't be any problem with sftdyn though:
Building wheels for collected packages: yarl
Running setup.py bdist_wheel for yarl ... errorInstall bind9 and nginx:
apt install bind9 dnsutils nginxDNS Configuration
There are too many variables in play in order to configure the dynamic host
directly under the root domain as muppet.lastpixel.tv. I'm just delegating a
subdomain, dynamic.lastpixel.tv, to the server's DNS by adding 2 records:
-
Aand/orAAAArecords towards the server. This is probably achieved via a web based control panel, it should generally look like:ns.lastpixel.tv. IN A 185.250.105.114 ns.lastpixel.tv. IN AAAA 2a06:cd40:400:1:0:0:0:3d1 -
An
NSrecord delegatingdynamic's resolution towardsns.lastpixel.tv:dynamic.lastpixel.tv IN NS ns.lastpixel.tv
It might take a while until the DNS settings propagate, depending on how the server's operator have it set up.
This subdomain is mandatory as only the server's operators' DNS system can
manage lastpixel.tv. The slave server can only manage queries related to a
subdomain in this case.
Bind9 Configuration
Create a zone file for dynamic.lastpixel.tv at
/etc/bind/db.dynamic.lastpixel.tv:
$TTL 86400
@ IN SOA ns.lastpixel.tv. root.dynamic.lastpixel.tv. (
1 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
86400 ) ; Negative Cache TTL
;
@ IN NS ns.lastpixel.tv.and reference it in /etc/bind/named.conf.local:
zone "lastpixel.tv" {
type master;
file "/etc/bind/db.lastpixel.tv";
};
zone "dynamic.lastpixel.tv" IN {
type master;
file "/etc/bind/db.dynamic.lastpixel.tv";
journal "/var/cache/bind/db.dynamic.lastpixel.tv.jnl";
update-policy local;
};Restart Bind9:
systemctl restart bind9Sftdyn Configuration
Grab the sftdyn config from
/opt/sftdyn/lib/python3.7/site-packages/etc/sftdyn/sample.conf:
mkdir /etc/sftdyn
cp /opt/sftdyn/lib/python3.7/site-packages/etc/sftdyn/sample.conf /etc/sftdyn/confand edit the file:
# snip
http = "127.0.0.1:8080"
# snip
clients = {
"register/muppet?secret=mysecret": "muppet.dynamic.lastpixel.tv",
}This assumes sftdyn binds to localhost:8080 only. Feel free to work with a
different port but it's essential that the address is local only for NGINX.
Configure sftdyn as a systemd service:
cp /opt/sftdyn/lib/python3.7/site-packages/usr/lib/systemd/system/sftdyn.service /etc/systemd/system/and tune it up for the virtual environment and bind9 credentials:
[Unit]
Description=sft dynamic dns service
After=network.target
[Service]
User=bind
Group=bind
ExecStart=/opt/sftdyn/bin/python3 -u -m sftdyn -v
Restart=on-failure
[Install]
WantedBy=multi-user.targetAnd make sure systemctl sees everything and runs the service on boot:
systemctl daemon-reload
systemctl enable --now sftdyn.serviceNGINX Configuration
Edit /etc/nginx/sites-enabled/default to contain the following:
server {
# snip
# redirects to local sftdyn instance here:
location /dynamic/ {
proxy_pass http://127.0.0.1:8080/;
include /etc/nginx/proxy_params;
}
# snip
}and restart NGINX:
systemctl restart nginxThis will make calls towards lastpixel.tv/dynamic hit sftdyn directly. The
configured sftdyn URL register/muppet?secret=mysecret is viewed externally as
lastpixel.tv/dynamic/register/muppet?secret=mysecret.
with letsencrypt
This one is straightforward with the letsencrypt and certbot instructions.
It's recommended during the certbot run to completely disable http and select
https only. certbot will edit the NGINX config and have the http schema redirect
to https.
Final Result
I just configure the home router to update its dynamic dns via calls to
https://lastpixel.tv/dynamic/register/muppet?secret=mysecret now.
