isitdns? wiki/Security/Dynamic DNS: when your A record has to chase a moving IP
Security

Dynamic DNS: when your A record has to chase a moving IP

Residential ISPs hand out a public IP that changes without warning. Dynamic DNS (DDNS) pins a stable hostname on top of it: something on the network notices the change and updates the A record, either an in-protocol UPDATE (RFC 2136) authenticated with TSIG (RFC 8945), or an HTTPS call to a provider's API. DHCP servers and Active Directory use UPDATE directly; consumer DDNS providers almost always use the API form instead.

Asks for the serial, which moves every time the zone is updated.

 dig @1.1.1.1 isitdns.net SOA
; <<>> DiG 9.20.11-0ubuntu0.2-Ubuntu <<>> @1.1.1.1 isitdns.net SOA
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28842
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;isitdns.net. IN SOA
;; ANSWER SECTION:
isitdns.net. 1800 IN SOA coleman.ns.cloudflare.com. dns.cloudflare.com. 2414911402 10000 2400 604800 1800
;; Query time: 37 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Mon Sep 14 17:42:31 CDT 2026
;; MSG SIZE rcvd: 105

GO NOERROR and a serial you can compare before and after an update

NO GO a serial that never moves, which means your updates are not landing

run this query in the dig tool →

what each part of this output means →

Check the record#

A DDNS hostname is a plain A record: no special type, no DNSSEC magic. It normally carries a short TTL, 60-300 seconds, so caches drop the old answer within minutes of an IP change.

dig @1.1.1.1 +noall +answer example.duckdns.org A
example.duckdns.org.   60   IN   A   192.0.2.30

example.duckdns.org is a real name that resolves; the address it returns belongs to whoever holds that lease today and rotates with their ISP, so the capture above shows 192.0.2.30, a documentation address from RFC 5737, where the real answer goes. DuckDNS hard-codes that 60-second TTL and does not let you raise it; checked live over DoH on 2026-09-23, a DuckDNS A record answers with "TTL":60. On your own domain, set the TTL yourself, 60-300 seconds, for the same reason.

Compare the record against your current public IP (curl -s https://cloudflare.com/cdn-cgi/trace, or your router's WAN status page). If they don't match, the client didn't run or its last update failed. Providers that expose a DNS API update the record with one call, for example Cloudflare's PATCH DNS record endpoint against a zone-scoped token.

Things that go wrong#

  • Stale record. The client missed an update (crashed, offline, expired credentials) and the record still points at your last IP. Restart the client; DNS itself isn't broken.
  • TTL too long. A record left at 3600s stays wrong for up to an hour after every IP change. Set it to 60-300s.
  • CGNAT. An ISP behind carrier-grade NAT hands your router an address from the shared range in RFC 6598 (100.64.0.0/10), not a routable public IP. DDNS will happily publish that address; nothing reaches it from outside. Compare your WAN IP against https://cloudflare.com/cdn-cgi/trace: if it doesn't match what your ISP handed you, you're behind CGNAT and DDNS can't fix that.
  • Dangling names. Stop running the client and the record keeps pointing at your last IP. Your ISP eventually hands that address to someone else, and anything still trusting the hostname (VPN configs, webhooks, bookmarks) now connects to them instead. Delete the record when you retire the host.

See also#