Introduction

Dynamic DNS is one of those things that sounds trivial until it breaks your entire remote access setup.

Because #lazy, I was relying on the router’s built-in DDNS (noip.com), which worked… until it didn’t. The DNS record stopped reflecting the actual WAN IP, and suddenly my WireGuard tunnels across sites became unreliable, that and the fact that noip.com services are "freeware" and there is a nuance about monthly validation.

Instead of debugging opaque router behavior, I decided to take control of the problem and build a simple, deterministic solution using deSEC.io and a small Python script.

What it does

Updates the public IP of YOUR.domain.com <example>

  • Uses deSEC.io as the DNS provider
  • Runs independently of the router
  • Only updates DNS when the IP actually changes
  • Is simple, transparent, and easy to debug, including logging

Why deSEC.io

deSEC is a DNS hosting service with:

  • Full API access
  • Token-based authentication
  • Native DynDNS endpoint
  • No dependency on proprietary router integrations
  • German based and friendly with Privacy and Community

Most importantly, it behaves predictably. Which already puts it ahead of half the consumer networking stack.

Architecture

The solution is intentionally simple:

[ *nix Based LXC/VM ]
        │
        ├── Python script (hourly cron)
        │       ├── Get public IP
        │       ├── Compare with last known IP
        │       └── Update deSEC if changed
        │
        └── deSEC DNS
                └── YOUR.domain.com → WAN IP

Remote systems resolve the hostname and connect to hosted services using a DNS name that actually reflects reality.

The Script

The script executes four steps:

  1. Detect current public IPv4
  2. Read previously stored IP from local state
  3. Compare values
  4. Update deSEC only if the IP changed

It also:

  • logs actions
  • handles failures cleanly
  • avoids unnecessary API calls

Repository:

Main Repo

Script Folder + Readme

Installation

1. Create directories

sudo mkdir -p /etc/desec-ddns /var/lib/desec-ddns

2. Store the token

sudo nano /etc/desec-ddns/token

Paste your deSEC token (no quotes). [Create a deSEC Token]

Secure it:

sudo chmod 600 /etc/desec-ddns/token
sudo chown root:root /etc/desec-ddns/token

3. Install the script

sudo cp desec-ddns.py /usr/local/bin/
sudo chmod +x /usr/local/bin/desec-ddns.py

4. Test manually - Optional

sudo /usr/local/bin/desec-ddns.py

Verify:

dig +short YOUR.domain.com
curl -4 -s https://api.ipify.org ; echo

5. Schedule with cron

sudo crontab -e
0 * * * * /usr/local/bin/desec-ddns.py >> /var/log/desec-ddns.log 2>&1

Runs once per hour. More than enough unless your ISP is feeling particularly chaotic.

Key Implementation Details

The script explicitly sends:

myipv6=preserve

This makes sure:

  • IPv6 records are not overwritten
  • Only IPv4 is updated

Without this, you risk unintentionally breaking dual-stack setups.

Why Not Use the Router

  • You don’t control how often it updates
  • You don’t control error handling
  • You don’t get logs
  • When it fails, you guess

This approach gives you:

  • full control
  • visibility
  • reproducibility
  • portability

Lessons Learned

  • “Built-in” features in networking gear are often black boxes
  • DNS consistency is critical for VPN / Services reliability
  • A 100-ish lines script can replace an unreliable vendor feature
  • Owning the logic beats trusting magic

To-Do

  • Monitoring/alerting when DNS drifts from actual IP
  • Extend script to support IPv6 updates if needed
  • Integrate with other homelab services that depend on public reachability

012. Dynamic DNS Python Script -> deSEC.io DNS Service

In this guide, we create, deploy an hourly cron job executing a Python script to update IPV4 to a deSEC.io hosted domain.