Dynamic DNS
Servers get static addresses and permanent DNS records. Desktops do not. A desktop or laptop connected to the Core VLAN gets whatever address the DHCP server decides to give it, and that address may change between connections. For most purposes this does not matter: the machine can reach the network regardless of its address, and most traffic originates outbound rather than inbound.
Where it does matter is when you want to reach your desktop by hostname from elsewhere on the network. Logging in via SSH from a server, connecting a remote desktop session, or any other scenario where something else needs to find your machine requires a DNS record that reflects the current address. Dynamic DNS handles this: whenever the desktop’s address changes, it updates its own DNS record automatically.
This page covers two distinct scenarios. The first is internal dynamic DNS, updating your own internal DNS server with the desktop’s current address whenever it connects. The second is external dynamic DNS, used when the machine is roaming outside the home network and needs to be reachable from the internet.
Internal dynamic DNS with nsupdate
nsupdate is a standard DNS utility that sends RFC 2136 dynamic update requests to a DNS server. It is included in the bind9-dnsutils package and is the right tool for updating your own authoritative DNS server with TSIG-authenticated requests.
Prerequisites
Install the utilities package:
sudo apt install bind9-dnsutils
Your internal DNS server must be configured to accept authenticated dynamic updates for your zone. This is covered in the server section of this series. The configuration requires a TSIG key that both the server and the desktop share. The key file is typically /etc/rndc.key on the server.
Copy the TSIG key from your DNS server to the desktop:
sudo scp root@dns.yourdomain.net:/etc/rndc.key /etc/rndc.key
sudo chmod 640 /etc/rndc.key
sudo chown root:root /etc/rndc.key
Test that nsupdate can reach the DNS server and authenticate:
echo "zone yourdomain.net
server 10.1.0.1
update add $(hostname -s).yourdomain.net 300 A $(hostname -I | awk '{print $1}')
send" | sudo nsupdate -k /etc/rndc.key
Verify the record was created:
dig $(hostname -s).yourdomain.net @10.1.0.1 +short
NetworkManager dispatcher script
The same NetworkManager dispatcher mechanism used in previous sections handles this. When a connection comes up, the desktop registers its current address. When it goes down, the record is removed.
Create /etc/NetworkManager/dispatcher.d/30-update-dns:
#!/usr/bin/env bash
#
# Register desktop hostname with internal DNS server via nsupdate.
# Runs when a NetworkManager connection comes up or goes down.
#
INTERFACE=$1
ACTION=$2
# Configuration
DNS_SERVER="10.1.0.1"
DOMAIN="yourdomain.net"
HOSTNAME="$(hostname -s)"
FQDN="${HOSTNAME}.${DOMAIN}"
TTL=300
KEY_FILE="/etc/rndc.key"
LOG_TAG="nm-dns-update"
# Only act on the primary interface
# Adjust this to match your primary wired interface name
PRIMARY_INTERFACE="enp3s0"
if [ "$INTERFACE" != "$PRIMARY_INTERFACE" ]; then
exit 0
fi
# Require the TSIG key file
if [ ! -f "$KEY_FILE" ]; then
logger -t "$LOG_TAG" "TSIG key file not found: $KEY_FILE"
exit 1
fi
case "$ACTION" in
up)
# Get the current IPv4 address for this interface
IPV4=$(nmcli -g IP4.ADDRESS device show "$INTERFACE" 2>/dev/null | \
head -1 | cut -d/ -f1)
if [ -z "$IPV4" ]; then
logger -t "$LOG_TAG" "No IPv4 address found for $INTERFACE"
exit 0
fi
# Get the current IPv6 address for this interface (if any)
IPV6=$(nmcli -g IP6.ADDRESS device show "$INTERFACE" 2>/dev/null | \
grep -v '^fe80' | head -1 | cut -d/ -f1)
# Build the reverse zone name for IPv4
IFS='.' read -r -a OCTETS <<< "$IPV4"
REVERSE_ZONE="${OCTETS[2]}.${OCTETS[1]}.${OCTETS[0]}.in-addr.arpa."
REVERSE_RECORD="${OCTETS[3]}.${REVERSE_ZONE}"
logger -t "$LOG_TAG" "Registering $FQDN -> $IPV4"
# Update forward A record and reverse PTR record
nsupdate -k "$KEY_FILE" << EOF
server $DNS_SERVER
zone $DOMAIN
update delete $FQDN A
update add $FQDN $TTL A $IPV4
send
zone $REVERSE_ZONE
update delete $REVERSE_RECORD PTR
update add $REVERSE_RECORD $TTL PTR $FQDN.
send
EOF
# Update AAAA record if IPv6 is available
if [ -n "$IPV6" ]; then
logger -t "$LOG_TAG" "Registering $FQDN -> $IPV6"
nsupdate -k "$KEY_FILE" << EOF
server $DNS_SERVER
zone $DOMAIN
update delete $FQDN AAAA
update add $FQDN $TTL AAAA $IPV6
send
EOF
fi
;;
down)
logger -t "$LOG_TAG" "Removing DNS records for $FQDN"
nsupdate -k "$KEY_FILE" << EOF
server $DNS_SERVER
zone $DOMAIN
update delete $FQDN A
update delete $FQDN AAAA
send
EOF
;;
esac
Make it executable:
sudo chmod 0744 /etc/NetworkManager/dispatcher.d/30-update-dns
sudo chown root:root /etc/NetworkManager/dispatcher.d/30-update-dns
Update the PRIMARY_INTERFACE, DNS_SERVER, DOMAIN, and KEY_FILE variables to match your setup. Find your primary interface name with ip link show.
Test by bringing the connection down and up:
sudo nmcli con down "Wired connection 1"
sudo nmcli con up "Wired connection 1"
Then verify the record:
dig $(hostname -s).yourdomain.net @10.1.0.1
dig -x $(hostname -I | awk '{print $1}') @10.1.0.1
Multi-site considerations
With three sites in this network, the DNS server address changes depending on which site you are connecting from. The dispatcher script above uses a hardcoded DNS server address, which works if you always connect to the same site or if all sites share a single authoritative DNS server reachable from all three.
If each site has its own DNS server, extend the script to detect which network you are on based on the IP address range received from DHCP, and select the appropriate DNS server accordingly:
# Detect which site we are on based on the IP range
case "$IPV4" in
10.1.*) DNS_SERVER="10.1.0.1" ;; # Burnage Mad House
10.2.*) DNS_SERVER="10.2.0.1" ;; # Fallowfield Asylum
10.3.*) DNS_SERVER="10.3.0.1" ;; # The Lighthouse
*) DNS_SERVER="10.1.0.1" ;; # Default
esac
External dynamic DNS
When the desktop leaves the home network and connects from elsewhere, the internal DNS update is irrelevant. If you need to reach the machine from outside the home network, a different approach is needed.
The cleanest solution is a VPN: connect back to the home network over WireGuard and the machine appears on the internal network as if it were local. VPN configuration is covered in the VPN section of this series. If VPN is in place, external dynamic DNS is generally unnecessary.
If you do need external DNS registration for some reason, the standard approach on Linux is ddclient. It supports a wide range of dynamic DNS providers including Cloudflare, Hurricane Electric, and others.
sudo apt install ddclient
ddclient is configured at /etc/ddclient.conf. The configuration depends on your provider. For a Cloudflare-managed domain:
# ddclient configuration for Cloudflare
protocol=cloudflare
zone=yourdomain.net
ttl=120
login=your@email.com
password=your-cloudflare-api-token
desktop.yourdomain.net
Enable and start the service:
sudo systemctl enable --now ddclient
The more deliberate approach: given that this network already has a VPN connecting all three sites, external dynamic DNS is not needed for this setup. Use the VPN. It is simpler, more secure, and means you do not need to expose the desktop’s address publicly.
Verifying the full setup
After connecting to the home network, verify both directions:
# Forward lookup
dig $(hostname -s).yourdomain.net @10.1.0.1 +short
# Reverse lookup
dig -x $(hostname -I | awk '{print $1}') @10.1.0.1 +short
# Check via local Unbound (should give the same result)
dig $(hostname -s).yourdomain.net +short
Check the system journal for any errors from the dispatcher script:
sudo journalctl -t nm-dns-update
Zones that accept dynamic updates should not be edited manually. The dynamic update mechanism and manual edits conflict, and data can be lost. If you need to make permanent additions to a zone that also accepts dynamic updates, do it through
nsupdaterather than by editing zone files directly.