Not affiliated with or endorsed by Technitium.
Dynamic DNS updater for Technitium DNS Server.
Reads standard BIND 9 zone files and pushes records to Technitium via its HTTP API. Designed for self-hosted DNS on a home server with a dynamic IP. Supports split-horizon DNS so internal clients get LAN addresses and external clients get WAN addresses automatically.
- BIND 9 zone file format — works with existing zone files
- Supports A, AAAA, CAA, CNAME, MX, NS, PTR, SOA, SRV, TXT
- Technitium APP records with
$APPdirective for any installed app - Split Horizon shorthand —
true/falsegenerates the right JSON automatically ${WAN}and${LAN}placeholders substituted at runtime$ORIGINwith relative names (no trailing dot appended to current origin)- Multi-line records with
( ), multi-part TXT strings, full escape sequence support serial auto— lets Technitium manage SOA serial with its date scheme--dry-run— prints resolved FQDNs and real IPs, no API calls made- Zones auto-created if missing
- Automatic WAN IP detection and bogon address validation
- Logs to terminal when interactive, syslog when run from cron/systemd
- Optional FreeDNS update support
Split Horizon — install from the Technitium app store to use APP true/false
records. Returns ${LAN} to private clients and ${WAN} to public clients.
WildIp — install from the Technitium app store. Resolves the IP encoded
in the label itself, e.g. 1.2.3.4.wildip.example.com → A 1.2.3.4.
FreeDNS (freedns.afraid.org) — free dynamic DNS. Register subdomains on any of thousands of community-shared domains. They also support pointing an NS record at your own server, making Technitium authoritative for that subdomain — enabling wildcard records, split-horizon DNS, and automated DNS-01 Let's Encrypt challenges. Set FREEDNS_KEYS in the config to push IP updates on each run.
Install dependencies:
sudo apt install grepcidr jq gettext-base
Install:
git clone https://github.com/rsbfox/technitium-ddns
cd technitium-ddns
sudo bash install.sh
Edit the config:
sudo nano /etc/technitium-ddns/technitium-ddns.conf
See what it will do before touching anything:
sudo technitium-ddns --dry-run
sudo technitium-ddns # all zones
sudo technitium-ddns your.domain # one zone
sudo technitium-ddns --dry-run # preview all zones
sudo technitium-ddns --dry-run your.domain # preview one zone
sudo technitium-ddns example.com # always dry run, feature demo
sudo technitium-ddns --run-tests # run all zones in tests/
sudo technitium-ddns --run-tests stress.test # run one test zone
sudo technitium-ddns --run-tests --dry-run # dry run all test zones
sudo technitium-ddns --run-tests --dry-run stress.test # dry run one test zone
Override IPs without editing config:
sudo WAN=1.2.3.4 LAN=192.168.1.1 technitium-ddns
Zones live under zones/ inside the config directory, one subdirectory per
zone named after the FQDN, each containing a main.zone file:
/etc/technitium-ddns/
technitium-ddns.conf
zones/
example.com/
main.zone ← always dry run, feature demo
your.domain/
main.zone ← your zone, must be root:root 640
0.168.192.in-addr.arpa/
main.zone ← reverse zone
tests/
stress.test/
main.zone ← must use .test TLD (RFC 6761)
Zone files are standard BIND 9 format. Copy and edit example.com/main.zone
to get started. Run sudo technitium-ddns example.com to see a full dry run
of the example zone demonstrating every supported feature.
$TTL 1h
$APP SplitHorizon.SimpleAddress Split Horizon
@ IN SOA ns1 hostmaster auto 3600 900 604800 300
@ IN NS ns1
@ IN MX 10 mail
@ IN TXT "v=spf1 ip4:${WAN} ~all"
@ IN CAA 0 issue "letsencrypt.org"
@ IN APP true ; split horizon — LAN gets ${LAN}, WAN gets ${WAN}
ns1 IN A ${WAN}
mail IN A ${WAN}
home IN APP false ; LAN only
Sets the Technitium app for all following APP records. Multiple $APP
directives can appear in one file.
$APP SplitHorizon.SimpleAddress Split Horizon
@ IN APP true ; {"private":["${LAN}"],"public":["${WAN}"]}
home IN APP false ; {"private":["${LAN}"]}
vpn IN APP {"private":["10.0.0.2"],"public":["1.2.3.4"]} ; custom JSON
$APP WildIp.App Wild IP
wildip IN APP ; no recordData
true/false shorthand is only valid for SplitHorizon.SimpleAddress.
All other apps accept JSON or empty.
The stress-test zone is located at tests/stress.test/main.zone.
If you encounter a valid BIND 9 record that the parser does not handle correctly, please open a PR to add it to the zone.
Test zones must use the .test TLD (RFC 6761). The .test domain is reserved
and never delegated in the public DNS, so records pushed to Technitium via
--run-tests cannot leak to the internet.
/etc/technitium-ddns/technitium-ddns.conf must be root:root 640.
| Variable | Required | Description |
|---|---|---|
WAN_IFACE |
if needed | Network interface for WAN IP detection |
LAN_IFACE |
if needed | Network interface for LAN IP detection |
TECH_TOKEN |
yes* | Technitium API token (*not needed for --dry-run) |
TECH_API_BASE |
no | API base URL (default: http://localhost:5380/api) |
FREEDNS_KEYS |
no | Comma-separated afraid.org update keys |
WAN_IFACE and LAN_IFACE are only required if your zone files use ${WAN},
${LAN}, or SplitHorizon.SimpleAddress APP records. Zones with only static
records need neither.
Tested on Ubuntu 24.04 with Technitium DNS Server. Use at your own risk.
A networkd-dispatcher script that triggers technitium-ddns automatically
when the WAN interface obtains a new routable IP. Validates the IP is public,
cycles the interface if a bogon is assigned (e.g. during cable modem sync),
and skips the update if the IP hasn't changed.
Requires systemd-networkd as the network renderer (set renderer: networkd in Netplan).
Install:
sudo cp wan-routable /etc/networkd-dispatcher/routable.d/wan-routable
sudo chown root:root /etc/networkd-dispatcher/routable.d/wan-routable
sudo chmod 755 /etc/networkd-dispatcher/routable.d/wan-routable
sudo systemctl enable --now networkd-dispatcher
Edit WAN_IFACE at the top of wan-routable to match your interface name.