Cloudflare does not offer a dynamic DNS service as a product, but it does expose an API that makes one straightforward: a script checks your current public address and updates the DNS record when it changes. The bigger question is whether your connection can accept inbound traffic at all, because a growing number cannot.
That second point decides the approach entirely. If your ISP puts you behind carrier-grade NAT, no amount of DNS updating will make your home server reachable, and the answer is a tunnel rather than a record.
Table of contents
- Check whether inbound connections are possible first
- Getting a scoped API token
- The update script
- The proxy setting matters more than you expect
- The tunnel alternative
- What running it at home actually costs
- How this fits the rest of the stack
- FAQ
Check whether inbound connections are possible first
Before building anything, confirm you have a routable public address.
Compare what the internet sees against what your router thinks it has:
curl -s https://api.ipify.org
Then look at the WAN address in your router’s admin interface. If they match, you have a public address and port forwarding will work.
If the router shows an address in 100.64.0.0/10, you are behind carrier-grade NAT. That range is reserved for exactly this, and it means your connection shares a public address with other customers. Inbound connections cannot reach you, port forwarding does nothing, and dynamic DNS solves nothing.
Options at that point: ask the ISP for a public address, which some provide on request or for a fee, or use a tunnel, which works regardless and is covered below.
Also worth checking: many residential terms of service prohibit running public servers. Worth knowing before you build something you depend on.
Getting a scoped API token
Cloudflare’s API needs a token, and it should be scoped narrowly. Create one with:
- Permission: Zone → DNS → Edit
- Zone Resources: the single zone you want updated
Do not use a Global API Key. It has full account access and cannot be scoped, so a leak from a device sitting in your house compromises everything. A zone-scoped token that can edit DNS for one domain is a much smaller problem.
You also need the zone id, shown on the domain’s overview page, and the record id, which you fetch once:
curl -s -H "Authorization: Bearer $CF_TOKEN" \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?name=home.example.com"
Create the A record in the dashboard first, with any placeholder address, then read its id from that response.
The update script
The whole thing is short. The important part is that it only calls the API when the address has actually changed:
#!/bin/bash
set -euo pipefail
ZONE_ID="..."
RECORD_ID="..."
RECORD_NAME="home.example.com"
CACHE=/var/tmp/current-ip
IP=$(curl -s https://api.ipify.org)
[[ -z "$IP" ]] && exit 1
if [[ -f "$CACHE" && "$(cat "$CACHE")" == "$IP" ]]; then
exit 0
fi
curl -s -X PATCH \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"$RECORD_NAME\",\"content\":\"$IP\",\"ttl\":120}"
echo "$IP" > "$CACHE"
Three details worth noting. The empty-response check matters — without it, a failed lookup writes an empty address into your DNS record. The cache file avoids hammering the API when nothing changed. And a TTL of 120 keeps propagation fast after a change.
Schedule it every five minutes. A systemd timer is better than cron here because it logs to the journal automatically and will not start a second copy if one is still running:
[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
Persistent=true
Keep the token out of the script itself — put it in an environment file readable only by root, and reference it from the unit.
The proxy setting matters more than you expect
Cloudflare records can be proxied — the orange cloud — or DNS-only. For a home server the choice has real consequences.
Proxied means traffic goes through Cloudflare, which hides your home address from the public, provides TLS at the edge, and gives you DDoS protection. It also restricts you to HTTP and HTTPS on a specific set of ports; anything else does not pass through, and there are limits on request body sizes on the free plan.
DNS-only publishes your home address publicly and lets any protocol and port through.
For a web service, proxied is clearly better — not least because publishing your home address in public DNS is genuinely worth avoiding. For SSH, game servers, or anything non-HTTP, you either need DNS-only or a different approach entirely.
The mixed setup that works well: a proxied record for the web service, and a tunnel or VPN for administrative access, so nothing needs a DNS-only record pointing at your house.
The tunnel alternative
If you are behind CGNAT, or you would rather not open ports at all, a tunnel changes the model completely.
A daemon on your home machine makes an outbound connection to Cloudflare and holds it open. Requests arrive at Cloudflare and travel back down that connection. Since the connection originates from inside your network, none of it depends on inbound connectivity.
What that gets you:
- Works behind CGNAT, because nothing inbound is required.
- No port forwarding, and no open inbound ports on your router at all.
- Your home address is never published.
- No dynamic DNS needed, since the tunnel identifies itself rather than being located by address.
The trade is a dependency on the tunnel provider and their daemon running on your machine. For most home server use that is a good trade, and it removes the entire category of problem this article started with.
The honest summary: dynamic DNS is the right answer when you have a public address and want direct connectivity, particularly for non-HTTP protocols. A tunnel is the right answer for HTTP services, for anyone behind CGNAT, and for anyone who would rather not run inbound-facing services on a home connection at all.
What running it at home actually costs
Worth being clear-eyed about, because the appeal is understandable and the costs are easy to defer.
A home server means your electricity, your hardware, your upstream bandwidth — usually much lower than downstream — and your availability. Power cuts, ISP maintenance, and a router reboot are all outages. Nobody else is on call.
It is a genuinely good option for learning, for things only you use, and for anything where the data should not leave your house. It is a poor option for anything other people depend on, because the failure modes are all yours and several of them are outside your control.
The middle ground a lot of people land on: keep the storage-heavy and privacy-sensitive things at home, and put anything with users on infrastructure that stays up without you. That is a split by requirement rather than by ideology, and it usually costs less than expected.
How this fits the rest of the stack
The recurring theme is that home hosting works well until something needs to be reachable by other people, at which point the constraints — CGNAT, upstream bandwidth, and being the only person on call — start doing the deciding. For the things that other people depend on, a service with a domain and a certificate handled for you removes that whole category. Custom domains and certificates on RunxBuild covers attaching a hostname, and a managed Postgres or MySQL means the database is not the thing that dies with a power cut. The RunxBuild hosting calculator shows the service, the database, and the bandwidth as separate line items, which makes the comparison against running it yourself a concrete one.
Useful related references:
- Home Assistant Dockerfile: Why You Probably Want Compose Instead
- Ubuntu Home Server: Install, Static IP, SSH, and the First 5 Services
- Free SSL Certificate: Let’s Encrypt, ZeroSSL, and Cloudflare
- Custom domains and certificates on RunxBuild
FAQ
Does Cloudflare offer dynamic DNS?
Not as a product, but its API makes one straightforward. A short script checks your public address and PATCHes the DNS record when it changes. Use a zone-scoped API token with DNS edit permission, never the Global API Key.
How do I know if I am behind CGNAT?
Compare the address from curl https://api.ipify.org against the WAN address in your router. If the router shows something in 100.64.0.0/10, you are behind carrier-grade NAT and inbound connections cannot reach you.
Should I proxy the record through Cloudflare?
For a web service, yes — it hides your home address, provides TLS at the edge, and adds DDoS protection. Proxied records only pass HTTP and HTTPS on specific ports, so SSH or game servers need DNS-only or a tunnel.
What TTL should I use for a dynamic DNS record?
Around 120 seconds. Short enough that a changed address propagates quickly, long enough to avoid unnecessary lookups. Cloudflare ignores the TTL entirely on proxied records, since traffic goes through its edge.
Is a tunnel better than dynamic DNS?
For HTTP services, usually. A tunnel makes an outbound connection so it works behind CGNAT, needs no port forwarding, and never publishes your home address. Dynamic DNS is better when you need direct connectivity for non-HTTP protocols.