Skip to content

Instantly share code, notes, and snippets.

@nh4ttruong
Created October 21, 2025 09:23
Show Gist options
  • Save nh4ttruong/c4d1d1aaa81ec1f1423c31241f914705 to your computer and use it in GitHub Desktop.
Save nh4ttruong/c4d1d1aaa81ec1f1423c31241f914705 to your computer and use it in GitHub Desktop.
Quick Update DNS On CloudFlare
#!/bin/bash
#
# A script to manage 'A' or 'CNAME' DNS records in Cloudflare via API.
#
# Usage:
# ./cloudflare_dns.sh (Interactive add mode)
# ./cloudflare_dns.sh -l [-t TYPE] (List records, optionally filter by TYPE: A or CNAME)
# ./cloudflare_dns.sh -u (Interactive update mode)
# ./cloudflare_dns.sh -r (Interactive remove mode)
# --- User-configurable variables ---
# You can pre-fill these or leave them empty to be prompted.
CLOUDFLARE_API_TOKEN="xxx"
ZONE_ID="xxx"
# --- Script Configuration ---
ACTION="add"
RECORD_TYPE_FILTER=""
# --- Color Codes for Output ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# --- Functions ---
usage() {
echo "Usage: $0 [-l] [-u] [-r] [-t type]"
echo " -l List DNS records."
echo " -u Update an existing DNS record."
echo " -r Remove an existing DNS record."
echo " -t TYPE Filter list by record type ('A' or 'CNAME'). Used with -l."
echo " (no args) Add a new DNS record (default)."
exit 1
}
# Function to prompt for user input if a variable is empty
prompt_if_empty() {
local var_name="$1"
local prompt_text="$2"
local is_secret="${3:-false}"
if [ -z "${!var_name}" ]; then
if [ "$is_secret" = true ]; then
read -sp "$prompt_text: " "$var_name"
echo
else
read -p "$prompt_text: " "$var_name"
fi
fi
if [ -z "${!var_name}" ]; then
echo -e "${RED}Error: $prompt_text cannot be empty.${NC}"
exit 1
fi
}
# Function to list DNS records
list_records() {
local filter_url=""
if [ -n "$RECORD_TYPE_FILTER" ]; then
filter_url="?type=$RECORD_TYPE_FILTER"
fi
echo "Fetching DNS records..."
API_URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records$filter_url"
response=$(curl -s -X GET "$API_URL" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json")
if ! command -v jq &> /dev/null; then
echo -e "${YELLOW}Warning: 'jq' is not installed. Output will be raw JSON.${NC}"
echo "$response"
return
fi
if ! echo "$response" | jq -e '.success' &> /dev/null; then
echo -e "${RED}Error fetching records.${NC}"
echo "$response" | jq .
exit 1
fi
echo -e "${CYAN}ID TYPE NAME CONTENT PROXIED${NC}"
echo "------------------------------------ ------- ----------------------------- ----------------------------- -------"
echo "$response" | jq -r '.result[] | [.id, .type, .name, .content, .proxied] | @tsv' | while IFS=$'\t' read -r id type name content proxied; do
printf "%-36s %-7s %-29s %-29s %-7s\n" "$id" "$type" "$name" "$content" "$proxied"
done
}
# Function to add a DNS record
add_record() {
echo "--- Add New DNS Record ---"
# 3. Get Record Type
while true; do
read -p "Enter DNS record type (A or CNAME): " record_type
record_type=$(echo "$record_type" | tr '[:lower:]' '[:upper:]')
if [ "$record_type" == "A" ] || [ "$record_type" == "CNAME" ]; then
break
else
echo -e "${RED}Invalid record type. Please enter 'A' or 'CNAME'.${NC}"
fi
done
# 4. Get Record Name
read -p "Enter DNS record name (e.g., 'sub.domain.com'): " record_name
if [ -z "$record_name" ]; then echo -e "${RED}Error: Record name cannot be empty.${NC}"; exit 1; fi
# 5. Get Record Content
if [ "$record_type" == "A" ]; then
read -p "Enter the IP address (IPv4) for the 'A' record: " record_content
if ! [[ $record_content =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo -e "${RED}Error: Invalid IPv4 address format.${NC}"; exit 1;
fi
else # CNAME
read -p "Enter the target domain for the 'CNAME' record: " record_content
fi
if [ -z "$record_content" ]; then echo -e "${RED}Error: Record content cannot be empty.${NC}"; exit 1; fi
# 6. Get Proxy Status
while true; do
read -p "Enable Cloudflare proxy (proxied)? (true/false): " proxied_status
proxied_status=$(echo "$proxied_status" | tr '[:upper:]' '[:lower:]')
if [ "$proxied_status" == "true" ] || [ "$proxied_status" == "false" ]; then
break
else echo -e "${RED}Invalid input. Please enter 'true' or 'false'.${NC}"; fi
done
json_payload=$(cat <<EOF
{"type":"$record_type","name":"$record_name","content":"$record_content","ttl":1,"proxied":$proxied_status}
EOF
)
API_URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records"
echo "Sending request to Cloudflare API..."
response=$(curl -s -X POST "$API_URL" -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json" --data "$json_payload")
if command -v jq &> /dev/null && echo "$response" | jq -e '.success' &> /dev/null; then
echo -e "${GREEN}Successfully created DNS record!${NC}"
echo "$response" | jq .
else
echo -e "${RED}Failed to create DNS record.${NC}"
if command -v jq &> /dev/null; then echo "$response" | jq .; else echo "$response"; fi
exit 1
fi
}
# Function to remove a DNS record
remove_record() {
echo "--- Remove DNS Record ---"
list_records
echo "--------------------------"
read -p "Enter the ID of the record to REMOVE: " record_id
if [ -z "$record_id" ]; then echo -e "${RED}Error: Record ID cannot be empty.${NC}"; exit 1; fi
read -p "Are you sure you want to delete record $record_id? (y/n): " confirm
if [ "$confirm" != "y" ]; then echo "Operation cancelled."; exit 0; fi
API_URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$record_id"
echo "Sending delete request..."
response=$(curl -s -X DELETE "$API_URL" -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json")
if command -v jq &> /dev/null && echo "$response" | jq -e '.success' &> /dev/null; then
echo -e "${GREEN}Successfully removed DNS record!${NC}"
echo "$response" | jq .
else
echo -e "${RED}Failed to remove DNS record.${NC}"
if command -v jq &> /dev/null; then echo "$response" | jq .; else echo "$response"; fi
exit 1
fi
}
# Function to update a DNS record
update_record() {
echo "--- Update DNS Record ---"
list_records
echo "--------------------------"
read -p "Enter the ID of the record to UPDATE: " record_id
if [ -z "$record_id" ]; then echo -e "${RED}Error: Record ID cannot be empty.${NC}"; exit 1; fi
# Fetch existing record to get its type
API_URL_GET="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$record_id"
existing_record=$(curl -s -X GET "$API_URL_GET" -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json")
if ! echo "$existing_record" | jq -e '.success' &> /dev/null; then
echo -e "${RED}Could not fetch record with ID $record_id.${NC}"; exit 1;
fi
record_type=$(echo "$existing_record" | jq -r '.result.type')
echo "Updating a '$record_type' record."
read -p "Enter new record name [$(echo "$existing_record" | jq -r '.result.name')]: " record_name
if [ -z "$record_name" ]; then record_name=$(echo "$existing_record" | jq -r '.result.name'); fi
read -p "Enter new record content [$(echo "$existing_record" | jq -r '.result.content')]: " record_content
if [ -z "$record_content" ]; then record_content=$(echo "$existing_record" | jq -r '.result.content'); fi
read -p "Enable Cloudflare proxy? [$(echo "$existing_record" | jq -r '.result.proxied')] (true/false): " proxied_status
if [ -z "$proxied_status" ]; then proxied_status=$(echo "$existing_record" | jq -r '.result.proxied'); fi
json_payload=$(cat <<EOF
{"type":"$record_type","name":"$record_name","content":"$record_content","ttl":1,"proxied":$proxied_status}
EOF
)
API_URL_PUT="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$record_id"
echo "Sending update request..."
response=$(curl -s -X PUT "$API_URL_PUT" -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json" --data "$json_payload")
if command -v jq &> /dev/null && echo "$response" | jq -e '.success' &> /dev/null; then
echo -e "${GREEN}Successfully updated DNS record!${NC}"
echo "$response" | jq .
else
echo -e "${RED}Failed to update DNS record.${NC}"
if command -v jq &> /dev/null; then echo "$response" | jq .; else echo "$response"; fi
exit 1
fi
}
# --- Main Script ---
# Parse command-line options
while getopts "lurt:" opt; do
case ${opt} in
l ) ACTION="list" ;;
u ) ACTION="update" ;;
r ) ACTION="remove" ;;
t ) RECORD_TYPE_FILTER=$(echo "$OPTARG" | tr '[:lower:]' '[:upper:]') ;;
\? ) usage ;;
esac
done
echo -e "${YELLOW}Cloudflare DNS Record Manager${NC}"
echo "------------------------------------------------------------"
# Get credentials
prompt_if_empty "CLOUDFLARE_API_TOKEN" "Enter your Cloudflare API Token" true
prompt_if_empty "ZONE_ID" "Enter your Cloudflare Zone ID"
# Execute action
case $ACTION in
add) add_record ;;
list) list_records ;;
remove) remove_record ;;
update) update_record ;;
*) usage ;;
esac
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment