Skip to content

Instantly share code, notes, and snippets.

Forked from kevinoconnor7/
Last active August 29, 2023 17:42
  • Star 87 You must be signed in to star a gist
  • Fork 25 You must be signed in to fork a gist
Star You must be signed in to star a gist
What would you like to do?
Quick and dirty DDNS using Bash and Cloudflare (API v4 compatible)
#!/usr/bin/env bash
# Step 1: Fill in EMAIL, TOKEN, DOMAIN and SUBDOMAIN. Your API token is here:
# Make sure the token is the Global token, or has these permissions: #zone:read, #dns_record:read, #dns_records:edit
# If you want to set the root domain instead of a subdomain, set SUBDOMAIN to "@"
# Step 2: Create an A record on Cloudflare with the subdomain you chose
# Step 3: Run "./ -l" to get the zone_id and rec_id of the record you created.
# Fill in ZONE_ID and REC_ID below
# This step is optional, but will save you 2 requests every time you run this script
# Step 4: Run "./". It should tell you that record was updated or that it didn't need updating.
# Step 5: Run it every hour with cron. Use the '-s' flag to silence normal output
# 0 * * * * /path/to/ -s
set -euo pipefail
while getopts ":lsd" opt; do
case ${opt} in
l ) LOOKUP="true" ;;
s ) VERBOSE="false" ;;
d ) set -x ;; # for debugging
\? ) echo -e "Usage: $(basename "$0") [-l] [-s] [-d]\nRead the script source for detailed instructions" && exit 1 ;;
CURL="curl -s \
-H Content-Type:application/json \
-H X-Auth-Key:$TOKEN \
-H X-Auth-Email:$EMAIL "
if [ -z "$ZONE_ID" ] || $LOOKUP; then
ZONE_ID="$($CURL "$API_URL/zones?name=$DOMAIN" | sed -e 's/[{}]/\n/g' | grep '"name":"'"$DOMAIN"'"' | sed -e 's/,/\n/g' | grep '"id":"' | cut -d'"' -f4)"
if [ -z "$REC_ID" ] || $LOOKUP; then
#REC_ID="$($CURL "$API_URL/zones/$ZONE_ID/dns_records" | grep -C 5 '"name": "'"$FULL_DOMAIN"'"' | grep '"id": "' | cut -d'"' -f4)"
REC_ID="$($CURL "$API_URL/zones/$ZONE_ID/dns_records" | sed -e 's/[{}]/\n/g' | grep '"name":"'"$FULL_DOMAIN"'"' | sed -e 's/,/\n/g' | grep '"id":"' | cut -d'"' -f4)"
$VERBOSE && echo "REC_ID='$REC_ID'"
$LOOKUP && exit 0
set +e
for IP_URL in "" ""; do
IP="$(curl -s "$IP_URL")"
[ -n "$IP" ] && break
set -e
if [ -z "$IP" ]; then
echo "Could not get external IP"
exit 1
RECORD_IP="$($CURL "$API_URL/zones/$ZONE_ID/dns_records/$REC_ID" | grep -o '"content":"[^"]\+' | cut -d '"' -f4)"
if [ "$IP" == "$RECORD_IP" ]; then
$VERBOSE && echo "IP Unchanged"
exit 0
$VERBOSE && echo "Setting IP to $IP"
$CURL -X PUT "$API_URL/zones/$ZONE_ID/dns_records/$REC_ID" --data '{"type":"A","name":"'"$SUBDOMAIN"'","content":"'"$IP"'","proxied":false}' 1>/dev/null
exit 0
Copy link


Have you found any solution? Still looking for a fix...

Copy link

For anyone interested, I wrote a similar script in bash for use with multiple Cloudflare accounts, zones, records, and a proxy option: here

Copy link

reaplace L42
REC_ID="$($CURL "$API_URL/zones/$ZONE_ID/dns_records?name=$SUBDOMAIN" | awk '{t=$0;gsub(/.*"id":"|".*/,"",t);print t}')"

Copy link

replace l37

ZONE_ID="$($CURL "$API_URL/zones?name=$DOMAIN" | jq '.result[0].id'

Copy link

@waiyanwh that works but i'd like to avoid dependence on extra things like jq

Copy link

frankang commented May 2, 2020

cloudflare has changed its response format, so this line
| sed -e 's/[{}]/\n/g' | grep '"name":"'"$SUBDOMAIN"'.'"$DOMAIN"'"' | sed -e 's/,/\n/g' | grep '"id": "' | cut -d'"' -f4 doesn't work now.
REC_ID needs to be manually set. (you can get your id by uncomment the -x in the script, then manually curl request the dns_records path as given by the debug info )

Copy link

Thanks @frankang. I updated the grep commands so it finds the correct record. Should work now

Copy link


Wondering the same as Agazed did years ago. Is it at all possible to set the root domain using this script?

Copy link

lyoshenka commented May 25, 2020

@ImmortalScientist @Agazed - I just updated the script to support root domains. Set SUBDOMAIN to @ and give it a try. Let me know if that doesn't work for you.

Copy link

Hi, i'm trying to get it working with monit but unfortunatelly looks like monit doesn't run the script btw when i run it myself everything works.
Do you have any idea ?

Perhaps there is my command on monit

check host xxxxxx with address xxxxxx
  if failed ping4 then exec "/bin/bash -c /etc/monit/"
  else if succeeded then exec "/bin/bash -c /etc/monit/"

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