Skip to content

Instantly share code, notes, and snippets.

Embed
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: https://www.cloudflare.com/a/account/my-account
# 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 "./ddns.sh -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 "./ddns.sh". 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/ddns.sh -s
EMAIL=''
TOKEN=''
DOMAIN=''
SUBDOMAIN=''
ZONE_ID=''
REC_ID=''
set -euo pipefail
VERBOSE="true"
LOOKUP="false"
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 ;;
esac
done
[[ "$SUBDOMAIN" = "@" ]] && FULL_DOMAIN="$DOMAIN" || FULL_DOMAIN="$SUBDOMAIN.$DOMAIN"
API_URL="https://api.cloudflare.com/client/v4"
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)"
$VERBOSE && echo "ZONE_ID='$ZONE_ID'"
fi
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'"
fi
$LOOKUP && exit 0
IP="$(curl -s http://ipv4.icanhazip.com)"
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
fi
$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
@davidcastellani

This comment has been minimized.

Copy link

davidcastellani commented May 17, 2016

@lyoshenka , Cloudflare is deprecating API v1 in favor of v4.

Email I received just this morning.

Starting November 9th, 2016 at noon Pacific Time (20:00 UTC), CloudFlare will no longer be supporting API v1.

They then link to https://www.cloudflare.com/migrating-to-v4/

Any chance you would convert this script to API v4?

@lyoshenka

This comment has been minimized.

Copy link
Owner Author

lyoshenka commented Jul 24, 2016

@davidcastellani just updated it. give it a try and let me know if you see any issues

@Agazed

This comment has been minimized.

Copy link

Agazed commented Sep 28, 2016

How would you change a wildcard record or the root domain? What would you set in the subdomain variable for those records? I tried putting in * and (mydomain).net and it does not work. @lyoshenka

@niclasreich

This comment has been minimized.

Copy link

niclasreich commented Mar 2, 2017

@Agazed

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

@MachineITSvcs

This comment has been minimized.

Copy link

MachineITSvcs commented Jan 3, 2018

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

@DkWyatt

This comment has been minimized.

Copy link

DkWyatt commented Sep 23, 2018

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

@waiyanwh

This comment has been minimized.

Copy link

waiyanwh commented May 21, 2019

replace l37

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

@lyoshenka

This comment has been minimized.

Copy link
Owner Author

lyoshenka commented Jan 21, 2020

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

@frankang

This comment has been minimized.

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 for now.
REC_ID needs to be manually set. (you can get your id by uncomment the -x in the ddns.sh script, then manually curl request the dns_records path as given by the debug info )

@lyoshenka

This comment has been minimized.

Copy link
Owner Author

lyoshenka commented May 7, 2020

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

@ImmortalScientist

This comment has been minimized.

Copy link

ImmortalScientist commented May 23, 2020

@lyoshenka

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

@lyoshenka

This comment has been minimized.

Copy link
Owner Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.