Dynamic DNS

Posted on 3 2026

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 nsupdate rather than by editing zone files directly.