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 sftdyn
wheel
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 ... error
Install bind9 and nginx:
apt install bind9 dnsutils nginx
DNS 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:
-
A
and/orAAAA
records 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
NS
record 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 bind9
Sftdyn 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/conf
and 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.target
And make sure systemctl
sees everything and runs the service on boot:
systemctl daemon-reload
systemctl enable --now sftdyn.service
NGINX 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 nginx
This 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.