A bash script to automatically update multiple Cloudflare DNS A records when your public IP address changes. Perfect for home servers and dynamic IP addresses. And for when your pfSense/OPNsense is broken / sold.
- Support for unlimited domains
- Works with subdomains (sub.domain.com, www.domain.com, etc.)
- Easy configuration using simple arrays
- API Token support (more secure than Global API Key)
- Checks if IP actually changed before updating
- Success/failure reporting for each domain
- Systemd integration for automatic updates
- Detailed logging
The script works perfectly with:
- Root domains:
domain.com - Subdomains:
sub.domain.com,api.domain.com, etc. - www subdomain:
www.domain.com - Multiple subdomains per domain: All use the same Zone ID, different Record IDs
Example configuration:
DOMAINS=(
"example.com:abc123:record1" # root domain
"www.example.com:abc123:record2" # www subdomain (same zone ID)
"api.example.com:abc123:record3" # api subdomain (same zone ID)
"otherdomain.com:xyz789:record4" # different domain (different zone ID)
)
## Files Included
### Using Global API Key (legacy)
1. **cloudflare-ddns-multi.sh** - Main DDNS update script
2. **cloudflare-get-record-ids.sh** - Helper to fetch DNS record IDs
### Using API Token (RECOMMENDED - more secure)
1. **cloudflare-ddns-token.sh** - Main DDNS update script with API token
2. **cloudflare-get-record-ids-token.sh** - Helper to fetch DNS record IDs with API token
3. **API-TOKEN-GUIDE.md** - Complete guide on creating and using API tokens
### Systemd files (work with either version)
1. **cloudflare-ddns.service** - Systemd service file
2. **cloudflare-ddns.timer** - Systemd timer file (runs every 5 minutes)
## Installation
### Choose Your Authentication Method
**Option A: API Token (RECOMMENDED)** - More secure, limited permissions
- See **[API-TOKEN-GUIDE.md](API-TOKEN-GUIDE.md)** for detailed instructions
- Use `cloudflare-ddns-token.sh` and `cloudflare-get-record-ids-token.sh`
**Option B: Global API Key (Legacy)** - Full account access
- Use `cloudflare-ddns-multi.sh` and `cloudflare-get-record-ids.sh`
The instructions below work for both methods. Just use the appropriate script names.
---
### Step 1: Get your Cloudflare credentials
**For API Token (Option A):**
1. Follow the complete guide in **[API-TOKEN-GUIDE.md](API-TOKEN-GUIDE.md)**
2. Create a token with **Zone.DNS - Edit** permission for **All zones**
3. Copy and save your token securely
**For Global API Key (Option B):**
1. Log in to [Cloudflare](https://dash.cloudflare.com/)
2. Get your **Global API Key**: Profile → API Tokens → Global API Key → View
3. Note your **email address** associated with your Cloudflare account
### Step 2: Get Zone IDs
For each domain:
1. Go to your domain's overview page in Cloudflare
2. Scroll down to find the **Zone ID** on the right sidebar
3. Copy and save it
### Step 3: Configure the helper script
**For API Token:**
Edit `cloudflare-get-record-ids-token.sh`:
```bash
# Set your API token
AUTH_TOKEN="your_api_token_here"
# Add your domains with their zone IDs (works with subdomains!)
DOMAINS=(
"example.com:zone_id_for_example"
"www.example.com:zone_id_for_example" # same zone as example.com
"api.otherdomain.com:zone_id_for_other" # subdomain of different domain
)For Global API Key:
Edit cloudflare-get-record-ids.sh:
# Set your credentials
AUTH_EMAIL="your-email@domain.com"
AUTH_KEY="your-global-api-key"
# Add your domains with their zone IDs (works with subdomains!)
DOMAINS=(
"example.com:zone_id_for_example"
"www.example.com:zone_id_for_example"
"api.example2.com:zone_id_for_example2"
)Make the helper script executable and run it:
For API Token:
chmod +x cloudflare-get-record-ids-token.sh
./cloudflare-get-record-ids-token.shFor Global API Key:
chmod +x cloudflare-get-record-ids.sh
./cloudflare-get-record-ids.shThe script will output the record IDs in the correct format. Copy these lines.
For API Token:
Edit cloudflare-ddns-token.sh:
# Set your API token
AUTH_TOKEN="your_api_token_here"
# Paste the domain configurations from step 4
# This works with root domains AND subdomains!
DOMAINS=(
"example.com:zone_id:record_id"
"www.example.com:zone_id2:record_id2"
"api.example.com:zone_id3:record_id3"
# Add as many as you need
)
# Optional: Adjust these settings
TTL=180 # DNS TTL in seconds
PROXIED=true # Set to false to disable Cloudflare proxyFor Global API Key:
Edit cloudflare-ddns-multi.sh:
# Set your credentials
AUTH_EMAIL="your-email@domain.com"
AUTH_KEY="your-global-api-key"
# Paste the domain configurations from step 4
DOMAINS=(
"example.com:zone_id:record_id"
"example2.com:zone_id2:record_id2"
# Add as many as you need
)
# Optional: Adjust these settings
TTL=180 # DNS TTL in seconds
PROXIED=true # Set to false to disable Cloudflare proxyFor API Token:
# Make script executable
chmod +x cloudflare-ddns-token.sh
# Copy to system location
sudo cp cloudflare-ddns-token.sh /usr/local/bin/cloudflare-ddns-multi.sh
# Test the script
sudo /usr/local/bin/cloudflare-ddns-multi.shFor Global API Key:
# Make script executable
chmod +x cloudflare-ddns-multi.sh
# Copy to system location
sudo cp cloudflare-ddns-multi.sh /usr/local/bin/
# Test the script
sudo /usr/local/bin/cloudflare-ddns-multi.shNote: Both versions are copied to the same final location (
/usr/local/bin/cloudflare-ddns-multi.sh) so the systemd service works with either.
# Copy systemd files
sudo cp cloudflare-ddns.service /etc/systemd/system/
sudo cp cloudflare-ddns.timer /etc/systemd/system/
# Reload systemd
sudo systemctl daemon-reload
# Enable and start the timer
sudo systemctl enable cloudflare-ddns.timer
sudo systemctl start cloudflare-ddns.timer
# Check timer status
sudo systemctl status cloudflare-ddns.timersudo /usr/local/bin/cloudflare-ddns-multi.sh# If using systemd
sudo journalctl -u cloudflare-ddns.service -f
# Or check the log file
sudo tail -f /var/log/cloudflare-ddns.log# See when the timer last ran and when it will run next
sudo systemctl list-timers cloudflare-ddns.timer
# View timer logs
sudo journalctl -u cloudflare-ddns.timer -fSimply edit /usr/local/bin/cloudflare-ddns-multi.sh and add more entries to the DOMAINS array:
DOMAINS=(
"domain1.com:zoneid1:recordid1"
"domain2.com:zoneid2:recordid2"
"newdomain.com:newzoneid:newrecordid" # Just add here
)Then restart the timer:
sudo systemctl restart cloudflare-ddns.timer- Verify your credentials are correct
- Check that your API key has the necessary permissions
- Ensure the zone IDs and record IDs are correct
- Check the logs for error messages
Try the alternative IP detection method in the script:
# Comment out the default method
# PUBLIC_IP=$(dig +short myip.opendns.com @resolver1.opendns.com) || exit 1
# Uncomment the alternative
PUBLIC_IP=$(curl --silent https://api.ipify.org) || exit 1You can test updating a specific domain using curl:
curl -X PUT \
"https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records/RECORD_ID" \
-H "Content-Type: application/json" \
-H "X-Auth-Email: YOUR_EMAIL" \
-H "X-Auth-Key: YOUR_API_KEY" \
-d '{
"type": "A",
"name": "domain.com",
"content": "YOUR_IP",
"ttl": 180,
"proxied": true
}'Edit cloudflare-ddns.timer:
# Update every 1 minute
OnUnitActiveSec=60
# Update every 10 minutes
OnUnitActiveSec=600
# Update every hour
OnUnitActiveSec=3600Then reload:
sudo systemctl daemon-reload
sudo systemctl restart cloudflare-ddns.timerIn the main script, set:
PROXIED=falseIn the main script, adjust:
TTL=300 # 5 minutes
TTL=600 # 10 minutes
TTL=3600 # 1 hour- Keep your API key secure and don't commit it to version control
- Consider using API tokens instead of the Global API Key for better security
- The script stores the current IP in
/tmp/cloudflare-ddns-ip-record - Logs may contain IP addresses
This script is provided as-is for personal and commercial use.