A lightweight Cloudflare Dynamic DNS shell script.
- Dual Stack Support: Support for both IPv4 and IPv6;
- Multi-Record Support: Support for updating multiple records simultaneously;
- Smart Monitoring: Only updates DNS records when IP address changes;
- Auto Caching: Automatically caches DNS records and zone information for improved performance;
- Multiple Authentication Support: Support for both Cloudflare API Token and Legacy API Key authentication;
- Proxy Protocol Support: Support for configuring Socks proxy for API requests;
- Systemd Support: Provides service/timer examples and dynamic user support;
- Telegram Push: Highly readable Telegram notification push;
- CSV Logging: Automatic logging of DNS updates to CSV file for history tracking and analysis;
- Hook Commands: Execute custom commands when IPv4 or IPv6 addresses change;
- Nix Package: Easy installation through Nix package manager with all dependencies included;
- Flexible Configuration: Support for command line parameter passing and environment variable configuration;
Install directly from this repository using Nix flakes:
nix profile install github:fernvenue/cloudflare-ddnsAfter installation, you can run the script using:
cloudflare-ddns --helpThe Nix package automatically includes all required dependencies (curl, jq, bash, etc.) and ensures they are available at runtime.
Get the script:
curl -o /usr/local/bin/cloudflare-ddns.sh https://raw.githubusercontent.com/fernvenue/cloudflare-ddns/refs/heads/master/cloudflare-ddns.sh
chmod +x /usr/local/bin/cloudflare-ddns.shMake sure the corresponding domain already has records before running, otherwise the script cannot find the records to update.
For detailed help information, you can use:
cloudflare-ddns --help
# or if using manual installation:
./cloudflare-ddns.sh --helpCLOUDFLARE_API_TOKEN: API Token of your Cloudflare account (recommended);CLOUDFLARE_API_KEY: Global API Key of your Cloudflare account;CLOUDFLARE_RECORD_NAMES: Target record names, such asddns.example.comorddns01.example.com,ddns02.example.com;CLOUDFLARE_RECORD_TYPES: Record types, can be4(A) or6(AAAA), corresponds one-to-one with record names, such as4,6,4;CLOUDFLARE_USER_MAIL: The email address of your Cloudflare account;CLOUDFLARE_ZONE_NAME: Zone name, such asexample.com;OUTBOUND_INTERFACE: Optional, used to specify the network interface;SOCKS_ADDR: Optional, used to configure Socks proxy for API requests (Cloudflare and Telegram), IP detection does not go through this proxy;SOCKS_PORT: Optional, corresponding Socks proxy port;TELEGRAM_BOT_ID: Optional, Telegram bot ID;TELEGRAM_CHAT_ID: Optional, Telegram target chat for push notifications;CUSTOM_TELEGRAM_ENDPOINT: Optional, used to customize the API domain used for Telegram push;ENABLE_CSV_LOG: Optional, enable CSV logging (default:true), set tofalseto disable;IPV4_HOOK_COMMAND: Optional, command to execute when IPv4 address changes;IPV6_HOOK_COMMAND: Optional, command to execute when IPv6 address changes;FORCE_UPDATE: Force update, update DNS records even if IP hasn't changed;
--cloudflare-api-token TOKEN=$CLOUDFLARE_API_TOKEN--cloudflare-api-key KEY=$CLOUDFLARE_API_KEY--cloudflare-record-names NAMES=$CLOUDFLARE_RECORD_NAMES--cloudflare-record-types TYPES=$CLOUDFLARE_RECORD_TYPES--cloudflare-user-mail EMAIL=$CLOUDFLARE_USER_MAIL--cloudflare-zone-name NAME=$CLOUDFLARE_ZONE_NAME--outbound-interface IFACE=$OUTBOUND_INTERFACE--socks-addr ADDR=$SOCKS_ADDR--socks-port PORT=$SOCKS_PORT--telegram-bot-id ID=$TELEGRAM_BOT_ID--telegram-chat-id ID=$TELEGRAM_CHAT_ID--custom-telegram-endpoint DOMAIN=$CUSTOM_TELEGRAM_ENDPOINT--enable-csv-log BOOL=$ENABLE_CSV_LOG--ipv4-hook-command COMMAND=$IPV4_HOOK_COMMAND--ipv6-hook-command COMMAND=$IPV6_HOOK_COMMAND--force-update=$FORCE_UPDATE
Refer to cloudflare-ddns.service and cloudflare-ddns.timer for standard systemd service and systemd timer examples.
The script automatically logs all DNS record updates to a CSV file for history tracking and analysis. This feature is enabled by default.
CSV File Location: The CSV file history.csv is created in the same directory as the configuration data:
- If
STATE_DIRECTORYis set:$STATE_DIRECTORY/history.csv - If
/var/libis writable:/var/lib/cloudflare-ddns/history.csv - If
$HOMEis available:$HOME/.cache/cloudflare-ddns/history.csv - Fallback:
/tmp/cloudflare-ddns/history.csv
CSV Format: The CSV file contains the following columns:
Timestamp: RFC3339 formatted timestamp of the updateZone Name: Cloudflare zone name (e.g.,example.com)Record Name: DNS record name (e.g.,ddns.example.com)Record Type: DNS record type (AorAAAA)Old IP: Previous IP address (empty for new records)New IP: New IP address after updateBackup API Used: Whether backup IP detection service was used (true/false)
Example CSV content:
Timestamp,Zone Name,Record Name,Record Type,Old IP,New IP,Backup API Used
2025-07-09T10:30:45+08:00,example.com,ddns.example.com,A,192.168.1.100,203.0.113.42,false
2025-07-09T10:30:45+08:00,example.com,ddns.example.com,AAAA,,2001:db8::1,false
Disable CSV Logging: To disable CSV logging, set the environment variable ENABLE_CSV_LOG=false or use the command line option --enable-csv-log false.
The script supports executing custom commands (hooks) when IP addresses change. This allows you to trigger additional actions when your IPv4 or IPv6 address updates.
Hook Types:
- IPv4 Hook: Executed when any IPv4 (A record) address changes
- IPv6 Hook: Executed when any IPv6 (AAAA record) address changes
Configuration:
- Set via environment variables:
IPV4_HOOK_COMMANDandIPV6_HOOK_COMMAND - Set via command line:
--ipv4-hook-commandand--ipv6-hook-command
Hook Environment Variables: When a hook is executed, the script provides the following environment variables:
CLOUDFLARE_DDNS_IP_VERSION: IP version ("4" for IPv4, "6" for IPv6)CLOUDFLARE_DDNS_OLD_IP: Previous IP addressCLOUDFLARE_DDNS_NEW_IP: New IP address after updateCLOUDFLARE_DDNS_ZONE_NAME: Cloudflare zone nameCLOUDFLARE_DDNS_RECORD_NAMES: Comma-separated list of updated record namesCLOUDFLARE_DDNS_TIMESTAMP: RFC3339 formatted timestamp of the update
Hook Execution Behavior:
- Hooks run after DNS records are successfully updated
- Hook output is suppressed (redirected to
/dev/null) - Hook failures do not affect the main script execution
- Only success/failure status is logged
- Hooks execute in parallel for different IP versions
Example Hook Commands:
Update Hurricane Electric DNS:
export IPV4_HOOK_COMMAND="curl -s -4 'https://dyn.dns.he.net/nic/update?hostname=example.com&password=your-password&myip=\$CLOUDFLARE_DDNS_NEW_IP'"Send webhook notification:
export IPV4_HOOK_COMMAND="curl -X POST https://webhook.example.com/ip-changed -H 'Content-Type: application/json' -d '{\"ip\":\"\$CLOUDFLARE_DDNS_NEW_IP\",\"type\":\"ipv4\"}'"Execute custom script:
export IPV4_HOOK_COMMAND="/path/to/your/script.sh"
export IPV6_HOOK_COMMAND="/path/to/your/ipv6-script.sh"Update multiple services:
export IPV4_HOOK_COMMAND="curl -s 'https://service1.com/update?ip=\$CLOUDFLARE_DDNS_NEW_IP' && curl -s 'https://service2.com/api/ip' -d 'ip=\$CLOUDFLARE_DDNS_NEW_IP'"The script requires the following tools to be installed on your system:
- curl - For sending HTTP requests to Cloudflare API and IP detection services;
- jq - For JSON parsing and configuration file operations;
- awk - For text processing (usually pre-installed);
- grep - For pattern matching (usually pre-installed);
- date - For timestamp generation (usually pre-installed);
The following examples are some common use cases, for reference only. For production environment deployment, it is recommended to use with systemd service and systemd timer.
Update a single A record with API token:
./cloudflare-ddns.sh \
--cloudflare-api-token "your-cloudflare-api-token" \
--cloudflare-user-mail "your-email@example.com" \
--cloudflare-zone-name "example.com" \
--cloudflare-record-names "ddns.example.com" \
--cloudflare-record-types "4"Update a single AAAA record with API token:
./cloudflare-ddns.sh \
--cloudflare-api-token "your-cloudflare-api-token" \
--cloudflare-user-mail "your-email@example.com" \
--cloudflare-zone-name "example.com" \
--cloudflare-record-names "ipv6.example.com" \
--cloudflare-record-types "6"Update both A and AAAA records for the same domain (note the repeated domain name):
./cloudflare-ddns.sh \
--cloudflare-api-token "your-cloudflare-api-token" \
--cloudflare-user-mail "your-email@example.com" \
--cloudflare-zone-name "example.com" \
--cloudflare-record-names "ddns.example.com,ddns.example.com" \
--cloudflare-record-types "4,6"In this example:
- First
ddns.example.comgets A record (IPv4) - Second
ddns.example.comgets AAAA record (IPv6)
Update multiple records with the same type (IPv4 only):
./cloudflare-ddns.sh \
--cloudflare-api-token "your-cloudflare-api-token" \
--cloudflare-user-mail "your-email@example.com" \
--cloudflare-zone-name "example.com" \
--cloudflare-record-names "ddns.example.com,api.example.com,home.example.com" \
--cloudflare-record-types "4,4,4"Update different records with different types (each record name corresponds to each record type):
./cloudflare-ddns.sh \
--cloudflare-api-token "your-cloudflare-api-token" \
--cloudflare-user-mail "your-email@example.com" \
--cloudflare-zone-name "example.com" \
--cloudflare-record-names "api.example.com,ipv6.example.com,home.example.com" \
--cloudflare-record-types "4,6,4"In this example:
api.example.comgets A record (IPv4)ipv6.example.comgets AAAA record (IPv6)home.example.comgets A record (IPv4)
export CLOUDFLARE_API_TOKEN="your-cloudflare-api-token"
export CLOUDFLARE_USER_MAIL="your-email@example.com"
export CLOUDFLARE_ZONE_NAME="example.com"
export CLOUDFLARE_RECORD_NAMES="api.example.com,ipv6.example.com,home.example.com"
export CLOUDFLARE_RECORD_TYPES="4,6,4"
./cloudflare-ddns.sh./cloudflare-ddns.sh \
--cloudflare-api-token "your-cloudflare-api-token" \
--cloudflare-user-mail "your-email@example.com" \
--cloudflare-zone-name "example.com" \
--cloudflare-record-names "ddns.example.com" \
--cloudflare-record-types "4" \
--telegram-bot-id "123456789:ABCdefGHIjklMNOpqrsTUVwxyz" \
--telegram-chat-id "-1001234567890"Use custom Telegram API domain:
./cloudflare-ddns.sh \
--cloudflare-api-token "your-cloudflare-api-token" \
--cloudflare-user-mail "your-email@example.com" \
--cloudflare-zone-name "example.com" \
--cloudflare-record-names "ddns.example.com" \
--cloudflare-record-types "4" \
--telegram-bot-id "123456789:ABCdefGHIjklMNOpqrsTUVwxyz" \
--telegram-chat-id "-1001234567890" \
--custom-telegram-endpoint "my-telegram-api.example.com"Use SOCKS proxy for API requests:
./cloudflare-ddns.sh \
--cloudflare-api-token "your-cloudflare-api-token" \
--cloudflare-user-mail "your-email@example.com" \
--cloudflare-zone-name "example.com" \
--cloudflare-record-names "ddns.example.com" \
--cloudflare-record-types "4" \
--socks-addr "[::1]" \
--socks-port "1080"Force update even if IP address hasn't changed:
./cloudflare-ddns.sh \
--cloudflare-api-token "your-cloudflare-api-token" \
--cloudflare-user-mail "your-email@example.com" \
--cloudflare-zone-name "example.com" \
--cloudflare-record-names "ddns.example.com" \
--cloudflare-record-types "4" \
--force-updateDisable CSV logging for DNS updates:
./cloudflare-ddns.sh \
--cloudflare-api-token "your-cloudflare-api-token" \
--cloudflare-user-mail "your-email@example.com" \
--cloudflare-zone-name "example.com" \
--cloudflare-record-names "ddns.example.com" \
--cloudflare-record-types "4" \
--enable-csv-log falseExecute custom commands when IP addresses change:
./cloudflare-ddns.sh \
--cloudflare-api-token "your-cloudflare-api-token" \
--cloudflare-user-mail "your-email@example.com" \
--cloudflare-zone-name "example.com" \
--cloudflare-record-names "ddns.example.com,ddns.example.com" \
--cloudflare-record-types "4,6" \
--ipv4-hook-command "curl -s -4 'https://dyn.dns.he.net/nic/update?hostname=ddns.example.com&password=your-he-password&myip=\$CLOUDFLARE_DDNS_NEW_IP'" \
--ipv6-hook-command "curl -s -6 'https://dyn.dns.he.net/nic/update?hostname=ddns.example.com&password=your-he-password&myip=\$CLOUDFLARE_DDNS_NEW_IP'"export IPV4_HOOK_COMMAND="curl -s 'https://webhook.example.com/ipv4-changed' -d 'ip=\$CLOUDFLARE_DDNS_NEW_IP'"
export IPV6_HOOK_COMMAND="/path/to/custom/ipv6-script.sh"
./cloudflare-ddns.sh \
--cloudflare-api-token "your-cloudflare-api-token" \
--cloudflare-user-mail "your-email@example.com" \
--cloudflare-zone-name "example.com" \
--cloudflare-record-names "ddns.example.com,ddns.example.com" \
--cloudflare-record-types "4,6"