{"id":13436260,"url":"https://github.com/timothymiller/cloudflare-ddns","last_synced_at":"2026-04-01T18:56:01.418Z","repository":{"id":37579820,"uuid":"211715048","full_name":"timothymiller/cloudflare-ddns","owner":"timothymiller","description":"🦀 Rust based dynamic DNS updater for Cloudflare","archived":false,"fork":false,"pushed_at":"2026-03-25T19:09:39.000Z","size":647,"stargazers_count":4096,"open_issues_count":0,"forks_count":404,"subscribers_count":18,"default_branch":"master","last_synced_at":"2026-03-26T18:44:06.347Z","etag":null,"topics":["amd64","arm64","armv7","cloudflare-ddns","ddns-client","docker-image","dynamic-dns","ipv6","raspberry-pi","rust","self-hosted"],"latest_commit_sha":null,"homepage":"https://timknowsbest.com/free-dynamic-dns","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/timothymiller.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["timothymiller"]}},"created_at":"2019-09-29T19:28:31.000Z","updated_at":"2026-03-26T01:07:27.000Z","dependencies_parsed_at":"2024-05-29T12:13:35.896Z","dependency_job_id":"ad12de54-4def-4b77-8067-69c7ba8463bd","html_url":"https://github.com/timothymiller/cloudflare-ddns","commit_stats":{"total_commits":142,"total_committers":28,"mean_commits":5.071428571428571,"dds":0.5985915492957746,"last_synced_commit":"f0d9510fffb026012b68b27b2d35ab742b255696"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/timothymiller/cloudflare-ddns","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timothymiller%2Fcloudflare-ddns","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timothymiller%2Fcloudflare-ddns/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timothymiller%2Fcloudflare-ddns/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timothymiller%2Fcloudflare-ddns/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/timothymiller","download_url":"https://codeload.github.com/timothymiller/cloudflare-ddns/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timothymiller%2Fcloudflare-ddns/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31290979,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["amd64","arm64","armv7","cloudflare-ddns","ddns-client","docker-image","dynamic-dns","ipv6","raspberry-pi","rust","self-hosted"],"created_at":"2024-07-31T03:00:45.989Z","updated_at":"2026-04-01T18:56:01.407Z","avatar_url":"https://github.com/timothymiller.png","language":"Rust","funding_links":["https://github.com/sponsors/timothymiller"],"categories":["Rust","HarmonyOS","Python","self-hosted"],"sub_categories":["Windows Manager"],"readme":"\u003cp align=\"center\"\u003e\u003ca href=\"https://timknowsbest.com/free-dynamic-dns\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003cimg width=\"1024\" src=\"feature-graphic.jpg\" alt=\"Cloudflare DDNS\"/\u003e\u003c/a\u003e\u003c/p\u003e\n\n# 🌍 Cloudflare DDNS\n\nAccess your home network remotely via a custom domain name without a static IP!\n\nA feature-complete dynamic DNS client for Cloudflare, written in Rust. The **smallest and most memory-efficient** open-source Cloudflare DDNS Docker image available — **~1.1 MB image size** and **~3.5 MB RAM** at runtime, smaller and leaner than Go-based alternatives. Built as a fully static binary from scratch with zero runtime dependencies.\n\nConfigure everything with environment variables. Supports notifications, heartbeat monitoring, WAF list management, flexible scheduling, and more.\n\n[![Docker Pulls](https://img.shields.io/docker/pulls/timothyjmiller/cloudflare-ddns?style=flat\u0026logo=docker\u0026label=pulls)](https://hub.docker.com/r/timothyjmiller/cloudflare-ddns) [![Docker Image Size](https://img.shields.io/docker/image-size/timothyjmiller/cloudflare-ddns/latest?style=flat\u0026logo=docker\u0026label=image%20size)](https://hub.docker.com/r/timothyjmiller/cloudflare-ddns)\n\n## ✨ Features\n\n- 🔍 **Multiple IP detection providers** — Cloudflare Trace, Cloudflare DNS-over-HTTPS, ipify, local interface, custom URL, or static IPs\n- 📡 **IPv4 and IPv6** — Full dual-stack support with independent provider configuration\n- 🌐 **Multiple domains and zones** — Update any number of domains across multiple Cloudflare zones\n- 🃏 **Wildcard domains** — Support for `*.example.com` records\n- 🌍 **Internationalized domain names** — Full IDN/punycode support (e.g. `münchen.de`)\n- 🛡️ **WAF list management** — Automatically update Cloudflare WAF IP lists\n- 🔔 **Notifications** — Shoutrrr-compatible notifications (Discord, Slack, Telegram, Gotify, Pushover, generic webhooks)\n- 💓 **Heartbeat monitoring** — Healthchecks.io and Uptime Kuma integration\n- ⏱️ **Cron scheduling** — Flexible update intervals via cron expressions\n- 🧪 **Dry-run mode** — Preview changes without modifying DNS records\n- 🧹 **Graceful shutdown** — Signal handling (SIGINT/SIGTERM) with optional DNS record cleanup\n- 💬 **Record comments** — Tag managed records with comments for identification\n- 🎯 **Managed record regex** — Control which records the tool manages via regex matching\n- 🎨 **Pretty output with emoji** — Configurable emoji and verbosity levels\n- 🔒 **Zero-log IP detection** — Uses Cloudflare's [cdn-cgi/trace](https://www.cloudflare.com/cdn-cgi/trace) by default\n- 🏠 **CGNAT-aware local detection** — Filters out shared address space (100.64.0.0/10) and private ranges\n- 🚫 **Cloudflare IP rejection** — Automatically rejects Cloudflare anycast IPs to prevent incorrect DNS updates\n- 🤏 **Tiny static binary** — ~1.1 MB Docker image built from scratch, zero runtime dependencies\n\n## 🚀 Quick Start\n\n```bash\ndocker run -d \\\n  --name cloudflare-ddns \\\n  --restart unless-stopped \\\n  --network host \\\n  -e CLOUDFLARE_API_TOKEN=your-api-token \\\n  -e DOMAINS=example.com,www.example.com \\\n  timothyjmiller/cloudflare-ddns:latest\n```\n\nThat's it. The container detects your public IP and updates the DNS records for your domains every 5 minutes.\n\n\u003e ⚠️ `--network host` is required to detect IPv6 addresses. If you only need IPv4, you can omit it and set `IP6_PROVIDER=none`.\n\n## 🔑 Authentication\n\n| Variable | Description |\n|----------|-------------|\n| `CLOUDFLARE_API_TOKEN` | API token with \"Edit DNS\" capability |\n| `CLOUDFLARE_API_TOKEN_FILE` | Path to a file containing the API token (Docker secrets compatible) |\n\nTo generate an API token, go to your [Cloudflare Profile](https://dash.cloudflare.com/profile/api-tokens) and create a token capable of **Edit DNS**.\n\n## 🌐 Domains\n\n| Variable | Description |\n|----------|-------------|\n| `DOMAINS` | Comma-separated list of domains to update for both IPv4 and IPv6 |\n| `IP4_DOMAINS` | Comma-separated list of IPv4-only domains |\n| `IP6_DOMAINS` | Comma-separated list of IPv6-only domains |\n\nWildcard domains are supported: `*.example.com`\n\nAt least one of `DOMAINS`, `IP4_DOMAINS`, `IP6_DOMAINS`, or `WAF_LISTS` must be set.\n\n## 🔍 IP Detection Providers\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `IP4_PROVIDER` | `ipify` | IPv4 detection method |\n| `IP6_PROVIDER` | `cloudflare.trace` | IPv6 detection method |\n\nAvailable providers:\n\n| Provider | Description |\n|----------|-------------|\n| `cloudflare.trace` | 🔒 Cloudflare's `/cdn-cgi/trace` endpoint (default, zero-log) |\n| `cloudflare.doh` | 🌐 Cloudflare DNS-over-HTTPS (`whoami.cloudflare` TXT query) |\n| `ipify` | 🌎 ipify.org API |\n| `local` | 🏠 Local IP via system routing table (no network traffic, CGNAT-aware) |\n| `local.iface:\u003cname\u003e` | 🔌 IP from a specific network interface (e.g., `local.iface:eth0`) |\n| `url:\u003curl\u003e` | 🔗 Custom HTTP(S) endpoint that returns an IP address |\n| `literal:\u003cips\u003e` | 📌 Static IP addresses (comma-separated) |\n| `none` | 🚫 Disable this IP type |\n\n## 🚫 Cloudflare IP Rejection\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `REJECT_CLOUDFLARE_IPS` | `true` | Reject detected IPs that fall within Cloudflare's IP ranges |\n\nSome IP detection providers occasionally return a Cloudflare anycast IP instead of your real public IP. When this happens, your DNS record gets updated to point at Cloudflare infrastructure rather than your actual address.\n\nBy default, each update cycle fetches [Cloudflare's published IP ranges](https://www.cloudflare.com/ips/) and skips any detected IP that falls within them. A warning is logged for every rejected IP. If the ranges cannot be fetched, the update is skipped entirely to prevent writing a Cloudflare IP.\n\nTo disable this protection, set `REJECT_CLOUDFLARE_IPS=false`.\n\n## ⏱️ Scheduling\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `UPDATE_CRON` | `@every 5m` | Update schedule |\n| `UPDATE_ON_START` | `true` | Run an update immediately on startup |\n| `DELETE_ON_STOP` | `false` | Delete managed DNS records on shutdown |\n\nSchedule formats:\n\n- `@every 5m` — Every 5 minutes\n- `@every 1h` — Every hour\n- `@every 30s` — Every 30 seconds\n- `@once` — Run once and exit\n\nWhen `UPDATE_CRON=@once`, `UPDATE_ON_START` must be `true` and `DELETE_ON_STOP` must be `false`.\n\n## 📝 DNS Record Settings\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `TTL` | `1` (auto) | DNS record TTL in seconds (1=auto, or 30-86400) |\n| `PROXIED` | `false` | Expression controlling which domains are proxied through Cloudflare |\n| `RECORD_COMMENT` | (empty) | Comment attached to managed DNS records |\n| `MANAGED_RECORDS_COMMENT_REGEX` | (empty) | Regex to identify which records are managed (empty = all) |\n\nThe `PROXIED` variable supports boolean expressions:\n\n| Expression | Meaning |\n|------------|---------|\n| `true` | ☁️ Proxy all domains |\n| `false` | 🔓 Don't proxy any domains |\n| `is(example.com)` | 🎯 Only proxy `example.com` |\n| `sub(cdn.example.com)` | 🌳 Proxy `cdn.example.com` and its subdomains |\n| `is(a.com) \\|\\| is(b.com)` | 🔀 Proxy `a.com` or `b.com` |\n| `!is(vpn.example.com)` | 🚫 Proxy everything except `vpn.example.com` |\n\nOperators: `is()`, `sub()`, `!`, `\u0026\u0026`, `||`, `()`\n\n## 🛡️ WAF Lists\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `WAF_LISTS` | (empty) | Comma-separated WAF lists in `account-id/list-name` format |\n| `WAF_LIST_DESCRIPTION` | (empty) | Description for managed WAF lists |\n| `WAF_LIST_ITEM_COMMENT` | (empty) | Comment for WAF list items |\n| `MANAGED_WAF_LIST_ITEMS_COMMENT_REGEX` | (empty) | Regex to identify managed WAF list items |\n\nWAF list names must match the pattern `[a-z0-9_]+`.\n\n## 🔔 Notifications (Shoutrrr)\n\n| Variable | Description |\n|----------|-------------|\n| `SHOUTRRR` | Newline-separated list of notification service URLs |\n\nSupported services:\n\n| Service | URL format |\n|---------|------------|\n| 💬 Discord | `discord://token@webhook-id` |\n| 📨 Slack | `slack://token-a/token-b/token-c` |\n| ✈️ Telegram | `telegram://bot-token@telegram?chats=chat-id` |\n| 📡 Gotify | `gotify://host/path?token=app-token` |\n| 📲 Pushover | `pushover://user-key@api-token` |\n| 🌐 Generic webhook | `generic://host/path` or `generic+https://host/path` |\n\nNotifications are sent when DNS records are updated, created, deleted, or when errors occur.\n\n## 💓 Heartbeat Monitoring\n\n| Variable | Description |\n|----------|-------------|\n| `HEALTHCHECKS` | Healthchecks.io ping URL |\n| `UPTIMEKUMA` | Uptime Kuma push URL |\n\nHeartbeats are sent after each update cycle. On failure, a fail signal is sent. On shutdown, an exit signal is sent.\n\n## ⏳ Timeouts\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `DETECTION_TIMEOUT` | `5s` | Timeout for IP detection requests |\n| `UPDATE_TIMEOUT` | `30s` | Timeout for Cloudflare API requests |\n\n## 🖥️ Output\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `EMOJI` | `true` | Use emoji in output messages |\n| `QUIET` | `false` | Suppress informational output |\n\n## 🏁 CLI Flags\n\n| Flag | Description |\n|------|-------------|\n| `--dry-run` | 🧪 Preview changes without modifying DNS records |\n| `--repeat` | 🔁 Run continuously (legacy config mode only; env var mode uses `UPDATE_CRON`) |\n\n## 📋 All Environment Variables\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `CLOUDFLARE_API_TOKEN` | — | 🔑 API token |\n| `CLOUDFLARE_API_TOKEN_FILE` | — | 📄 Path to API token file |\n| `DOMAINS` | — | 🌐 Domains for both IPv4 and IPv6 |\n| `IP4_DOMAINS` | — | 4️⃣ IPv4-only domains |\n| `IP6_DOMAINS` | — | 6️⃣ IPv6-only domains |\n| `IP4_PROVIDER` | `ipify` | 🔍 IPv4 detection provider |\n| `IP6_PROVIDER` | `cloudflare.trace` | 🔍 IPv6 detection provider |\n| `UPDATE_CRON` | `@every 5m` | ⏱️ Update schedule |\n| `UPDATE_ON_START` | `true` | 🚀 Update on startup |\n| `DELETE_ON_STOP` | `false` | 🧹 Delete records on shutdown |\n| `TTL` | `1` | ⏳ DNS record TTL |\n| `PROXIED` | `false` | ☁️ Proxied expression |\n| `RECORD_COMMENT` | — | 💬 DNS record comment |\n| `MANAGED_RECORDS_COMMENT_REGEX` | — | 🎯 Managed records regex |\n| `WAF_LISTS` | — | 🛡️ WAF lists to manage |\n| `WAF_LIST_DESCRIPTION` | — | 📝 WAF list description |\n| `WAF_LIST_ITEM_COMMENT` | — | 💬 WAF list item comment |\n| `MANAGED_WAF_LIST_ITEMS_COMMENT_REGEX` | — | 🎯 Managed WAF items regex |\n| `DETECTION_TIMEOUT` | `5s` | ⏳ IP detection timeout |\n| `UPDATE_TIMEOUT` | `30s` | ⏳ API request timeout |\n| `REJECT_CLOUDFLARE_IPS` | `true` | 🚫 Reject Cloudflare anycast IPs |\n| `EMOJI` | `true` | 🎨 Enable emoji output |\n| `QUIET` | `false` | 🤫 Suppress info output |\n| `HEALTHCHECKS` | — | 💓 Healthchecks.io URL |\n| `UPTIMEKUMA` | — | 💓 Uptime Kuma URL |\n| `SHOUTRRR` | — | 🔔 Notification URLs (newline-separated) |\n\n---\n\n## 🚢 Deployment\n\n### 🐳 Docker Compose\n\n```yml\nversion: '3.9'\nservices:\n  cloudflare-ddns:\n    image: timothyjmiller/cloudflare-ddns:latest\n    container_name: cloudflare-ddns\n    security_opt:\n      - no-new-privileges:true\n    network_mode: 'host'\n    environment:\n      - CLOUDFLARE_API_TOKEN=your-api-token\n      - DOMAINS=example.com,www.example.com\n      - PROXIED=true\n      - IP6_PROVIDER=none\n      - HEALTHCHECKS=https://hc-ping.com/your-uuid\n    restart: unless-stopped\n```\n\n\u003e ⚠️ Docker requires `network_mode: host` to access the IPv6 public address.\n\n### ☸️ Kubernetes\n\nThe included manifest uses the legacy JSON config mode. Create a secret containing your `config.json` and apply:\n\n```bash\nkubectl create secret generic config-cloudflare-ddns --from-file=config.json -n ddns\nkubectl apply -f k8s/cloudflare-ddns.yml\n```\n\n### 🐧 Linux + Systemd\n\n1. Build and install:\n\n```bash\ncargo build --release\nsudo cp target/release/cloudflare-ddns /usr/local/bin/\n```\n\n2. Copy the systemd units from the `systemd/` directory:\n\n```bash\nsudo cp systemd/cloudflare-ddns.service /etc/systemd/system/\nsudo cp systemd/cloudflare-ddns.timer /etc/systemd/system/\n```\n\n3. Place a `config.json` at `/etc/cloudflare-ddns/config.json` (the systemd service uses legacy config mode).\n\n4. Enable the timer:\n\n```bash\nsudo systemctl enable --now cloudflare-ddns.timer\n```\n\nThe timer runs the service every 15 minutes (configurable in `cloudflare-ddns.timer`).\n\n## 🔨 Building from Source\n\n```bash\ncargo build --release\n```\n\nThe binary is at `target/release/cloudflare-ddns`.\n\n### 🐳 Docker builds\n\n```bash\n# Single architecture (linux/amd64)\n./scripts/docker-build.sh\n\n# Multi-architecture (linux/amd64, linux/arm64, linux/ppc64le)\n./scripts/docker-build-all.sh\n```\n\n## 💻 Supported Platforms\n\n- 🐳 [Docker](https://docs.docker.com/get-docker/) (amd64, arm64, ppc64le)\n- 🐙 [Docker Compose](https://docs.docker.com/compose/install/)\n- ☸️ [Kubernetes](https://kubernetes.io/docs/tasks/tools/)\n- 🐧 [Systemd](https://www.freedesktop.org/wiki/Software/systemd/)\n- 🍎 macOS, 🪟 Windows, 🐧 Linux — anywhere Rust compiles\n\n---\n\n## 📁 Legacy JSON Config File\n\nFor backwards compatibility, cloudflare-ddns still supports configuration via a `config.json` file. This mode is used automatically when no `CLOUDFLARE_API_TOKEN` environment variable is set.\n\n### 🚀 Quick Start\n\n```bash\ncp config-example.json config.json\n# Edit config.json with your values\ncloudflare-ddns\n```\n\n### 🔑 Authentication\n\nUse either an API token (recommended) or a legacy API key:\n\n```json\n\"authentication\": {\n  \"api_token\": \"Your cloudflare API token with Edit DNS capability\"\n}\n```\n\nOr with a legacy API key:\n\n```json\n\"authentication\": {\n  \"api_key\": {\n    \"api_key\": \"Your cloudflare API Key\",\n    \"account_email\": \"The email address you use to sign in to cloudflare\"\n  }\n}\n```\n\n### 📡 IPv4 and IPv6\n\nSome ISP provided modems only allow port forwarding over IPv4 or IPv6. Disable the interface that is not accessible:\n\n```json\n\"a\": true,\n\"aaaa\": true\n```\n\n### ⚙️ Config Options\n\nBy default, the legacy config file is loaded from `./config.json`. Set the `CONFIG_PATH` environment variable to change the directory:\n\n```bash\nCONFIG_PATH=/etc/cloudflare-ddns cloudflare-ddns\n```\n\nOr in Docker Compose:\n\n```yml\nenvironment:\n  - CONFIG_PATH=/config\nvolumes:\n  - /your/path/config.json:/config/config.json\n```\n\n| Key | Type | Default | Description |\n|-----|------|---------|-------------|\n| `cloudflare` | array | required | List of zone configurations |\n| `a` | bool | `true` | Enable IPv4 (A record) updates |\n| `aaaa` | bool | `true` | Enable IPv6 (AAAA record) updates |\n| `purgeUnknownRecords` | bool | `false` | Delete stale/duplicate DNS records |\n| `ttl` | int | `300` | DNS record TTL in seconds (30-86400, values \u003c 30 become auto) |\n| `ip4_provider` | string | `\"cloudflare.trace\"` | IPv4 detection provider (same values as `IP4_PROVIDER` env var) |\n| `ip6_provider` | string | `\"cloudflare.trace\"` | IPv6 detection provider (same values as `IP6_PROVIDER` env var) |\n\n### 🚫 Cloudflare IP Rejection (Legacy Mode)\n\nCloudflare IP rejection is enabled by default in legacy mode too. To disable it, set `REJECT_CLOUDFLARE_IPS=false` alongside your `config.json`:\n\n```bash\nREJECT_CLOUDFLARE_IPS=false cloudflare-ddns\n```\n\nOr in Docker Compose:\n\n```yml\nenvironment:\n  - REJECT_CLOUDFLARE_IPS=false\nvolumes:\n  - ./config.json:/config.json\n```\n\n### 🔍 IP Detection (Legacy Mode)\n\nLegacy mode now uses the same shared provider abstraction as environment variable mode. By default it uses the `cloudflare.trace` provider, which builds an IP-family-bound HTTP client (`0.0.0.0` for IPv4, `[::]` for IPv6) to guarantee the correct address family on dual-stack hosts.\n\nYou can override the detection method per address family with `ip4_provider` and `ip6_provider` in your `config.json`. Supported values are the same as the `IP4_PROVIDER` / `IP6_PROVIDER` environment variables: `cloudflare.trace`, `cloudflare.doh`, `ipify`, `local`, `local.iface:\u003cname\u003e`, `url:\u003chttps://...\u003e`, `none`.\n\nSet a provider to `\"none\"` to disable detection for that address family (overrides `a`/`aaaa`):\n\n```json\n{\n  \"a\": true,\n  \"aaaa\": true,\n  \"ip4_provider\": \"cloudflare.trace\",\n  \"ip6_provider\": \"none\"\n}\n```\n\nEach zone entry contains:\n\n| Key | Type | Description |\n|-----|------|-------------|\n| `authentication` | object | API token or API key credentials |\n| `zone_id` | string | Cloudflare zone ID (found in zone dashboard) |\n| `subdomains` | array | Subdomain entries to update |\n| `proxied` | bool | Default proxied status for subdomains in this zone |\n\nSubdomain entries can be a simple string or a detailed object:\n\n```json\n\"subdomains\": [\n  \"\",\n  \"@\",\n  \"www\",\n  { \"name\": \"vpn\", \"proxied\": true }\n]\n```\n\nUse `\"\"` or `\"@\"` for the root domain. Do not include the base domain name.\n\n### 🔄 Environment Variable Substitution\n\nIn the legacy config file, values can reference environment variables with the `CF_DDNS_` prefix:\n\n```json\n{\n  \"cloudflare\": [{\n    \"authentication\": {\n      \"api_token\": \"${CF_DDNS_API_TOKEN}\"\n    },\n    ...\n  }]\n}\n```\n\n### 📠 Example: Multiple Subdomains\n\n```json\n{\n  \"cloudflare\": [\n    {\n      \"authentication\": {\n        \"api_token\": \"your-api-token\"\n      },\n      \"zone_id\": \"your_zone_id\",\n      \"subdomains\": [\n        { \"name\": \"\", \"proxied\": true },\n        { \"name\": \"www\", \"proxied\": true },\n        { \"name\": \"vpn\", \"proxied\": false }\n      ]\n    }\n  ],\n  \"a\": true,\n  \"aaaa\": true,\n  \"purgeUnknownRecords\": false,\n  \"ttl\": 300\n}\n```\n\n### 🌐 Example: Multiple Zones\n\n```json\n{\n  \"cloudflare\": [\n    {\n      \"authentication\": { \"api_token\": \"your-api-token\" },\n      \"zone_id\": \"first_zone_id\",\n      \"subdomains\": [\n        { \"name\": \"\", \"proxied\": false }\n      ]\n    },\n    {\n      \"authentication\": { \"api_token\": \"your-api-token\" },\n      \"zone_id\": \"second_zone_id\",\n      \"subdomains\": [\n        { \"name\": \"\", \"proxied\": false }\n      ]\n    }\n  ],\n  \"a\": true,\n  \"aaaa\": true,\n  \"purgeUnknownRecords\": false\n}\n```\n\n### 🐳 Docker Compose (legacy config file)\n\n```yml\nversion: '3.9'\nservices:\n  cloudflare-ddns:\n    image: timothyjmiller/cloudflare-ddns:latest\n    container_name: cloudflare-ddns\n    security_opt:\n      - no-new-privileges:true\n    network_mode: 'host'\n    volumes:\n      - /YOUR/PATH/HERE/config.json:/config.json\n    restart: unless-stopped\n```\n\n### 🏁 Legacy CLI Flags\n\nIn legacy config mode, use `--repeat` to run continuously (the TTL value is used as the update interval):\n\n```bash\ncloudflare-ddns --repeat\ncloudflare-ddns --repeat --dry-run\n```\n\n---\n\n## 🔗 Helpful Links\n\n- 🔑 [Cloudflare API token](https://dash.cloudflare.com/profile/api-tokens)\n- 🆔 [Cloudflare zone ID](https://support.cloudflare.com/hc/en-us/articles/200167836-Where-do-I-find-my-Cloudflare-IP-address-)\n- 📋 [Cloudflare zone DNS record ID](https://support.cloudflare.com/hc/en-us/articles/360019093151-Managing-DNS-records-in-Cloudflare)\n\n## 📜 License\n\nThis project is licensed under the GNU General Public License, version 3 (GPLv3).\n\n## 👨‍💻 Author\n\nTimothy Miller\n\n[View my GitHub profile 💡](https://github.com/timothymiller)\n\n[View my personal website 💻](https://itstmillertime.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimothymiller%2Fcloudflare-ddns","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftimothymiller%2Fcloudflare-ddns","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimothymiller%2Fcloudflare-ddns/lists"}