Dynamic DNS (DDNS) only updates a DNS A or AAAA record after an address changes. It does not allocate a public address, traverse carrier-grade NAT (CGNAT), open a router or host firewall, or secure a service that lacks a listener or authentication. Prove that the network path is reachable before choosing an updater; a correct DNS answer can still lead to no connection at all.
Table of Contents
1. Locate DDNS in the complete path
| Layer | Question to answer | Can DDNS solve it? |
|---|---|---|
| DNS | Which current IPv4/IPv6 address should the hostname name? | Yes, by updating A/AAAA |
| Access network | Does the router WAN really hold public IPv4, or does the host have routed IPv6? | No; it cannot allocate or change an address |
| NAT/CGNAT | Can an inbound Internet connection reach the boundary device? | No; it cannot traverse NAT |
| Routing and firewall | Is traffic explicitly forwarded and allowed to the correct host/port? | No; it opens nothing automatically |
| Application | Is the service listening, encrypted, authenticated, updated, and narrowly exposed? | No; it cannot replace application security |
Define the purpose first: remote administration, a home service, and a monitoring callback need different risk decisions. Do not blindly forward SSH, a database, an admin panel, or an unauthenticated service because DDNS succeeded.
2. Inventory addresses, routes, listeners, and existing updaters read-only
Collect host evidence from an ordinary user shell. Without privilege, ss may omit some process names; record that as a permission boundary, not proof that no service exists.
ip -brief address
ip route show default
ip -6 route show default
ss -lntup
resolvectl status
systemctl list-timers --all | grep -i ddns
systemctl list-unit-files | grep -i ddns
Separately record the router status page's WAN IPv4/IPv6 prefix, whether the ISP contract permits inbound traffic, the zone's authoritative servers, current A/AAAA/TTL, and any router, NAS, container, or host updater. Do not expose accounts, tokens, TSIG keys, full configuration exports, internal address inventories, or device serial numbers in screenshots or tickets.
3. Distinguish public IPv4, double NAT, and CGNAT
RFC 1918 defines 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16 as private IPv4. RFC 6598 defines 100.64.0.0/10 as shared address space. If the router WAN is in one of those ranges, or it differs from a trusted external HTTPS address check, at least one additional NAT exists. It may be a second router you control rather than ISP CGNAT.
An external address check reveals the source address and query time to that service. Prefer a trusted router/ISP interface. If an external service is necessary, approve an HTTPS endpoint that returns exactly one address and requires no token in a query parameter. The example uses IANA-reserved .invalid, so it intentionally cannot run until replaced with a reviewed endpoint.
PublicCheckUrl="https://ip-check.example.invalid/address"
case "$PublicCheckUrl" in
https://*) ;;
*) echo "Stop: public IP discovery must use HTTPS"; exit 1 ;;
esac
curl --proto '=https' --tlsv1.2 --fail --silent --show-error --max-time 15 "$PublicCheckUrl"
Do not scrape an “IP display” search result with an HTML regular expression or query it over plaintext HTTP. Page changes, proxies, redirects, and multiple-address responses create errors, and the lookup itself has a privacy cost.
4. IPv6 does not mean “reachable because there is no NAT”
RFC 4193 reserves fc00::/7 for IPv6 Unique Local addresses; these are not globally Internet-routable. Global IPv6 can still be affected by ISP prefix rotation, temporary privacy addresses, router inbound policy, and the host firewall. An AAAA record should identify the intended stable address authorized for exposure, not simply the first IPv6 shown by ip address.
Own A and AAAA separately. Do not retain a stale AAAA when no IPv6 service works; clients may try it first. If the host uses temporary addresses, choose a stable address/prefix design for the inbound service using current distribution, network-manager, and ISP documentation. Do not disable privacy behavior merely to hide an architecture problem.
5. Router updater or host updater: assign one owner only
| Situation | Preferred owner | Reason |
|---|---|---|
| Router directly holds dynamic public IPv4 | Router's maintained native DDNS function | It learns the WAN change first and does not depend on a LAN host |
| Server directly holds the routed address | One managed updater on that server | Address owner and record owner match |
| DNS provider only supports a dedicated HTTPS API | Managed router/plugin or constrained host service | Must follow the provider's current API |
| Authoritative DNS supports RFC 2136 | nsupdate with a constrained TSIG key | Standard, auditable, and restrictable by record |
| CGNAT with no inbound IPv6 | Reverse tunnel/managed VPN/relay | DDNS does not create an inbound path |
A router, host, NAS, and container must not update the same record simultaneously. They can observe different addresses or timing and make the record oscillate. For each FQDN, record type, and updater, document one owner, trigger, credential, rollback operator, and disable procedure.
6. Credential and update-protocol boundaries
Prefer a maintained native device integration, the provider's official constrained client/API, or RFC 2136 when the authoritative operator explicitly supports it. Do not assume every provider accepts nsupdate. For an HTTPS API, take the URL, method, authentication header, rate limit, and response semantics from current official provider documentation. Never put a token in a URL, command line, unit Environment=, or log.
RFC 2136 defines DNS UPDATE; RFC 8945 is the current TSIG specification. BIND explicitly discourages putting a secret in nsupdate -y, because argv and shell history can disclose it. Use -k to read a key file, and have the DNS administrator restrict that key to one name and the A/AAAA types rather than a whole zone. Stop if the provider offers only a global API key; seek narrower permission or an isolated account.
Give each updater a separate credential, make its file root-readable only, and document issuance time, scope, rotation, and revocation. Never paste the secret into examples, Git, chat, diagnostic logs, or systemctl status.
7. Reproducible RFC 2136 IPv4 updater example
This provider-neutral template applies only when the authoritative service explicitly supports RFC 2136, the name has one A RRset owned solely by this updater, and a least-privilege TSIG key has been issued. Every .invalid value is deliberately nonfunctional and must be replaced. The example does not automate AAAA, avoiding accidental publication of a temporary IPv6 address.
Save this as a local working copy named ddns-update and review it before installation. It rejects non-HTTPS discovery, validates one global IPv4 result, updates only when the authoritative answer lacks that address, and supplies commands to nsupdate -k through a temporary file. The secret never enters argv.
#!/bin/sh
set -eu
: "${DNS_SERVER:?}"
: "${DNS_ZONE:?}"
: "${DNS_NAME:?}"
: "${IP_DISCOVERY_URL:?}"
: "${CREDENTIALS_DIRECTORY:?}"
case "$IP_DISCOVERY_URL" in
https://*'?'*|https://*'#'*|https://*'@'*) echo "IP discovery URL must not contain credentials or a query" >&2; exit 1 ;;
https://*) ;;
*) echo "IP discovery URL must use HTTPS" >&2; exit 1 ;;
esac
case "$DNS_SERVER$DNS_ZONE$DNS_NAME" in
*[!A-Za-z0-9._-]*) echo "DNS values contain unsupported characters" >&2; exit 1 ;;
esac
case "$DNS_ZONE:$DNS_NAME" in
*.:*.) ;;
*) echo "DNS zone and name must be absolute" >&2; exit 1 ;;
esac
test -r "$CREDENTIALS_DIRECTORY/tsig.key" || { echo "TSIG credential is not readable" >&2; exit 1; }
PublicIPv4="$(curl --proto '=https' --tlsv1.2 --fail --silent --show-error --max-time 15 "$IP_DISCOVERY_URL")"
if ! python3 - "$PublicIPv4" <<'PY'
import ipaddress
import sys
try:
value = ipaddress.ip_address(sys.argv[1])
except ValueError:
raise SystemExit(1)
raise SystemExit(0 if value.version == 4 and value.is_global else 1)
PY
then
echo "IP discovery did not return one global IPv4 address" >&2
exit 1
fi
if dig @"$DNS_SERVER" "$DNS_NAME" A +short | grep -Fxq "$PublicIPv4"; then
echo "DDNS record already current"
exit 0
fi
umask 077
UpdateFile="$(mktemp)"
trap 'rm -f "$UpdateFile"' EXIT HUP INT TERM
cat >"$UpdateFile" <<EOF
server $DNS_SERVER
zone $DNS_ZONE
update delete $DNS_NAME A
update add $DNS_NAME 300 A $PublicIPv4
send
EOF
nsupdate -k "$CREDENTIALS_DIRECTORY/tsig.key" "$UpdateFile"
echo "DDNS record updated"
300 is an administrator-selected example TTL, not a speed guarantee. Check the provider or authoritative server's minimum TTL and update frequency first. Do not use this script for a multi-value, load-balanced, health-checked, CDN, DNSSEC-automation, or otherwise jointly controlled RRset.
8. Install files without starting them
First confirm the installed paths and versions of curl, python3, dig, and nsupdate. Ubuntu package names vary by release; use the current Ubuntu package index and apt-cache, not a random install script. The DNS administrator should deliver an nsupdate -k compatible key file over a secure channel; never type its secret into the terminal.
command -v curl python3 dig nsupdate
curl --version
python3 --version
dig -v
nsupdate -V
sudo install -d -m 0750 -o root -g root /etc/ddns
sudo install -m 0600 -o root -g root PATH_TO_ISSUED_KEY_FILE /etc/ddns/tsig.key
sudo install -m 0755 -o root -g root ddns-update /usr/local/libexec/ddns-update
PATH_TO_ISSUED_KEY_FILE is a required local-path replacement, not key content. Verify ownership and modes after installation. Never add /etc/ddns to Git, place it in public object storage, or copy the credential to multiple machines.
9. systemd oneshot service
Save this as a local working copy named ddns-update.service. Replace all four .invalid values with reviewed settings; the IP endpoint must carry no credential in its query string. LoadCredential= makes the key available at service start, while DynamicUser= and hardening constrain the persistent identity and filesystem view.
[Unit]
Description=Update one dynamic DNS A record
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
DynamicUser=yes
Environment=DNS_SERVER=nsupdate.example.invalid
Environment=DNS_ZONE=example.invalid.
Environment=DNS_NAME=home.example.invalid.
Environment=IP_DISCOVERY_URL=https://ip-check.example.invalid/address
LoadCredential=tsig.key:/etc/ddns/tsig.key
ExecStart=/usr/local/libexec/ddns-update
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
LockPersonality=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
This is not a generic provider API unit. For an HTTPS update API, use an official provider client or separately design a wrapper that reads a systemd credential. Do not disguise a bearer token as this TSIG file.
10. systemd timer and single-owner frequency
Save this as ddns-update.timer. It adds a boot delay, an interval after the previous run, and jitter so machines do not all request together. The actual interval must still respect the provider's rate limit and address-change behavior.
[Unit]
Description=Schedule dynamic DNS updates
[Timer]
OnBootSec=5min
OnUnitInactiveSec=15min
RandomizedDelaySec=90s
AccuracySec=30s
Unit=ddns-update.service
[Install]
WantedBy=timers.target
A timer does not replace a DHCP/network event and does not guarantee visibility within an exact number of seconds. Short polling increases provider load and privacy logs; long polling widens the stale-address window. Document the choice.
11. Validate statically, then run once
Confirm that no .invalid value remains, the scope is correct, and the key is revocable. Validate before installation, and do not enable the timer when installing it.
if grep -R -nF '.invalid' ddns-update ddns-update.service ddns-update.timer; then
echo "Stop: replace and review every .invalid placeholder"
exit 1
fi
systemd-analyze verify ./ddns-update.service ./ddns-update.timer
sudo install -m 0644 -o root -g root ddns-update.service /etc/systemd/system/ddns-update.service
sudo install -m 0644 -o root -g root ddns-update.timer /etc/systemd/system/ddns-update.timer
sudo systemctl daemon-reload
sudo systemctl start ddns-update.service
systemctl status ddns-update.service --no-pager
journalctl -u ddns-update.service --since today --no-pager
Output from grep means a placeholder remains and execution must stop; continue only with no output. Make the first run in a maintenance window, retaining provider-console access, the old RRset/TTL, a second management path, and authority to revoke the key. Do not experiment on the only production entry point.
12. Verify through authoritative, recursive, and external paths
Replace the uppercase placeholders with reviewed values. An authoritative query proves the zone changed; a recursive query shows one resolver's present cache. They are different evidence.
dig @AUTHORITATIVE_SERVER HOSTNAME A +noall +answer
dig @RECURSIVE_RESOLVER HOSTNAME A +noall +answer
dig @AUTHORITATIVE_SERVER HOSTNAME AAAA +noall +answer
getent ahosts HOSTNAME
systemctl list-timers ddns-update.timer --all
Then test application TLS, authentication, and only the intended port from an authorized external network. A hairpin-NAT test on the same LAN does not prove public reachability. Do not use a public “port scanner” for sensitive services or publish authentication failures, full hostname inventories, or address history.
Enable the timer only after those checks, the external test, and change approval all pass:
sudo systemctl enable --now ddns-update.timer
systemctl list-timers ddns-update.timer --all
13. TTL, caches, and update results
RFC 1035 defines TTL as the upper bound on how long an RR may be cached; RFC 2308 also defines negative caching. Updating an authoritative record does not purge positive or NXDOMAIN entries already held by resolvers worldwide. Clients, browsers, operating systems, and applications may add further cache behavior.
Never promise “instant propagation.” If safe and controlled, retain the old endpoint for a designed overlap interval; inspect authoritative answers separately from several controlled recursive resolvers, and use the old TTL/negative TTL to reason about waiting. Repeatedly deleting and recreating a name can produce fresh negative cache entries. Continuous DNS changes are not a repair strategy.
14. NAT, firewall, and application security are separate changes
After DNS is correct, separately verify that the boundary owns the target address, a minimal explicit rule forwards/allows traffic to the correct host, host firewall rules cover the intended IPv4/IPv6 path, the service listens only on the expected interface/port, and TLS, strong authentication, updates, rate limiting, and audit are ready.
Ubuntu documents ufw as a host-firewall frontend, but do not run ufw allow merely because of this guide. First collect evidence with sudo ufw status verbose, an administrator-reviewed read-only nftables/router view, and ss; then use a separate approved change to open an exact source, protocol, and port. Never expose a database, container socket, router UI, or unauthenticated dashboard directly to the Internet.
15. Logs, monitoring, and privacy
- Log only time, success/failure class, the necessary portion of the record name, and provider request ID. Secret, authentication headers, and full response bodies are normally unnecessary.
- Public DNS exposes names and addresses; address history can reveal online periods and network moves. Choose a name without a person, address, or device type.
- Do not leave
curl -v,nsupdate -d/-D, or shell tracing enabled; they expand network and authentication metadata. - Alert on consecutive failure, authentication rejection, rapid address oscillation, and owner conflict, with backoff to avoid rate limits.
- After rotating or revoking a credential, make one verification update. Do not retain old keys in images, snapshots, or log attachments.
Monitoring must distinguish discovery failure, rejected update, updated authoritative data, stale recursive cache, unreachable network path, and failed application authentication. DNS-only monitoring misses the most important layers.
16. Rollback and failure matrix
| Symptom | Likely layer | Safe action |
|---|---|---|
| Updater observes private/shared IPv4 | Double NAT/CGNAT | Stop A updates; verify ISP/upstream router |
| Authoritative A is correct, external connection fails | NAT/firewall/service | Stop editing DNS; inspect path and listener |
| Authoritative is new, recursive remains old | TTL/cache | Wait the published TTL; query controlled resolvers |
| A works, AAAA times out | IPv6 route/firewall/stale AAAA | Stop the AAAA owner; repair or restore through change control |
| Record alternates between two addresses | Multiple updater owners | Stop all automation; select one owner, then restore |
NOTAUTH/REFUSED/authentication failure | Zone/server/key scope | Do not broaden the key; involve the DNS administrator |
| A secret appears in logs | Credential incident | Stop service, revoke key, restrict logs, assess exposure |
Stop the timer first and retain evidence, then restore the recorded old RRset through the provider control plane. Do not delete the zone or reset the whole DNS account in an unknown state.
sudo systemctl disable --now ddns-update.timer
sudo systemctl stop ddns-update.service
systemctl is-enabled ddns-update.timer
systemctl is-active ddns-update.service
journalctl -u ddns-update.service --since today --no-pager
After the old record, TTL, external connection, and single owner are restored, decide whether to retain or remove units and keys. If the key may be exposed, revoke it at DNS first and issue a new one; never reuse it.
17. When to use a reverse tunnel, VPN, or relay
DDNS is the wrong connectivity solution when IPv4 is behind CGNAT, no inbound IPv6 is available, the ISP blocks inbound traffic, the edge firewall cannot be administered safely, or the service should not be public. Choose a reverse tunnel, overlay VPN, or authenticated relay that establishes an outbound connection to a controlled endpoint. Separately assess trust, encryption, identity, authorization, logging, availability, cost, and exit migration.
A VPN or tunnel is not automatically safe: control-plane accounts, device keys, ACLs, DNS leaks, relay jurisdiction, and failure rollback still need management. Do not retain unaudited public port forwarding alongside it. For remote administration, prefer access limited to explicit identities and devices over an admin port open to the Internet.
If you already have a relay and your own computers but want a second pair of eyes before changing network rules, I offer a fixed USD 250 LazyRemote Network Fit Review covering topology, listener boundaries, and recovery. Start with metadata only—no passwords, private keys, or unredacted configuration. Deployment and hardware are not included.
18. Current protocols and official references
- RFC 2136: DNS Dynamic Update
- RFC 8945: TSIG
- RFC 1035: DNS RRs and TTL
- RFC 2308: DNS negative caching
- RFC 1918: private IPv4 addresses
- RFC 6598: shared address space `100.64.0.0/10`
- RFC 4193: IPv6 Unique Local addresses
- ISC BIND 9: `nsupdate`, `-k`, and update input
- systemd: source documentation for credentials and sandboxing
- systemd: timer source documentation
- systemd: service-unit source documentation
- curl: HTTPS, protocol restrictions, and failure handling
- Ubuntu Server: firewall
- IANA: example and `.invalid` domains
References checked on 2026-09-01. DNS provider APIs, router firmware, Ubuntu packages, and ISP policies change; implement against installed-version and current provider/ISP documentation.
19. Historical 2014 source archive (provenance only)
The outer fence below preserves the complete visible source_export body byte for byte; no trailing whitespace was normalized and no private value was redacted. It contains plaintext HTTP, credential-like placeholders, obsolete provider endpoints, direct cron execution, and fragile HTML/regex IP scraping. These are inert historical evidence, not current instructions. Do not execute, copy, or restore these URLs.
~~~~markdown
3322的更新还有更加简单的办法,连客户端都不用安装,也不用配置文件,不用知道IP。
www.3322.org网站上介绍的是用lynx(一般的Linux,BSD都自带),就可以了!
使用方法:
lynx -mime_header -auth=用户名:密码 "http://members.3322.net/dyndns/update?system=dyndns&hostname=域名"
而Ubuntu/Debian默认带的是w3m,所以要将命令改成:
w3m -no-cookie -dump http://username:password@members.3322.net/dyndns/update?system=dyndns&hostname=your_domain.f3322.org
把这条命令放在计划任务crontab(编辑用户的Crontab文件: crontab -e ,用户所建立的Crontab文件存于/var/spool/cron中,其文件名与用户名一致。 )里15分钟执行一次就能定时更新了。
*/15 * * * * w3m -no-cookie -dump 'http://username:password@members.3322.net/dyndns/update?system=dyndns&hostname=your_domain.f3322.org'>/dev/null
顺便,使用w3m查询自己公网IP的方法:
w3m -no-cookie -dump www.ip138.com|grep -o "[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}"
~~~~
