cloudflare-ddns
                                
                                 cloudflare-ddns copied to clipboard
                                
                                    cloudflare-ddns copied to clipboard
                            
                            
                            
                        π A small, feature-rich, and robust Cloudflare DDNS updater
π Cloudflare DDNS
A small and fast DDNS updater for Cloudflare.
π Quiet mode enabled
π Cloudflare DDNS
π₯· Remaining priviledges:
   πΈ Effective UID:      1000
   πΈ Effective GID:      1000
   πΈ Supplementary GIDs: (none)
π£ Added a new A record of "β¦β¦" (ID: β¦β¦)
π£ Added a new AAAA record of "β¦β¦" (ID: β¦β¦)
π Highlights
β‘ Efficiency
- π€ The Docker images are small (less than 3 MB after compression).
- π The Go runtime will re-use existing HTTP connections.
- ποΈ Cloudflare API responses are cached to reduce the API usage.
π― Comprehensive Support of Domain Names
Simply list all the domain names and you are done!
- π Internationalized domain names (e.g., π±.example.organdζ₯ζ¬ο½‘coqjp) are fully supported. (The updater smooths out rough edges of the Cloudflare API.)
- π Wildcard domain names (e.g., *.example.org) are also supported.
- π This updater automatically finds the DNS zones for you, and it can handle multiple DNS zones.
- πΉοΈ You can toggle IPv4 (Arecords), IPv6 (AAAArecords) and Cloudflare proxying on a per-domain basis.
π΅οΈ Privacy
By default, public IP addresses are obtained using the Cloudflare debugging page. This minimizes the impact on privacy because we are already using the Cloudflare API to update DNS records. Moreover, if Cloudflare servers are not reachable, chances are you could not update DNS records anyways. You can also configure the updater to use ipify, which claims not to log any visitor information. Open a GitHub issue to propose a new method to detect public IP addresses.
π‘οΈ Security
- 
π The superuser privileges are immediately dropped after the updater starts. 
- 
π₯οΈ Optionally, you can monitor the updater via Healthchecks.io, which will notify you when the updating fails. 
- 
π The updater uses only established open-source Go libraries. π Full list of external Go libraries (click to expand)- cap:
 Manipulation of Linux capabilities.
- cloudflare-go:
 The official Go binding of Cloudflare API v4. It provides robust handling of pagination, rate limiting, and other tricky bits.
- cron:
 Parsing of Cron expressions.
- go-cache:
 Essentiallymap[string]anywith expiration times.
- mock (for testing only):
 A comprehensive, semi-official framework for mocking.
- testify (for testing only):
 A comprehensive tool set for testing Go programs.
 
- cap:
β·οΈ Quick Start
(Click to expand the following items.)
π Directly run the provided Docker images.
docker run \
  --network host \
  -e CF_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN \
  -e DOMAINS=example.org,www.example.org,example.io \
  -e PROXIED=true \
  favonia/cloudflare-ddns
