Skip to content

Instantly share code, notes, and snippets.

@lukedesu
Last active April 13, 2025 15:54
Show Gist options
  • Save lukedesu/80751505133828c122470bc56388b6c0 to your computer and use it in GitHub Desktop.
Save lukedesu/80751505133828c122470bc56388b6c0 to your computer and use it in GitHub Desktop.
Automating Cloudflare DNS Updates Using Bash and Cron
#!/bin/bash
# PREREQUISITES:
# 1. Ensure you have an active Cloudflare account.
# 2. Create an A record for each domain manually in your Cloudflare dashboard.
# - You can use a dummy IP address like 1.1.1.1 during setup.
# - This script will update the IP automatically during execution.
# 3. Obtain your Global API Key from "My Profile" in Cloudflare.
# 4. Make sure `curl` is installed on your system.
# MODIFY BELOW AS NEEDED
auth_email="[email protected]" # Email for Cloudflare login 'https://dash.cloudflare.com'
auth_key="YOUR_KEY" # Found in "My Profile" > "Global API Key" on Cloudflare
# Domain names with corresponding zone IDs and proxy settings
# Example: ["domain_name"]="zone_id:proxy_flag"
declare -A domain_config_map=(
["www.example.com"]="9c7eb4f4abeaaca8fa029e2a03c76df7:true"
["shop.example2.com"]="4d8dbe2cd4380be4954dff50cc238354:false"
)
# DO NOT MODIFY BELOW THIS LINE UNLESS YOU ARE AN EXPERT
# Create the logs directory if it doesn't exist
log_dir="$(dirname "$0")/logs"
mkdir -p "$log_dir"
###########################################
## Function to log messages with timestamp
###########################################
log_message() {
local message=$1
local log_file=$2
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo -e "[$timestamp] $message" | tee -a "$log_file"
}
###########################################
## Function to update DNS record for a domain
###########################################
update_record() {
local record_name=$1
local zone_identifier=$2
local proxy_setting=$3
local log_file=$4
local ip_file=$5
# Ensure the log file is writable
if [ ! -e "$log_file" ]; then
touch "$log_file"
fi
chmod 664 "$log_file"
# Ensure the IP file exists
if [ ! -e "$ip_file" ]; then
touch "$ip_file"
fi
###########################################
## Check if we have a public IP
###########################################
ip=$(curl -s https://ipv4.icanhazip.com/ || curl -s https://api.ipify.org)
if [ -z "$ip" ]; then
log_message "No public IP found." "$log_file"
exit 1
fi
###########################################
## Check if the IP has changed
###########################################
if [ -e "$ip_file" ]; then
stored_ip=$(cat "$ip_file")
if [ "$ip" == "$stored_ip" ]; then
log_message "IP has not changed. Current IP: $ip" "$log_file"
return 0
fi
fi
###########################################
## Seek for the A record
###########################################
log_message "Check Initiated for $record_name" "$log_file"
record=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records?name=$record_name" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json")
###########################################
## Set the record identifier from result
###########################################
record_identifier=$(echo "$record" | grep -Po '(?<="id":")[^"]*' | head -1)
if [ -z "$record_identifier" ]; then
log_message "Record identifier for $record_name not found. Creating empty file." "$log_file"
# Create an empty file to indicate the record was not found
touch "$(dirname "$0")/dns_record_not_found_$record_name.txt"
return 1
fi
###########################################
## Change the IP@Cloudflare using the API
###########################################
update=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records/$record_identifier" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json" --data "{\"type\":\"A\",\"proxied\":${proxy_setting},\"name\":\"$record_name\",\"content\":\"$ip\"}")
###########################################
## Report the status
###########################################
case "$update" in
*""success":false"*)
log_message "$ip $record_name DDNS failed for $record_identifier ($ip). DUMPING RESULTS:\n$update" "$log_file"
return 1
;;
*)
log_message "$ip $record_name DDNS updated." "$log_file"
;;
esac
###########################################
## Save the current IP
###########################################
echo "$ip" > "$ip_file"
}
###########################################
## Loop through all domain names, zone identifiers, and proxy settings
###########################################
for record_name in "${!domain_config_map[@]}"; do
config="${domain_config_map[$record_name]}"
zone_identifier=$(echo "$config" | cut -d':' -f1)
proxy_setting=$(echo "$config" | cut -d':' -f2)
log_file="$log_dir/${record_name}_update_logs.log"
ip_file="$log_dir/${record_name}_current_ip.txt"
update_record "$record_name" "$zone_identifier" "$proxy_setting" "$log_file" "$ip_file"
done
@lukedesu
Copy link
Author

lukedesu commented May 30, 2024

Two Steps to Automate the Script

1. Assign Permissions

Make the script executable by setting the appropriate permissions:

sudo chmod +x ~/cloudflare.sh

2. Add the Script to Your Crontab

Open the crontab editor to schedule the script:

sudo crontab -e

Append the following line to the end of the crontab file to run the script every 10 minutes:

## Ensure the path is absolute (Note: Replace /home/yourname with the correct path to your script)
*/10 * * * * /bin/bash /home/yourname/cloudflare.sh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment