Skip to content

Instantly share code, notes, and snippets.

@AssortedFantasy
Created July 23, 2025 22:22
Show Gist options
  • Save AssortedFantasy/a60aa79b6060358ff742d45eef0dd84b to your computer and use it in GitHub Desktop.
Save AssortedFantasy/a60aa79b6060358ff742d45eef0dd84b to your computer and use it in GitHub Desktop.
Cloudflare DDNS CRON Script
# ==============================================================================
# Example Configuration for Cloudflare DDNS Script (v3)
#
# Instructions:
# To add a DNS record to update, you must add a new entry to EACH of the
# five arrays below, making sure they have the same index.
# The first record is at index 0, the second at index 1, and so on.
# ==============================================================================
# --- Record 1 ---
dns_records[0]="YOUR_RECORD_NAME_HERE" # e.g., "home.yourdomain.com"
zone_ids[0]="YOUR_ZONE_ID_HERE"
api_tokens[0]="YOUR_CLOUDFLARE_API_TOKEN_HERE"
ttls[0]=1 # Use 1 for "Auto", or a value between 120-7200
proxied_statuses[0]="true" # Must be "true" or "false"
# --- To add a second record, uncomment and fill out the lines below ---
# dns_records[1]="ANOTHER_RECORD_NAME"
# zone_ids[1]="ANOTHER_ZONE_ID"
# api_tokens[1]="ANOTHER_API_TOKEN"
# ttls[1]=1
# proxied_statuses[1]="true"
#!/usr/bin/env bash
# ==============================================================================
# Cloudflare Dynamic DNS Update Script
#
# Description:
# This script updates Cloudflare DNS 'A' records with the machine's current
# external IP address. It supports per-record configuration for zone ID,
# API token, TTL, and proxy status.
#
# Dependencies:
# - curl: For making API requests.
# - jq: For robustly parsing JSON responses from the Cloudflare API.
#
# Author: Gemini
# Version: 3.1
# ==============================================================================
set -eo pipefail # Exit on error and on pipe failure
# --- Helper Functions ---
# Centralized logging functions.
log_error() {
echo "[$(date "+%Y-%m-%d %H:%M:%S")] ERROR: $1"
}
log_success() {
echo "[$(date "+%Y-%m-%d %H:%M:%S")] SUCCESS: $1"
}
log_info() {
echo "[$(date "+%Y-%m-%d %H:%M:%S")] INFO: $1"
}
# --- Script Setup ---
# Get the directory where the script is located
parent_path="$(dirname "${BASH_SOURCE[0]}")"
LOG_FILE="${parent_path}/update-cloudflare-dns.log"
# Redirect all stdout and stderr to a log file and also print to the console
exec > >(tee -a "$LOG_FILE") 2>&1
# --- Dependency Check ---
if ! command -v jq &> /dev/null; then
log_error "'jq' is not installed. Please install it to continue. (e.g., 'sudo apt-get install jq' or 'brew install jq')"
exit 1
fi
if ! command -v curl &> /dev/null; then
log_error "'curl' is not installed. Please install it to continue."
exit 1
fi
# --- Configuration Loading ---
# Load config file passed as an argument, or default to update-cloudflare-dns.conf
config_file="${1:-${parent_path}/update-cloudflare-dns.conf}"
if [[ ! -f "$config_file" ]]; then
log_error "Configuration file not found at ${config_file}"
exit 1
fi
source "$config_file"
# --- Get External IP Address ---
external_ip=$(curl -4 -s -X GET https://checkip.amazonaws.com --max-time 15)
ipv4_regex='^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])\.){3}(25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])$'
if ! [[ "$external_ip" =~ $ipv4_regex ]]; then
log_error "Failed to get a valid external IP. Received: '${external_ip}'"
exit 1
fi
# --- Main DNS Update Loop ---
# Loop through the indices of the dns_records array
for i in "${!dns_records[@]}"; do
# Extract per-record settings
record_name="${dns_records[$i]}"
zoneid="${zone_ids[$i]}"
api_token="${api_tokens[$i]}"
ttl="${ttls[$i]}"
proxied="${proxied_statuses[$i]}"
# --- Configuration Validation ---
if [[ -z "$record_name" || -z "$zoneid" || -z "$api_token" || -z "$ttl" || -z "$proxied" ]]; then
log_error "Incomplete configuration for record index ${i}. Please check all parameters."
continue # Skip to the next record
fi
# --- Get current DNS record details from Cloudflare API ---
api_url="https://api.cloudflare.com/client/v4/zones/${zoneid}/dns_records?type=A&name=${record_name}"
api_response=$(curl -s -X GET "$api_url" \
-H "Authorization: Bearer ${api_token}" \
-H "Content-Type: application/json")
if ! echo "$api_response" | jq -e '.success' > /dev/null; then
log_error "API call to get info for ${record_name} failed. Response: $(echo "$api_response" | jq .)"
continue
fi
# Extract current details using jq
current_ip=$(echo "$api_response" | jq -r '.result[0].content')
current_proxied_status=$(echo "$api_response" | jq -r '.result[0].proxied')
record_id=$(echo "$api_response" | jq -r '.result[0].id')
if [[ "$current_ip" == "null" ]]; then
log_error "DNS record '${record_name}' does not exist or could not be found."
continue
fi
# --- Check if an update is needed ---
if [[ "$current_ip" == "$external_ip" && "$current_proxied_status" == "$proxied" ]]; then
log_info "No change needed for ${record_name}: IP (${current_ip}) and proxy status match."
continue
fi
# --- Update the DNS Record ---
update_api_url="https://api.cloudflare.com/client/v4/zones/${zoneid}/dns_records/${record_id}"
update_data=$(jq -n \
--arg name "$record_name" \
--arg content "$external_ip" \
--argjson ttl "$ttl" \
--argjson proxied "$proxied" \
'{type: "A", name: $name, content: $content, ttl: $ttl, proxied: $proxied}')
update_response=$(curl -s -X PUT "$update_api_url" \
-H "Authorization: Bearer ${api_token}" \
-H "Content-Type: application/json" \
--data "$update_data")
if echo "$update_response" | jq -e '.success' > /dev/null; then
log_success "Updated ${record_name} to ${external_ip} (proxied: ${proxied}, ttl: ${ttl})"
else
log_error "Failed to update ${record_name}. Response: $(echo "$update_response" | jq .)"
fi
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment