This guide shows how to create a simple Bash script that automatically updates a Cloudflare DNS A record when your public IP address changes.

This is ideal for:

  • Home lab environments
  • Self-hosted services
  • Proxmox / VPN endpoints
  • ISP connections with dynamic IP addresses

Instead of using a third-party Dynamic DNS provider, this script updates Cloudflare directly via its API.


How It Works

The script:

  1. Fetches your current public IP address
  2. Queries Cloudflare for the existing DNS record
  3. Compares the two
  4. Updates the DNS record only if the IP has changed
  5. Logs all activity with timestamps

This prevents unnecessary API calls and keeps logs clean.


Requirements

Install required tools:

apt install -y curl jq

You will also need:

  • A Cloudflare account
  • Your Zone ID
  • Your DNS Record ID
  • An API key or token

The Script

Create the file:

nano /root/cloudflare-ddns.sh

Paste the following:

#!/bin/bash# Variables
ZONE_ID="YOUR_ZONE_ID"
RECORD_ID="YOUR_RECORD_ID"
AUTH_EMAIL="your@email.com"
AUTH_KEY="YOUR_GLOBAL_API_KEY"
DOMAIN="yourdomain.com"
LOG_FILE="/root/ddns.log"# Fetch the current public IP
CURRENT_IP=$(curl -s https://api.ipify.org)# Get the existing IP from Cloudflare
EXISTING_IP=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
-H "Content-Type: application/json" \
-H "X-Auth-Email: $AUTH_EMAIL" \
-H "X-Auth-Key: $AUTH_KEY" | jq -r '.result.content')# Timestamp
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")# Compare and update if needed
if [ "$CURRENT_IP" != "$EXISTING_IP" ]; then
echo "$TIMESTAMP - IP changed from $EXISTING_IP to $CURRENT_IP. Updating record..." | tee -a "$LOG_FILE" RESPONSE=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
-H "Content-Type: application/json" \
-H "X-Auth-Email: $AUTH_EMAIL" \
-H "X-Auth-Key: $AUTH_KEY" \
--data '{"type":"A","name":"'"$DOMAIN"'","content":"'"$CURRENT_IP"'","ttl":1,"proxied":false}') if echo "$RESPONSE" | grep -q '"success":true'; then
echo "$TIMESTAMP - DNS record updated successfully." | tee -a "$LOG_FILE"
else
echo "$TIMESTAMP - Update failed. Response: $RESPONSE" | tee -a "$LOG_FILE"
fi
else
echo "$TIMESTAMP - IP unchanged ($CURRENT_IP). No update required." | tee -a "$LOG_FILE"
fi

Save and make executable:

chmod +x /root/cloudflare-ddns.sh

Finding Your Cloudflare IDs

Zone ID

Cloudflare Dashboard → Domain → Overview → Right Sidebar → Zone ID

Record ID

Run:

curl -X GET "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/dns_records" \
-H "X-Auth-Email: your@email.com" \
-H "X-Auth-Key: YOUR_GLOBAL_API_KEY" \
-H "Content-Type: application/json"

Find the record for your domain and copy the "id" value.


Run the Script Manually

/root/cloudflare-ddns.sh

Check the log:

cat /root/ddns.log

Automate with Cron

Edit root crontab:

crontab -e

Run every 5 minutes:

*/5 * * * * /root/cloudflare-ddns.sh

Security Recommendation (Important)

Instead of using your Global API Key, it is strongly recommended to create a Cloudflare API Token with limited permissions:

Permissions required:

  • Zone → DNS → Edit
  • Zone → Zone → Read

This reduces risk if the script is ever exposed.


Optional Improvements

You could enhance this script by:

  • Sending a Pushover notification on IP change
  • Logging to syslog instead of a flat file
  • Supporting IPv6 (AAAA record)
  • Adding error handling if Cloudflare API is unreachable

Use Cases

This script is useful for:

  • Hosting services behind dynamic residential IP
  • WireGuard endpoints
  • Self-hosted dashboards
  • Proxmox remote access
  • Home VPN servers

Tested On

  • Debian 12
  • Proxmox VE 8
  • Cloudflare DNS (A records)