How to Set Up Dynamic DNS on a Linux Server (DuckDNS, Cloudflare and Your Own Domain)

Most home internet connections get a public IP address that changes now and then. Anything that points at your house — a VPN, a website, a Certbot certificate — breaks when it does. Dynamic DNS fixes that: a small script on the server tells a DNS provider your current address every few minutes, so a name like myhome.duckdns.org always resolves to wherever you are.
Quick reference
| Task | What |
|---|---|
| Free name | duckdns.org — pick a subdomain, note the token |
| Update script | ~/duckdns/duck.sh run by cron every 5 minutes |
| Your own domain | Cloudflare API token + a one-line curl |
| Check your public IP | curl ifconfig.me |
| Check the name | nslookup myhome.duckdns.org |
Option 1: DuckDNS (free, two minutes)
- Go to duckdns.org, sign in, add a subdomain, say myhome. Copy the token.
- On the server:
mkdir ~/duckdns && nano ~/duckdns/duck.sh
#!/bin/bash
echo url="https://www.duckdns.org/update?domains=myhome&token=YOUR-TOKEN&ip=" | curl -k -o ~/duckdns/duck.log -K -
chmod 700 ~/duckdns/duck.sh
~/duckdns/duck.sh && cat ~/duckdns/duck.log
It should say OK. Now make it run every five minutes:
crontab -e
*/5 * * * * ~/duckdns/duck.sh >/dev/null 2>&1
Done. myhome.duckdns.org follows your IP for as long as the server is on.
Option 2: Your own domain via Cloudflare
If your domain's DNS is on Cloudflare (free), you can update an A record directly.
- Cloudflare → My Profile → API Tokens → Create Token → Edit zone DNS, limited to your zone. Copy it.
- Find the zone ID (domain overview page) and the record ID:
curl -s -H "Authorization: Bearer TOKEN" \
"https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records?name=home.example.com" | grep -o '"id":"[^"]*' | head -1
- The update script:
#!/bin/bash
IP=$(curl -s https://ifconfig.me)
curl -s -X PATCH "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\":\"home.example.com\",\"content\":\"$IP\",\"proxied\":false}" > /dev/null
Same cron line. Keep proxied off for VPN and SSH — Cloudflare's proxy only passes web traffic.
For a wildcard, make the record *.home.example.com; every subdomain for your reverse proxy then follows your IP too.
Prefer a container?
ddns-updater (qmcgaw) is a Docker container with a web page that supports DuckDNS, Cloudflare, Namecheap, Google and dozens more. Same idea, no script.
Does your router do it?
Many routers have a DDNS page supporting DuckDNS or No-IP. If so, use it — the router knows the moment the IP changes, where a cron job only notices within five minutes.
Troubleshooting
The name points to the wrong IP. Compare curl ifconfig.me and nslookup myhome.duckdns.org. If they differ, run the script by hand and read its output.
It works but Certbot still fails. Dynamic DNS doesn't open ports. You still need 80/443 forwarded on the router (tutorial 27).
Your public IP starts with 100.64–100.127 or 10. That's CGNAT: you don't have a real public IP and nothing you forward will work. Use Tailscale (tutorial 47).
DuckDNS says "KO". Wrong token or subdomain in the script.
Wrapping up
A DuckDNS name and a five-minute cron job is all most home servers need. If you own a domain on Cloudflare, one curl keeps your own name current instead. Either way, check the name against ifconfig.me once and forget about it.
