favonia/cloudflare-ddns
GitHub: favonia/cloudflare-ddns
轻量级 Cloudflare DDNS 更新器,自动检测公网 IP 并通过 Cloudflare API 同步 DNS 记录,支持 IP 列表维护和多渠道通知。
Stars: 2782 | Forks: 111
🔏 You can verify with cosign that the Docker images were built from this repository click to expand
cosign verify favonia/cloudflare-ddns:1 \ --certificate-identity-regexp https://github.com/favonia/cloudflare-ddns/ \ --certificate-oidc-issuer https://token.actions.githubusercontent.com This only proves that the Docker image is from this repository, assuming that no one hacks into GitHub or the repository. It does not prove that the code itself is secure.📦 The updater uses only a small set of established external Go packages click to expand
- cloudflare-go: official Go binding of Cloudflare API v4.
- cron: parsing of Cron expressions.
- go-retryablehttp: HTTP clients with retries and exponential backoff.
- go-querystring: library to construct URL query parameters.
- shoutrrr: notification library for sending general updates.
- ttlcache: in-memory cache to hold Cloudflare API responses.
- x/net: official Go supplementary packages for domain handling and low-level DNS support.
- x/text: official Go supplementary packages for locale-aware text handling.
- mock (for testing only): semi-official framework for mocking.
- testify (for testing only): tool set for testing Go programs.
🐋 Directly run the Docker image click to expand
Create a Cloudflare API token from the [API Tokens page](https://dash.cloudflare.com/profile/api-tokens) with the **Zone - DNS - Edit** and **Account - Account Filter Lists - Edit** permissions. You can remove unneeded permissions based on your setup; see [Cloudflare API Tokens](#cloudflare-api-token) for details. docker run \ --network host \ -e CLOUDFLARE_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN \ -e DOMAINS=example.org,www.example.org,example.io \ -e PROXIED=true \ favonia/cloudflare-ddns:1 ⚠️ `PROXIED=true` does not change the proxy statuses of existing records. See [DNS and WAF Fallback Values](#dns-and-waf-fallback-values).🧬 Directly run the updater from its source click to expand
Create a Cloudflare API token from the [API Tokens page](https://dash.cloudflare.com/profile/api-tokens) with the **Zone - DNS - Edit** and **Account - Account Filter Lists - Edit** permissions. You can remove unneeded permissions based on your setup; see [Cloudflare API Tokens](#cloudflare-api-token) for details. You need the [Go tool](https://go.dev/doc/install) to run the updater from its source. CLOUDFLARE_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN \ DOMAINS=example.org,www.example.org,example.io \ PROXIED=true \ go run github.com/favonia/cloudflare-ddns/cmd/ddns@latest ⚠️ `PROXIED=true` does not change the proxy statuses of existing records. See [DNS and WAF Fallback Values](#dns-and-waf-fallback-values).CLOUDFLARE_API_TOKEN should be a Cloudflare API token, not the older global API key used by some other tools. Create one from the API Tokens page, typically using the Edit zone DNS template. If you also use WAF lists, add the Account - Account Filter Lists - Edit permission.
📍 DOMAINS is the list of domains to update click to expand
The value of `DOMAINS` should be a list of [fully qualified domain names (FQDNs)](https://en.wikipedia.org/wiki/Fully_qualified_domain_name) separated by commas. For example, `DOMAINS=example.org,www.example.org,example.io` instructs the updater to manage the domains `example.org`, `www.example.org`, and `example.io`. These domains do not have to share the same DNS zone---the updater will take care of the DNS zones behind the scenes.
🚨 Remove PROXIED=true if you are not running a web server click to expand
Keep `PROXIED=true` when you want [Cloudflare’s proxy](https://developers.cloudflare.com/dns/proxy-status/) for the domains managed by this updater. Proxying lets Cloudflare cache webpages and hide your IP addresses.
| If you want... | Do this |
| ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Create new records that expose your real IP addresses | Remove `PROXIED=true` or change it to `PROXIED=false` |
| Create new records for non-HTTP(S) traffic | Remove `PROXIED=true` or change it to `PROXIED=false`, because Cloudflare cannot proxy it |
| Create new records whose HTTP(S) traffic is proxied | Keep `PROXIED=true` |
| Change the proxy statuses of existing records | Change them manually on the [Cloudflare DNS Records page](https://dash.cloudflare.com/?to=/:account/:zone/dns/records) |
The default value of `PROXIED` is `false`.
exec /bin/ddns: operation not permitted
Some Docker, kernel, and virtualization combinations do not work well with [`security_opt: [no-new-privileges:true]`](https://docs.docker.com/reference/cli/docker/container/run/). If this happens, try removing that one hardening option and start the container again. This slightly reduces security, so keep the other hardening options if possible.
If removing `no-new-privileges` fixes the problem, keep it disabled for this container or adjust your security policy to allow this binary.
If removing `no-new-privileges` does not help, try a minimal image such as `alpine` or another popular Docker image with the same hardening option. If that also fails, the problem is likely in the host environment rather than this updater. Reported cases have included older kernels and some QEMU/Proxmox-style virtualized setups.
If none of these applies, please [open an issue on GitHub](https://github.com/favonia/cloudflare-ddns/issues/new/choose) and include your compose file with secrets redacted, `docker version`, `uname -a`, your host OS and virtualization platform (if any), and whether a minimal image such as `alpine` shows the same error.
### 🤔 I am getting error code: 1034
There have been reports of intermittent issues with the default provider `cloudflare.trace`. If you see `error code: 1034`, upgrade to version 1.15.1 or later, or switch to another provider such as `cloudflare.doh` or `url:context deadline exceeded and IP detection failed
The first thing to check is whether a container can reach Cloudflare from the Docker environment at all. A simple way to test that is to run a minimal image such as `alpine` and try both DNS resolution and HTTPS connectivity:
docker run --rm alpine nslookup api.cloudflare.com
docker run --rm alpine wget -qO- https://api.cloudflare.com/cdn-cgi/trace
If `nslookup` fails, your Docker setup likely has a DNS problem. If `wget` fails, outbound HTTPS connectivity to Cloudflare is likely blocked or broken. If both commands work, try increasing `DETECTION_TIMEOUT` (for example, `DETECTION_TIMEOUT=1m`) in case requests are simply slow in your environment.
If that still does not help, please [open a GitHub issue](https://github.com/favonia/cloudflare-ddns/issues/new/choose) and include your setup details, relevant configs with secrets redacted, and any logs you have so that we can investigate further.
### 🤔 Why did the updater detect a public IP address different from the WAN IP address on my router?
If your router shows an address between `100.64.0.0` and `100.127.255.255`, you are likely behind [CGNAT (Carrier-grade NAT)](https://en.wikipedia.org/wiki/Carrier-grade_NAT). In that case, your ISP is not giving you a real public IP address, so ordinary DDNS cannot make your home network directly reachable from the Internet.
Your options are usually to switch to an ISP that gives you a real public IP address or to use a different approach such as [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/).
### 🤔 How can I see the timestamps of the IP checks and/or updates?
The updater does not add timestamps itself because most runtimes already do:
- If you are using Docker Compose, Kubernetes, or Docker directly, add `--timestamps` when viewing the logs.
- If you are using Portainer, [enable “Show timestamp” when viewing the logs](https://docs.portainer.io/user/docker/containers/logs).
## 🎛️ Further Customization
### ⚙️ All Settings
The emoji “🧪” marks experimental features, and the emoji “🤖” marks technical details that most readers can skip on a first pass.
🔐 Cloudflare API Access click to expand
| Name | Meaning | | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `CLOUDFLARE_API_TOKEN` | The [Cloudflare API token](https://dash.cloudflare.com/profile/api-tokens) to access the Cloudflare API | | `CLOUDFLARE_API_TOKEN_FILE` | An absolute path to a file that contains the [Cloudflare API token](https://dash.cloudflare.com/profile/api-tokens) to access the Cloudflare API | | `CF_API_TOKEN` (will be deprecated in version 2.0.0) | Same as `CLOUDFLARE_API_TOKEN` | | `CF_API_TOKEN_FILE` (will be deprecated in version 2.0.0) | Same as `CLOUDFLARE_API_TOKEN_FILE` |🌐 DNS Record Scope click to expand
| Name | Meaning | | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `DOMAINS` | Comma-separated fully qualified domain names or wildcard domain names that the updater should manage for both `A` and `AAAA` records. Listing a domain in `DOMAINS` manages the same DNS records as listing the same domain in both `IP4_DOMAINS` and `IP6_DOMAINS`. | | `IP4_DOMAINS` | Comma-separated fully qualified domain names or wildcard domain names that the updater should manage for `A` records (in addition to those in `DOMAINS`) | | `IP6_DOMAINS` | Comma-separated fully qualified domain names or wildcard domain names that the updater should manage for `AAAA` records (in addition to those in `DOMAINS`) | | Name | Meaning | Default Value | | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | | `MANAGED_RECORDS_COMMENT_REGEX` (available since version 1.16.0) | Regex that selects which DNS records this updater manages by their comments. Matched records are updated or deleted as needed; new records are created with comments that match. Uses [RE2](https://github.com/google/re2/wiki/Syntax) syntax (the Go `regexp` syntax, not Perl/PCRE). | `""` (empty regex; manages all DNS records) |📋 WAF List Scope click to expand
| Name | Meaning | | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 🧪 `WAF_LISTS` (available since version 1.14.0) |🧪 Comma-separated references of [WAF lists](https://developers.cloudflare.com/waf/tools/lists/custom-lists/) the updater should manage. A list reference is written in the format `
🔑 The API token needs the **Account - Account Filter Lists - Edit** permission.
💡 See [how to find your account ID](https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/).
🔍 IP Detection click to expand
| Name | Meaning | Default Value | | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | | `IP4_PROVIDER` | This specifies how to detect the current IPv4 address. Available providers include `cloudflare.trace`, `cloudflare.doh`, `local`, `local.iface:Get the IP address via local network interfaces and routing tables. The updater will use the local address that _would have_ been used for outbound UDP connections to Cloudflare servers. (No data will be transmitted.)
⚠️ The updater needs access to the host network (such as `network_mode: host` in Docker Compose) for this provider, for otherwise the updater will detect the addresses inside [the default bridge network in Docker](https://docs.docker.com/engine/network/drivers/bridge/) instead of those in the host network.
| | 🧪 `local.iface:🧪 Get IP addresses via the specific local network interface `iface`. Since version 1.16.0, the updater collects all matching global unicast addresses of the selected IP family (IPv4 or IPv6) instead of just the first one, then reconciles DNS records and WAF lists against that full set.
⚠️ The updater needs access to the host network (such as `network_mode: host` in Docker Compose) for this provider, for otherwise the updater cannot access host network interfaces.
🤖 The updater ignores the prefix length reported by the interface, because it commonly describes its local subnet, not the range the updater should claim. The updater uses the default prefix lengths from `IP4_DEFAULT_PREFIX_LEN` or `IP6_DEFAULT_PREFIX_LEN` instead.
| | `url:Fetch the IP address from a URL. The provider format is `url:` followed by the URL itself. For example, `IP4_PROVIDER=url:https://api4.ipify.org` fetches the IPv4 address from
The updater connects over IPv4 for `IP4_PROVIDER` and over IPv6 for `IP6_PROVIDER`. The intention is to query a public IP detection server with the correct IP family. If you want to override that, use `IP4_PROVIDER=url.via6:
The response may also use CIDR notation. 🧪 It may contain multiple addresses using the line-based text format described after this table.
🕰️ Before version 1.15.0, `url:
Fetch the IP address from a URL while always connecting to that URL over IPv4. It accepts the same CIDR notation and 🧪 multiple-address text format as `url:`.
The intention is to get an IPv6 address over IPv4 with `IP6_PROVIDER=url.via4:
Fetch the IP address from a URL while always connecting to that URL over IPv6. It accepts the same CIDR notation and 🧪 multiple-address text format as `url:`.
The intention is to get an IPv4 address over IPv6 with `IP4_PROVIDER=url.via6:
Read the IP address from a local file. The path must be absolute.
The file is re-read on every detection cycle, so you can update it without restarting the updater.
🧪 The file may also use the line-based text format described after this table for multiple addresses.
⚠️ The file must be readable by the user configured by `user: "UID:GID"`.
| | `static:Use one or more explicit IP addresses or addresses in CIDR notation as a fixed set, separated by commas. This is an advanced provider for tests, debugging, and special fixed-input setups.
⚠️ Most users should not use it for normal long-running DDNS.
🤖 The entries are parsed, deduplicated, sorted, and validated for the selected IP family via the same normalization pipeline used by other providers.
| | `static.empty` (available since version 1.16.0) |Clear existing managed content for the selected IP family. In contrast, `none` preserves existing managed content for that family.
🧪 If you also use WAF lists, this clears managed items of that IP family but does not delete the list itself. The updater will try to delete the list on exit only when `DELETE_ON_STOP` is enabled.
⚠️ Most users should not use it for normal long-running DDNS.
| | `none` |Stop managing the specified IP family for this run. For example `IP4_PROVIDER=none` stops managing IPv4. Existing managed DNS records of that IP family are preserved.
🧪 Existing managed WAF list items of that IP family are preserved too, because that family is out of scope. Use `static.empty` if you want to clear managed content for that family. As the support of WAF lists is still experimental, please [provide feedback](https://github.com/favonia/cloudflare-ddns/issues/new/choose) if this does not match your needs.
|📅 Update Schedule and Lifecycle click to expand
| Name | Meaning | Default Value | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | | `CACHE_EXPIRATION` | The expiration of cached Cloudflare API responses. It can be any positive time duration accepted by [time.ParseDuration](https://pkg.go.dev/time#ParseDuration), such as `1h` or `10m`. | `6h0m0s` (6 hours) | | `DELETE_ON_STOP` |Whether managed DNS records and managed WAF content are deleted when the updater exits. It accepts any boolean value supported by [strconv.ParseBool](https://pkg.go.dev/strconv#ParseBool), such as `true`, `false`, `0`, or `1`.
DNS cleanup applies only to the IP families this updater is managing in that run.
🧪 For WAF lists, the updater deletes the whole list only when the updater manages both IP families and no filtering is enabled by `MANAGED_WAF_LIST_ITEMS_COMMENT_REGEX`. Otherwise shutdown cleanup keeps the list and deletes only managed items in the managed IP families.
| `false` | | `TZ` |The timezone used for logging messages and parsing `UPDATE_CRON`. It can be any timezone accepted by [time.LoadLocation](https://pkg.go.dev/time#LoadLocation), including any IANA Time Zone.
🤖 The pre-built Docker images come with the embedded timezone database via the [time/tzdata](https://pkg.go.dev/time/tzdata) package.
| `UTC` | | `UPDATE_CRON` |The schedule to re-check IP addresses and update DNS records and WAF lists (if needed). The format is [any cron expression accepted by the `cron` library](https://pkg.go.dev/github.com/robfig/cron/v3#hdr-CRON_Expression_Format) or the special value `@once`. The special value `@once` means the updater will terminate immediately after updating the DNS records or WAF lists, effectively disabling the scheduling feature.
🤖 The update schedule _does not_ take the time to update records into consideration. For example, if the schedule is `@every 5m`, and if the updating itself takes 2 minutes, then the actual interval between adjacent updates is 3 minutes, not 5 minutes.
| `@every 5m` (every 5 minutes) | | `UPDATE_ON_START` | Whether to check IP addresses (and possibly update DNS records and WAF lists) _immediately_ on start, regardless of the update schedule specified by `UPDATE_CRON`. It can be any boolean value accepted by [strconv.ParseBool](https://pkg.go.dev/strconv#ParseBool), such as `true`, `false`, `0`, or `1`. | `true` |⏳ Operation Timeouts click to expand
| Name | Meaning | Default Value | | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | | `DETECTION_TIMEOUT` | The timeout of each attempt to detect IP address, per IP version (IPv4 and IPv6). It can be any positive time duration accepted by [time.ParseDuration](https://pkg.go.dev/time#ParseDuration), such as `1h` or `10m`. | `5s` (5 seconds) | | `UPDATE_TIMEOUT` | The timeout of each attempt to update DNS records, per domain and per record type, or per WAF list. It can be any positive time duration accepted by [time.ParseDuration](https://pkg.go.dev/time#ParseDuration), such as `1h` or `10m`. | `30s` (30 seconds) |🛟 DNS and WAF Fallback Values click to expand
| Name | Meaning | Default Value | | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | | `PROXIED` |Fallback proxy setting for DNS records managed by the updater. It can be any boolean value accepted by [strconv.ParseBool](https://pkg.go.dev/strconv#ParseBool), such as `true`, `false`, `0`, or `1`.
🤖 Advanced usage: it can also be a domain-dependent boolean expression, as described in the examples later in this section.
| `false` | | `TTL` | Fallback TTL (in seconds) for DNS records managed by the updater. | `1` (This means “automatic” to Cloudflare) | | `RECORD_COMMENT` | Fallback [record comment](https://developers.cloudflare.com/dns/manage-dns-records/reference/record-attributes/) for DNS records managed by the updater. | `""` | | 🧪 `WAF_LIST_DESCRIPTION` (available since version 1.14.0) |🧪 Fallback description for WAF lists managed by the updater.
🤖 This matters only when the updater needs to create a new WAF list, because a WAF list has only one description.
| `""` | | 🧪 `WAF_LIST_ITEM_COMMENT` (available since version 1.16.0) | 🧪 Fallback comment for WAF list items managed by the updater. | `""` |👁️ Logging click to expand
| Name | Meaning | Default Value | | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | `EMOJI` | Whether the updater should use emojis in the logging. It can be any boolean value accepted by [strconv.ParseBool](https://pkg.go.dev/strconv#ParseBool), such as `true`, `false`, `0`, or `1`. | `true` | | `QUIET` | Whether the updater should reduce the logging. It can be any boolean value accepted by [strconv.ParseBool](https://pkg.go.dev/strconv#ParseBool), such as `true`, `false`, `0`, or `1`. | `false` |📣 Notifications click to expand
| Name | Meaning | | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `HEALTHCHECKS` |The [Healthchecks ping URL](https://healthchecks.io/docs/) to ping when the updater successfully updates IP addresses, such as `https://hc-ping.com/
⚠️ The ping schedule should match the update schedule specified by `UPDATE_CRON`.
🤖 The updater can work with _any_ server following the [same Healthchecks protocol](https://healthchecks.io/docs/http_api/), including self-hosted instances of [Healthchecks](https://github.com/healthchecks/healthchecks). Both UUID and Slug URLs are supported, and the updater works regardless whether the POST-only mode is enabled.
The Uptime Kuma’s Push URL to ping when the updater successfully updates IP addresses, such as `https://
⚠️ The “Heartbeat Interval” should match the update schedule specified by `UPDATE_CRON`.
| | `SHOUTRRR` |Newline-separated [shoutrrr URLs](https://containrrr.dev/shoutrrr/latest/services/overview/) to which the updater sends notifications of IP address changes and other events. In other words, put one URL on each line. Blank lines and lines whose first non-whitespace character is `#` are ignored. Each shoutrrr URL represents a notification service; for example, `discord://
If you configure this value via YAML, prefer literal block style \| over folded style >.
An absolute path to a file containing shoutrrr URLs, one per line. This is the file version of `SHOUTRRR`, useful for keeping URLs that embed secret tokens out of the environment (for example with [Docker secrets](https://docs.docker.com/compose/how-tos/use-secrets/)).
Blank lines and lines whose first non-whitespace character is `#` are ignored. If both `SHOUTRRR` and `SHOUTRRR_FILE` are set, they must specify the same URLs (order does not matter) or the updater refuses to start.
⚠️ The file must be readable by the user configured by `user: "UID:GID"`.
|