Home internet connections usually get a public IP that changes from time to time, after a router restart or every few days. If you run something at home that you want to reach remotely, such as a NAS, Home Assistant, a game server or a VPN endpoint, dynamic DNS (DDNS) keeps a hostname like home.example.com updated with your current IP automatically.
How it works
- A small client (on your router, a NAS or a server at home) checks your current public IP periodically.
- When it changes, the client calls the DNS provider’s API to update the A (and AAAA) record.
- Records use a short TTL (60–300 seconds), so the change takes effect quickly. See DNS propagation.
Do you even have a public IP?
DDNS only helps if your router has a real public IP. Check:
curl -s https://ifconfig.me # your public IP as seen from the internet
Compare it with the WAN IP in your router’s status page. If they differ, or the router shows 100.64.x.x, you’re behind CGNAT. Port forwarding and DDNS won’t work, and you need a tunnel to a VPS instead. See NAT explained and Cloudflare Tunnel.
Options
1. The router’s built-in DDNS
Many routers support providers like DynDNS, No-IP and DuckDNS, sometimes their own (FRITZ!Box: MyFRITZ, ASUS: asuscomm.com). It’s the easiest option, but limited to the providers the router supports.
2. Free DDNS services
DuckDNS, No-IP (free tier with monthly confirmation) and deSEC give you a subdomain like myhome.duckdns.org. Update DuckDNS with one URL:
curl "https://www.duckdns.org/update?domains=myhome&token=YOUR-TOKEN&ip="
Put that in cron every five minutes. See crontab.
3. Your own domain via the DNS provider’s API (Cloudflare example)
Create an API token with Zone → DNS → Edit permission for one zone. Then use a small script:
#!/usr/bin/env bash
# /usr/local/bin/cf-ddns.sh
set -euo pipefail
TOKEN="cf-api-token"
ZONE_ID="your-zone-id"
RECORD_ID="your-record-id" # GET /zones/$ZONE_ID/dns_records?name=home.example.com
NAME="home.example.com"
IP=$(curl -fsS https://api.ipify.org)
CURRENT=$(dig +short "$NAME" @1.1.1.1 | tail -1)
[ "$IP" = "$CURRENT" ] && exit 0
curl -fsS -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"$NAME\",\"content\":\"$IP\",\"ttl\":120,\"proxied\":false}" > /dev/null
logger -t cf-ddns "updated $NAME to $IP"
Run it every few minutes with a systemd timer or cron. See systemd timers.
4. ddclient
ddclient is a long-standing DDNS client supporting many providers (Cloudflare, Namecheap, Google Domains successors, DynDNS protocol services):
sudo apt install ddclient
# /etc/ddclient.conf (Cloudflare example)
daemon=300
ssl=yes
use=web, web=https://api.ipify.org
protocol=cloudflare
zone=example.com
login=token
password=cf-api-token
home.example.com
Containers like favonia/cloudflare-ddns do the same in Docker.
IPv6
With IPv6, each device usually has its own global address, and the prefix may change too. Update the AAAA record from the device that hosts the service (not the router), and allow the port in the router’s IPv6 firewall. ISPs that change prefixes frequently make this tricky.
Security considerations
- Opening ports at home exposes your devices to the internet. Keep them updated, use strong authentication, and prefer exposing only a VPN (WireGuard) rather than individual services. See WireGuard.
- Use scoped API tokens (one zone, DNS edit only), never your global account key.
- Your home IP becomes public via DNS. Some people prefer Cloudflare’s proxy (
proxied: true) for web services to hide it. That only works for HTTP(S).
When a VPS is the better answer
DDNS plus port forwarding works, but it has limits: CGNAT, ISP restrictions on ports 25 and 80, asymmetric upload speeds, home outages and changing IPs. A small VPS with a static IP solves these for anything public-facing:
- host the service on the VPS directly, or
- keep the service at home and connect it to the VPS through a tunnel (frp, WireGuard), with the VPS’s static IP as the public entry point.
Quick reference
| Need | Option |
|---|---|
| Easiest | Router’s built-in DDNS |
| Free hostname | DuckDNS, No-IP, deSEC |
| Own domain on Cloudflare | API script, ddclient, container |
| Behind CGNAT | Tunnel to a VPS instead |
| Check public IP | curl -s https://ifconfig.me |
Related: port forwarding explained and Home Assistant.