𧬠Directly run the updater from its source on Linux.
You need the Go tool to run the updater from its source.
CF_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN \
  DOMAINS=example.org,www.example.org,example.io \
  PROXIED=true \
  go run ./cmd/*.go
π For non-Linux operating systems, please use Docker images instead.
π Deployment with Docker Compose
π¦ Step 1: Updating the Compose File
Incorporate the following fragment into the compose file (typically docker-compose.yml or docker-compose.yaml).
version: "3"
services:
  cloudflare-ddns:
    image: favonia/cloudflare-ddns:latest
    network_mode: host
    restart: always
    security_opt:
      - no-new-privileges:true
    environment:
      - PGID=1000
      - PUID=1000
      - CF_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN
      - DOMAINS=example.org,www.example.org,example.io
      - PROXIED=true
(Click to expand the following items.)
π‘ Use network_mode: host to enable IPv6 (or read more).
The easiest way to enable IPv6 is to use network_mode: host so that the updater can access the host IPv6 network directly. If you wish to keep the updater isolated from the host network, check out the official documentation on IPv6 and this GitHub issue about IPv6. If your host OS is Linux, hereβs the tl;dr:
- Use network_mode: bridgeinstead ofnetwork_mode: host.
- Edit or create /etc/docker/daemon.jsonwith the following content:{ "ipv6": true, "fixed-cidr-v6": "fd00::/8", "experimental": true, "ip6tables": true }
- Restart the Docker daemon (if you are using systemd):
systemctl restart docker.service
π Use restart: always to automatically restart the updater on system reboot.
Dockerβs default restart policies should prevent excessive logging when there are configuration errors.
π‘οΈ Use no-new-privileges:true, PUID, and PGID to protect yourself.
Change 1000 to the user or group IDs you wish to use to run the updater. The setting no-new-privileges:true provides additional protection, especially when you run the container as a non-superuser. The updater itself will read PUID and PGID and attempt to drop all those privileges as much as possible.
π Use PROXIED=true to hide your IP addresses.
The setting PROXIED=true instructs Cloudflare to cache webpages on your machine and hide your actual IP addresses. If you wish to bypass that and expose your actual IP addresses, simply remove PROXIED=true. (The default value of PROXIED is false.)
πͺ§ Step 2: Updating the Environment File
Add these lines to your environment file (typically .env):
CF_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN
DOMAINS=example.org,www.example.org,example.io
(Click to expand the following items.)
π CF_API_TOKEN is your Cloudflare API token.
The value of CF_API_TOKEN should be an API token (not an API key), which can be obtained from the API Tokens page. Use the Edit zone DNS template to create and copy a token into the environment file. β οΈ The less secure API key authentication is deliberately not supported.
π DOMAINS contains the domains to update.
The value of DOMAINS should be a list of fully qualified domain names 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 be in the same zone---the updater will identify their zones automatically.
π Step 3: Building the Container
docker-compose pull cloudflare-ddns
docker-compose up --detach --build cloudflare-ddns
βΈοΈ Deployment with Kubernetes
Kubernetes offers great flexibility in assembling different objects together. The following shows a minimum setup.
π Step 1: Creating a YAML File
Save the following configuration as cloudflare-ddns.yaml.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cloudflare-ddns
  labels:
    app: cloudflare-ddns
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: cloudflare-ddns
  template:
    metadata:
      name: cloudflare-ddns
      labels:
        app: cloudflare-ddns
    spec:
      restartPolicy: Always
      containers:
        - name: cloudflare-ddns
          image: favonia/cloudflare-ddns:latest
          securityContext:
            allowPrivilegeEscalation: false
            runAsUser: 1000
            runAsGroup: 1000
          env:
            - name: "IP6_PROVIDER"
              value: "none"
            - name: "PROXIED"
              value: "true"
            - name: "CF_API_TOKEN"
              value: "YOUR-CLOUDFLARE-API-TOKEN"
            - name: "DOMAINS"
              value: "example.org,www.example.org,example.io"
(Click to expand the following items.)
π Use restartPolicy: Always to automatically restart the updater on system reboot.
Kubernetesβs default restart policies should prevent excessive logging when there are configuration errors.
π‘οΈ Use runAsUser, runAsGroup, and allowPrivilegeEscalation: false to protect yourself.
Kubernetes comes with built-in support to drop superuser privileges. The updater itself will also attempt to drop all of them.
π‘ Use IP6_PROVIDER: "none" to disable IPv6 management.
The support of IPv6 in Kubernetes has been improving, but a working setup still takes effort. Since Kubernetes 1.21+, the IPv4/IPv6 dual stack is enabled by default, but a setup which allows IPv6 egress traffic (e.g., to reach Cloudflare servers to detect public IPv6 addresses) still requires deep understanding of Kubernetes and is beyond this simple guide. The popular tool minicube, which implements a simple local Kubernetes cluster, unfortunately does not support IPv6 yet. Until there is an easy way to enable IPv6 in Kubernetes, the template here will have IPv6 disabled.
If you manage to enable IPv6, congratulations. Feel free to remove IP6_PROVIDER: "none" to detect and update both A and AAAA records. There is almost no danger in enabling IPv6 even when the IPv6 setup is not working. In the worst case, the updater will remove all AAAA records associated with the domains in DOMAINS and IP6_DOMAINS because those records will appear to be βstale.β The deleted records will be recreated once the updater correctly detects the IPv6 addresses.
π Use PROXIED: "true" to hide your IP addresses.
The setting PROXIED: "true" instructs Cloudflare to cache webpages on your machine and hide your actual IP addresses. If you wish to bypass that and expose your actual IP addresses, simply remove PROXIED: "true". (The default value of PROXIED is false.)
π CF_API_TOKEN is your Cloudflare API token.
The value of CF_API_TOKEN should be an API token (not an API key), which can be obtained from the API Tokens page. Use the Edit zone DNS template to create and copy a token into the environment file. β οΈ The less secure API key authentication is deliberately not supported.
π DOMAINS contains the domains to update.
The value of DOMAINS should be a list of fully qualified domain names 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 be in the same zone---the updater will identify their zones automatically.
π Step 2: Creating the Deployment
kubectl create -f cloudflare-ddns.yaml
ποΈ Further Customization
βοΈ All Settings
(Click to expand the following items.)
π Cloudflare accounts and API tokens
| Name | Valid Values | Meaning | Required? | Default Value | 
|---|---|---|---|---|
| CF_ACCOUNT_ID | Cloudflare Account IDs | The account ID used to distinguish multiple zone IDs with the same name | No | (unset) | 
| CF_API_TOKEN_FILE | Paths to files containing Cloudflare API tokens | A file that contains the token to access the Cloudflare API | Exactly one of CF_API_TOKENandCF_API_TOKEN_FILEshould be set | N/A | 
| CF_API_TOKEN | Cloudflare API tokens | The token to access the Cloudflare API | Exactly one of CF_API_TOKENandCF_API_TOKEN_FILEshould be set | N/A | 
In most cases, CF_ACCOUNT_ID is not needed.
π Domains and IP providers
| Name | Valid Values | Meaning | Required? | Default Value | 
|---|---|---|---|---|
| DOMAINS | Comma-separated fully qualified domain names or wildcard domain names | The domains the updater should manage for both AandAAAArecords | (See below) | (empty list) | 
| IP4_DOMAINS | Comma-separated fully qualified domain names or wildcard domain names | The domains the updater should manage for Arecords | (See below) | (empty list) | 
| IP6_DOMAINS | Comma-separated fully qualified domain names or wildcard domain names | The domains the updater should manage for AAAArecords | (See below) | (empty list) | 
| IP4_PROVIDER | cloudflare.doh,cloudflare.trace,ipify,local, andnone | How to detect IPv4 addresses. (See below) | No | cloudflare.trace | 
| IP6_PROVIDER | cloudflare.doh,cloudflare.trace,ipify,local, andnone | How to detect IPv6 addresses. (See below) | No | cloudflare.trace | 
π At least one of
DOMAINSandIP4/6_DOMAINSmust be non-empty.At least one domain should be listed in
DOMAINS,IP4_DOMAINS, orIP6_DOMAINS. Otherwise, if all of them are empty, then the updater has nothing to do. It is fine to list the same domain in bothIP4_DOMAINSandIP6_DOMAINS, which is equivalent to listing it inDOMAINS. Internationalized domain names are supported using the non-transitional processing that is fully compatible with IDNA2008.
π Available providers for
IP4_PROVIDERandIP6_PROVIDER:
cloudflare.doh
Get the public IP address by queryingwhoami.cloudflare.against Cloudflare via DNS-over-HTTPS and update DNS records accordingly.
cloudflare.trace
Get the public IP address by parsing the Cloudflare debugging page and update DNS records accordingly.
ipify
Get the public IP address via ipifyβs public API and update DNS records accordingly.
local
Get the address via local network interfaces and update DNS records accordingly. When multiple local network interfaces or in general multiple IP addresses are present, the updater will use the address that would have been used for outbound UDP connections to Cloudflare servers. β οΈ You need access to the host network (such asnetwork_mode: hostin Docker Compose orhostNetwork: truein Kubernetes) for this policy, for otherwise the updater will detect the addresses inside the bridge network in Docker or the default namespaces in Kubernetes instead of those in the host network.
none
Stop the DNS updating completely. Existing DNS records will not be removed.The option
IP4_PROVIDERis governing IPv4 addresses andA-type records, while the optionIP6_PROVIDERis governing IPv6 addresses andAAAA-type records. The two options act independently of each other.
π What are wildcard domains?
Wildcard domains (
*.example.org) represent all subdomains that would not exist otherwise. Therefore, if you have another subdomain entrysub.example.org, the wildcard domain is independent of it, because it only represents the other subdomains which do not have their own entries. Also, you can only have one layer of*---*.*.example.orgwould not work.
β³ Schedules, triggers, and timeouts
| Name | Valid Values | Meaning | Required? | Default Value | 
|---|---|---|---|---|
| CACHE_EXPIRATION | Positive time durations with a unit, such as 1hand10m. See time.ParseDuration | The expiration of cached Cloudflare API responses | No | 6h0m0s(6 hours) | 
| DELETE_ON_STOP | Boolean values, such as true,false,0and1. See strconv.ParseBool | Whether managed DNS records should be deleted on exit | No | false | 
| DETECTION_TIMEOUT | Positive time durations with a unit, such as 1hand10m. See time.ParseDuration | The timeout of each attempt to detect IP addresses | No | 5s(5 seconds) | 
| TZ | Recognized timezones, such as UTC | The timezone used for logging and parsing UPDATE_CRON | No | UTC | 
| UPDATE_CRON | Cron expressions. See the documentation of cron | The schedule to re-check IP addresses and update DNS records (if necessary) | No | @every 5m(every 5 minutes) | 
| UPDATE_ON_START | Boolean values, such as true,false,0and1. See strconv.ParseBool | Whether to check IP addresses on start regardless of UPDATE_CRON | No | true | 
| UPDATE_TIMEOUT | Positive time durations with a unit, such as 1hand10m. See time.ParseDuration | The timeout of each attempt to update DNS records, per domain, per record type | No | 30s(30 seconds) | 
β οΈ The update schedule does not take the time to update records into consideration. For example, if the schedule is βfor every 5 minutesβ, and if the updating itself takes 2 minutes, then the actual interval between adjacent updates is 3 minutes, not 5 minutes.
π£ Parameters of new DNS records
| Name | Valid Values | Meaning | Required? | Default Value | 
|---|---|---|---|---|
| PROXIED | Boolean values, such as true,false,0and1. See strconv.ParseBool. See below for experimental support of per-domain proxy settings. | Whether new DNS records should be proxied by Cloudflare | No | false | 
| TTL | Time-to-live (TTL) values in seconds | The TTL values used to create new DNS records | No | 1(This means βautomaticβ to Cloudflare) | 
π The updater will preserve existing proxy and TTL settings until it has to create new DNS records (or recreate deleted ones). Only when it creates DNS records, the above settings will apply. To change existing proxy and TTL settings now, you can go to your Cloudflare Dashboard and change them directly. If you think you have a use case where the updater should actively overwrite existing proxy and TTL settings in addition to IP addresses, please let me know. It is not hard to implement optional overwriting.
π§ͺ Experimental per-domain proxy settings (subject to changes):
The
PROXIEDcan be a boolean expression. Here are some examples:
PROXIED=is(example.org): proxy only the domainexample.org
PROXIED=is(example1.org) || sub(example2.org): proxy only the domainexample1.organd subdomains ofexample2.org
PROXIED=!is(example.org): proxy every managed domain except forexample.org
PROXIED=is(example1.org) || is(example2.org) || is(example3.org): proxy only the domainsexample1.org,example2.org, andexample3.orgA boolean expression has one of the following forms (all whitespace is ignored):
- A boolean value accepted by strconv.ParseBool, such as
tastrueorFALSEasfalse.
is(d)which matches the domaind. Note thatis(*.a)only matches the wildcard domain*.a; usesub(a)to match all subdomains ofa(including*.a).
sub(d)which matches subdomains ofd, such asa.dandb.d. It does not match the domainditself.
! ewhereeis a boolean expression, representing logical negation ofe.
e1 || e2wheree1ande2are boolean expressions, representing logical disjunction ofe1ande2.
e1 && e2wheree1ande2are boolean expressions, representing logical conjunction ofe1ande2.One can use parentheses to group expressions, such as
!(is(a) && (is(b) || is(c))). For convenience, the engine also accepts these short forms:
is(d1, d2, ..., dn)isis(d1) || is(d2) || ... || is(dn)
sub(d1, d2, ..., dn)issub(d1) || sub(d2) || ... || sub(dn)For example, these two settings are equivalent:
PROXYD=is(example1.org) || is(example2.org) || is(example3.org)
PROXIED=is(example1.org,example2.org,example3.org)
π‘οΈ Dropping superuser privileges
| Name | Valid Values | Meaning | Required? | Default Value | 
|---|---|---|---|---|
| PGID | Non-zero POSIX group ID | The group ID the updater should assume | No | Effective group ID; if it is zero, then the real group ID; if it is still zero, then 1000 | 
| PUID | Non-zero POSIX user ID | The user ID the updater should assume | No | Effective user ID; if it is zero, then the real user ID; if it is still zero, then 1000 | 
π The updater will also try to drop supplementary group IDs.
ποΈ Monitoring the updater
| Name | Valid Values | Meaning | Required? | Default Value | 
|---|---|---|---|---|
| QUIET | Boolean values, such as true,false,0and1. See strconv.ParseBool | Whether the updater should reduce the logging to the standard output | No | false | 
| HEALTHCHECKS | Healthchecks.io ping URLs, such as https://hc-ping.com/<uuid>orhttps://hc-ping.com/<project-ping-key>/<name-slug>(see below) | If set, the updater will ping the URL when it successfully updates IP addresses | No | (unset) | 
For HEALTHCHECKS, the updater accepts any URL that follows the same notification protocol.
π Restarting the Container
If you are using Docker Compose, run docker-compose up --detach after changing the settings.
If you are using Kubernetes, run kubectl replace -f cloudflare-ddns.yaml after changing the settings.
π΅ Migration Guides
(Click to expand the following items.)
I am migrating from oznu/cloudflare-ddns.
β οΈ oznu/cloudflare-ddns relies on unverified DNS responses to obtain public IP addresses; a malicious hacker could potentially manipulate or forge DNS responses and trick it into updating your domain with any IP address. In comparison, we use only verified responses from Cloudflare or ipify.
| Old Parameter | New Paramater | |
|---|---|---|
| API_KEY=key | βοΈ | Use CF_API_TOKEN=key | 
| API_KEY_FILE=file | βοΈ | Use CF_API_TOKEN_FILE=file | 
| ZONE=example.organdSUBDOMAIN=sub | βοΈ | Use DOMAINS=sub.example.orgdirectly | 
| PROXIED=true | βοΈ | Same | 
| RRTYPE=A | βοΈ | Both IPv4 and IPv6 are enabled by default; use IP6_PROVIDER=noneto disable IPv6 | 
| RRTYPE=AAAA | βοΈ | Both IPv4 and IPv6 are enabled by default; use IP4_PROVIDER=noneto disable IPv4 | 
| DELETE_ON_STOP=true | βοΈ | Same | 
| INTERFACE=iface | βοΈ | Not required for localproviders; we can handle multiple network interfaces | 
| CUSTOM_LOOKUP_CMD=cmd | β | There is not even a shell in the minimum Docker image | 
| DNS_SERVER=server | β | Only the secure Cloudflare and ipify are supported | 
I am migrating from timothymiller/cloudflare-ddns.
| Old JSON Key | New Paramater | |
|---|---|---|
| cloudflare.authentication.api_token | βοΈ | Use CF_API_TOKEN=key | 
| cloudflare.authentication.api_key | β | Use the newer, more secure API tokens | 
| cloudflare.zone_id | βοΈ | Not needed; automatically retrieved from the server | 
| cloudflare.subdomains[].name | βοΈ | Use DOMAINSwith fully qualified domain names (FQDNs); for example, if your zone isexample.organd your subdomain iswww, useDOMAINS=sub.example.org | 
| cloudflare.subdomains[].proxied | π§ͺ | (experimental) Write boolean expressions for PROXIEDto specify per-domain settings; see above for the detailed documentation for this experimental feature | 
| a | βοΈ | Both IPv4 and IPv6 are enabled by default; use IP4_PROVIDER=noneto disable IPv4 | 
| aaaa | βοΈ | Both IPv4 and IPv6 are enabled by default; use IP6_PROVIDER=noneto disable IPv6 | 
| proxied | βοΈ | Use PROXIED=trueorPROXIED=false | 
| purgeUnknownRecords | β | The updater never deletes unmanaged DNS records | 
π Feedback
Questions, suggestions, feature requests, and contributions are all welcome! Feel free to open a GitHub issue.