unifi-ddns, a Cloudflare Worker that answers the UniFi dynamic DNS call
Cloudflare DDNS (Dynamic DNS) support for UniFi OS
At a glance
- What is it?
- unifi-ddns is a Cloudflare Worker that lets a UniFi gateway update Cloudflare DNS A and AAAA records, filling a gap in older UniFi firmware. It is a small TypeScript project whose whole design is shaped by one thing, that the controller has to talk to it over a URL with placeholders in it.
- Who is it for?
- unifi-ddns suits an operator on UniFi firmware older than 9.1.92 who wants Cloudflare as their dynamic DNS provider, and it suits anyone who needs the features the native implementation does not have, namely multi-hostname updates, dual-stack addressing and preserved proxy status. It is the wrong choice if your gateway already runs current UniFi Network Application firmware and you only need a single A record, because the native support covers that case.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The gap it fills, and the one the native support leaves
unifi-ddns is a Cloudflare Worker script that enables UniFi devices, for example the UDM-Pro and the USG, to dynamically update DNS A and AAAA records on Cloudflare.
The Why Use This section is more interesting than a typical README section because it tells you when not to use the project. UniFi Network Application 9.1.92 and newer includes native Cloudflare DDNS support, so if your gateway runs that firmware or newer you may not need this worker. But the native implementation does not support multi-hostname updates, dual-stack IPv4 and IPv6, or preserving proxy status, and the worker remains useful for those cases and for older firmware.
That is an honest positioning, and it identifies the real reason to prefer the worker even on new firmware. The native feature is a single dynamic DNS entry point, and the gaps are the ones that only show up once you have more than one hostname or an IPv6 address or a proxied record. The worker exists because the controller's built-in provider integration does not expose those options.
So the problem statement is narrower than it first appears. UniFi devices running older firmware do not natively support Cloudflare as a DDNS provider, and this script bridges that gap, allowing the device to keep your DNS records updated with your public IP address.
The project is MIT licensed, the default branch is main, and the last push was on 2026-10-15 with release tags v3.3.1, v3.3.0 and v3.2.0 all from 2026-03-04, which is a burst of releases in a single afternoon. The homepage is not listed separately, and the repository is described as a Cloudflare DDNS support project for UniFi OS.
Deployment, and the two routes
There are two ways to get the worker running, and the README gives both in the same detail.
The first is a click to deploy button that opens the Cloudflare Workers deploy flow with the repository URL pre-filled. You complete the deployment, and then note the *.workers.dev route, which is the next step in the process and the part people usually miss.
The second is the Wrangler CLI, which is the path to take if you want to see what is being deployed:
npm i
wrangler login
wrangler deployYou clone the repository, install the Wrangler CLI, install the dependencies, authenticate and deploy, and again you note the workers.dev route. That route is the address the UniFi controller will call, so it has to be written down exactly.
The repository layout shows what a Worker project needs. There is a wrangler.toml for the Worker configuration, a src/ directory for the source, a test/ directory, a worker-configuration.d.ts file for the generated bindings types, a vitest.config.mts for the test runner, a renovate.json for dependency updates, and a docs/ directory which holds the FAQ at docs/faq.md.
The package.json is small and every dependency is there for a reason. The runtime dependencies are @cloudflare/types and the cloudflare package at 6.2.0, which is the Cloudflare API client the worker uses to write DNS records. The dev dependencies are the Cloudflare Workers types, vitest 4.1.6 with the v8 coverage provider, TypeScript 6.0.3, and wrangler 4.92.0.
The scripts are the Worker standard set. deploy runs wrangler deploy, dev and start both run wrangler dev, test runs vitest, coverage runs vitest with coverage, and cf-typegen runs wrangler types, which regenerates the bindings types from the Worker configuration. That last one is the script that keeps worker-configuration.d.ts honest as the bindings change.
The UniFi side, and why the URL format matters
Configuring the controller is the part where the design of the whole thing becomes clear, because UniFi's dynamic DNS feature calls out to a URL with placeholders in it and the worker has to answer.
You log in to the UniFi OS Controller, go to Settings, then Internet, then WAN, then Dynamic DNS, and create a new dynamic DNS entry with four values.
The service is custom, which tells the controller not to use a built-in provider. The hostname is your subdomain.example.com or example.com. The username is the Cloudflare account email address, for example [email protected]. The password is a Cloudflare User API Token, and the README is emphatic about this, noting it is not an Account API Token.
The server field is the one that matters, and it is the worker route with two placeholders:
<worker-name>.<worker-subdomain>.workers.dev/update?ip=%i&hostname=%hwith the instruction to omit https://. The %i is replaced by the controller with the public IP address it detected, and %h with the hostname. That is the entire contract between the two systems, and it explains why the worker can be written in any language and deployed anywhere, as long as it reads those two query parameters and returns a response the controller accepts.
It also explains the token requirement. The controller sends the username and password to the worker on every update, and the worker uses them to call the Cloudflare API. So the token has to be the kind that can edit DNS records for the zone, scoped narrowly, and the README's step 4 says to scope the token to one specific zone. The username field is the account email rather than a key identifier, which is the shape the Cloudflare API expects alongside a token.
The three capabilities the native implementation lacks all live in the worker. Multi-hostname updates happen because the worker receives the hostname in the request. Dual-stack works because the worker handles both A and AAAA records. And preserving proxy status works because the worker reads the existing record before writing, so it does not reset the proxied flag on an update.
The security posture, and where to look when it breaks
The repository carries a small set of security-related files, which for a project that handles a DNS write token is the right set of things to have.
There is a SECURITY.md, a CODE_OF_CONDUCT.md, a CONTRIBUTING.md and a CHANGELOG.md. The badge set in the README header is also telling: CodeQL code scanning, code coverage, Dependabot updates and the deploy workflow. CodeQL on a repository that takes an API token and calls the Cloudflare API is a reasonable baseline, and Dependabot matters more here than in most projects because a compromise in a dependency is a compromise in a path to your DNS zone.
The test coverage badge and the vitest configuration with the v8 coverage provider show that the worker has a test suite, which for a component whose entire job is a request-response contract is the important part to have tested.
The troubleshooting section is short and points at the FAQ in docs/faq.md as the first stop, with a questions and answers category on the discussions page as the second, and an issue tracker as the last. That ordering is sensible, because the failures in this project are almost entirely about the interaction between the controller's expectations and the worker's response, and the FAQ is where that knowledge accumulates.
The last point worth making is about the deployment model itself. The worker holds a token that can edit DNS for a zone, and the controller holds that token and passes it on every update. The controller is a device on your network, and the worker is public infrastructure. The token is scoped to one zone rather than the account, which is the mitigation, and the setup guide insists on the User API Token rather than the Account API Token for the same reason. Everything about the design pushes you toward the narrowest credential that works, which is the right instinct for a component whose failure mode is a hijacked subdomain.
Editorial conclusion
unifi-ddns suits an operator on UniFi firmware older than 9.1.92 who wants Cloudflare as their dynamic DNS provider, and it suits anyone who needs the features the native implementation does not have, namely multi-hostname updates, dual-stack addressing and preserved proxy status. It is the wrong choice if your gateway already runs current UniFi Network Application firmware and you only need a single A record, because the native support covers that case. Before deploying, read the note about which API token to use, since the configuration asks for a User API Token and explicitly not an Account API Token, and scope the token to one zone as the setup guide instructs.
Frequently asked questions
What does unifi-ddns do?
It is a Cloudflare Worker script that lets UniFi devices such as the UDM-Pro and USG dynamically update DNS A and AAAA records on Cloudflare, bridging the gap for gateways on firmware that does not support Cloudflare as a DDNS provider.
Do I still need this if my UniFi gateway has recent firmware?
Maybe not. UniFi Network Application 9.1.92 and newer includes native Cloudflare DDNS support. The worker remains useful because the native implementation does not support multi-hostname updates, dual-stack IPv4 and IPv6, or preserving proxy status, and it is also the answer for older firmware.
How do I configure the UniFi controller to use the worker?
In the UniFi OS Controller go to Settings, Internet, WAN, Dynamic DNS, and create a new entry with service set to custom, the hostname, the Cloudflare account email as username, a Cloudflare User API Token as password, and the server set to the worker route ending in /update?ip=%i&hostname=%h without the https:// prefix.
What Cloudflare API token do I need?
A User API Token created with the Edit zone DNS template, and not an Account API Token. The setup guide says to scope the token to one specific zone and to save it securely.
How do I deploy the worker?
Either use the click to deploy button in the README, which opens the Cloudflare Workers deploy flow, or use the Wrangler CLI with npm i, wrangler login and wrangler deploy. Either way you need to note the *.workers.dev route, because that is the address the UniFi controller calls.
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/willswire-unifi-ddns)Was this useful?