timothymiller/cloudflare-ddns: A 1.1 MB Rust DDNS Client for Cloudflare DNS
🦀 Rust based dynamic DNS (DDNS) updater for Cloudflare
At a glance
- What is it?
- A static Rust binary that keeps Cloudflare A and AAAA records pointed at your home IP, configured entirely through environment variables. It is a good fit for Docker and Kubernetes hosts, and a poor fit for anyone who wants a management UI or a non-Cloudflare DNS provider.
- Who is it for?
- Adopt it if you run Docker, Kubernetes or systemd on a host with a changing public address, you already manage DNS in Cloudflare, and you are comfortable with an environment-variable interface. Do not adopt it if you need a web UI, if your DNS is not at Cloudflare, or if you cannot grant an API token the Edit DNS capability.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem: a home IP that moves while the domain stays put
Residential and small-office connections rarely hold a fixed public address. A router reboot or a carrier lease change can move the address, and any A or AAAA record still pointing at the old one goes dark. The usual fix is a small process that detects the current public IP on a schedule and writes it into DNS. This project is that process, restricted to Cloudflare as the DNS provider.
The audience is narrower than the feature list suggests. You need a Cloudflare-managed zone, an API token with Edit DNS capability, and a host that can run a container or a static binary. The README frames the use case directly: access your home network remotely via a custom domain name without a static IP. If your DNS lives anywhere else, nothing here applies to you.
How the update loop works: detect, compare, patch
The binary takes a flag called --repeat, which the Dockerfile sets as the container entrypoint. The README describes an update cycle that runs on a schedule: detect the current IP, compare it against the record Cloudflare holds, and patch only when they differ. UPDATE_CRON defaults to @every 5m, and UPDATE_ON_START defaults to true, so a fresh container performs one update immediately and then settles into the interval.
IP detection is pluggable and configured per address family. IP4_PROVIDER defaults to ipify; IP6_PROVIDER defaults to cloudflare.trace, which queries Cloudflare's /cdn-cgi/trace endpoint. Other options include Cloudflare DNS-over-HTTPS, a custom HTTP endpoint via url:<url>, a static value via literal:<ips>, and local interface lookups such as local.iface:eth0. Setting a provider to none disables that family entirely.
Two safeguards sit between detection and the API write. The first is Cloudflare IP rejection: with REJECT_CLOUDFLARE_IPS left at its default of true, each cycle fetches Cloudflare's published IP ranges and skips any detected address that falls inside them, because some providers occasionally return a Cloudflare anycast address instead of your real one. If the ranges cannot be fetched, the update is skipped rather than written. The second is failure classification. A transient failure, meaning a network provider errored or every candidate was rejected, never deletes or overwrites an existing record. Only a definitive report of no address of that family can trigger deletion, and only when DELETE_ON_FAILURE is enabled.
Domains are declared as comma-separated lists, split across DOMAINS for both families, IP4_DOMAINS and IP6_DOMAINS for one each. Wildcards and internationalized names are supported, and a regex setting controls which records the tool considers managed.
Installing cloudflare-ddns with Docker and running a first update
The README's quick start is a single docker run. The image is built from scratch, so there is no shell inside the container and no package manager to debug with.
docker run -d \
--name cloudflare-ddns \
--restart unless-stopped \
--network host \
-e CLOUDFLARE_API_TOKEN=your-api-token \
-e DOMAINS=example.com,www.example.com \
timothyjmiller/cloudflare-ddns:latestHost networking is required for IPv6 detection. If you only need IPv4, the README says you can drop --network host and set IP6_PROVIDER=none. The token must be created in the Cloudflare dashboard with Edit DNS capability.
Before letting it write anything, run it once with UPDATE_CRON=@once. The README notes that in this mode UPDATE_ON_START must be true and DELETE_ON_STOP must be false. Combined with dry-run mode, which previews changes without modifying records, that gives you a single pass you can inspect in the logs. Expect to see the detected address, the records considered managed, and either the patch or a statement that no change was needed.
Where the environment-variable interface gets awkward
Everything is configured through environment variables, and that is the whole interface. There is no web UI and no config file required at runtime, though the repository carries a config-example.json and an env-example for reference. For a container that is a strength. For a person who wants to see current record state, last update time, or a history of changes without reading container logs, it is a limitation the project does not try to solve.
The scratch image compounds this. Because the release stage copies only the binary and a CA certificate bundle, there is no shell, no curl, and no dig. Debugging a failed update means reading stdout from the outside, not execing into the container. The README does not document rollback of a bad record, so recovery depends on Cloudflare's own DNS history rather than anything the tool provides.
Scheduling has sharp edges too. @once is a special mode with constraints on UPDATE_ON_START and DELETE_ON_STOP, which means the scheduling variables are not fully independent. And DELETE_ON_FAILURE deserves caution: it acts on a definitive no-address report, which is the correct semantic, but a misconfigured provider that confidently returns nothing is indistinguishable from a genuinely addressless network at that layer.
Compared with ddclient and with Cloudflare Tunnel
ddclient is the long-standing alternative and the comparison people search for most. The difference is scope. ddclient supports many DNS providers and is typically configured through a config file with a Perl runtime underneath. This project supports exactly one provider, Cloudflare, and ships as a static Rust binary with no runtime dependencies. If you might move DNS away from Cloudflare, ddclient's provider abstraction is worth the extra weight. If you are committed to Cloudflare and want the smallest possible footprint, the trade goes the other way.
Cloudflare Tunnel is a different answer to a related question. A tunnel exposes a service without publishing your home IP at all, so there is no A record to keep current and no DDNS loop to run. That is a stronger privacy posture and removes the moving-IP problem entirely. It also requires running the tunnel daemon and, for HTTP services, routing through Cloudflare's edge. DDNS keeps direct addressing: the record points at your IP, and traffic reaches you without an intermediary. Choose based on whether you want your address published, not on which tool is smaller.
Notifications, heartbeat monitoring and the 2.2.0 release
The tool can report its own health. Notifications go through Shoutrrr-compatible targets, which the README lists as Discord, Slack, Telegram, Gotify, Pushover, Zulip and generic webhooks. Zulip support arrived in v2.2.0. Heartbeat monitoring integrates with Healthchecks.io and Uptime Kuma, which is the practical way to notice that updates stopped happening, since a silent DDNS client looks identical to a working one until the address changes.
v2.2.0 is titled Zulip Notifications, Safer Failure Handling and Helm Chart, and the repository carries a charts/ directory alongside k8s/ and systemd/ directories. So Kubernetes deployment is a first-class path rather than something you assemble yourself. The release notes files for 2.1.1, 2.1.2 and 2.2.0 are present at the repository root if you want the change detail.
On maintenance: the last push was on 2026-09-04, and the repository is not archived. The project is licensed GPL-3.0. That matters if you intend to redistribute a modified binary, because the licence carries copyleft obligations; linking or shipping derivatives is a question for your own counsel, not something this article can settle. Using it as an unmodified container in your own infrastructure is the ordinary case.
Editorial conclusion
Adopt it if you run Docker, Kubernetes or systemd on a host with a changing public address, you already manage DNS in Cloudflare, and you are comfortable with an environment-variable interface. Do not adopt it if you need a web UI, if your DNS is not at Cloudflare, or if you cannot grant an API token the Edit DNS capability. Verify three things first: that your token is scoped to the zones you intend to update, that your container can reach cdn-cgi/trace from the host network, and that UPDATE_CRON with @once behaves as the README describes before you point it at a production record.
Frequently asked questions
What is cloudflare-ddns and what does it do?
It is a dynamic DNS client written in Rust that updates Cloudflare DNS records when your public IP changes. It detects the current address on a schedule and patches the records for the domains you list.
How do I install cloudflare-ddns?
The README's quick start is a single docker run with CLOUDFLARE_API_TOKEN and DOMAINS set as environment variables. Host networking is required for IPv6 detection, and can be omitted if you set IP6_PROVIDER=none.
Is Cloudflare DDNS free?
The client itself is open source under GPL-3.0 and the Docker image is published on Docker Hub. Any cost would come from your Cloudflare account, which this material does not describe.
How does cloudflare-ddns compare with ddclient?
ddclient supports many DNS providers and is configured through a config file; this project supports Cloudflare only and is configured entirely through environment variables. It ships as a static Rust binary with no runtime dependencies.
How do I use cloudflare-ddns?
Set CLOUDFLARE_API_TOKEN and one of DOMAINS, IP4_DOMAINS or IP6_DOMAINS, then run the container. It detects your public IP and updates the matching records on the UPDATE_CRON interval, which defaults to @every 5m.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/timothymiller-cloudflare-ddns)