Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Cloudflare API v4 Dynamic DNS Update in Bash
#!/bin/bash
# CHANGE THESE
auth_email="user@example.com"
auth_key="c2547eb745079dac9320b638f5e225cf483cc5cfdda41" # found in cloudflare account settings
zone_name="example.com"
record_name="www.example.com"
# MAYBE CHANGE THESE
ip=$(curl -s http://ipv4.icanhazip.com)
ip_file="ip.txt"
id_file="cloudflare.ids"
log_file="cloudflare.log"
# LOGGER
log() {
if [ "$1" ]; then
echo -e "[$(date)] - $1" >> $log_file
fi
}
# SCRIPT START
log "Check Initiated"
if [ -f $ip_file ]; then
old_ip=$(cat $ip_file)
if [ $ip == $old_ip ]; then
echo "IP has not changed."
exit 0
fi
fi
if [ -f $id_file ] && [ $(wc -l $id_file | cut -d " " -f 1) == 2 ]; then
zone_identifier=$(head -1 $id_file)
record_identifier=$(tail -1 $id_file)
else
zone_identifier=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone_name" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json" | grep -Po '(?<="id":")[^"]*' | head -1 )
record_identifier=$(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" | grep -Po '(?<="id":")[^"]*')
echo "$zone_identifier" > $id_file
echo "$record_identifier" >> $id_file
fi
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 "{\"id\":\"$zone_identifier\",\"type\":\"A\",\"name\":\"$record_name\",\"content\":\"$ip\"}")
if [[ $update == *"\"success\":false"* ]]; then
message="API UPDATE FAILED. DUMPING RESULTS:\n$update"
log "$message"
echo -e "$message"
exit 1
else
message="IP changed to: $ip"
echo "$ip" > $ip_file
log "$message"
echo "$message"
fi

Thanks for your code!
Since grep -P isn't available on mac I made an alternative version for mac (will probably work for linux as well)
https://gist.github.com/rasmusbe/fc2e270095f1a3b41348

If one is tired of the OSX exceptions (such as not pcre with grep), then one could fix their grep and other cmd tools:
https://gist.github.com/tomdavidson/ee37773bc9089476f92d

It still appears to work but I am getting cloudflare-update-record.sh: 45: cloudflare-update-record.sh: [[: not found

for anyone(@123isme1) getting cloudflare-update-record.sh: 45: cloudflare-update-record.sh: [[: not found
Replace lines 45-55 with

case "$update" in
  *"\"success\":false"*)
    message="API UPDATE FAILED. DUMPING RESULTS:\n$update"
    log "$message"
    echo -e "$message"
    exit 1;;
  *)
      message="IP changed to: $ip"
    echo "$ip" > $ip_file
    log "$message"
    echo "$message";;
esac

A dirty workaround, but hey, it works!

If you want to change to root record of the domain you'll need to set record_name="www.example.com" (Duh!!) and make sure to replace dns_records?name=$record_name with "dns_records?type=A&name=$record_name" making sure the type corresponds with either A or CNAME(cname is not suggested for sec reasons)

Thank's for sharing!

gstuartj commented May 7, 2016

I started modifying this script for my own purposes, then ended up rewriting it. I did, however, use @benkulbertis's grep patterns for parsing the responses, so thanks for the inspiration. My script is POSIX compliant, so it can be used on embedded systems like consumer routers without BASH. I also added more error checking and some other useful actions & ease-of-use stuff. Repo: https://github.com/gstuartj/cf-ddns.sh/

Noino commented Aug 10, 2016

Felt like i had to add this starting line 25

if ! [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
    message="Fetched IP does not look valid! Quitting"
    log "$message"
    echo -e "$message"
    exit 1 
fi

very useful, thanks for sharing.

Wohlraj commented Dec 24, 2016

How about handling IPv6 as well? I can give it a try if not already done

Thanks for this!

This worked for me, thanks a lot!

PsychoTea commented Feb 24, 2017

Script is working great for me, bar one small issue. Say I have the domain s.tech. I'm able to update both a.s.tech, www.s.tech, but simply setting s.tech as the record name in hopes of updating s.tech doesn't work. Any ideas?

Edit: nevermind, it appears to be working now. Not sure how that happened since I have edited anything though O.o

tmkasun commented Apr 8, 2017

Thanks it's working
If by chance anyone get an error like below:
API UPDATE FAILED. DUMPING RESULTS: {"success":false,"errors":[{"code":7001,"message":"Method PUT not available for that URI."}],"messages":[],"result":null}

Try deleting cloudflare.ids and update again 👍

Thanks for sharing. The script works just fine (I do not need it for DDNS but to change the ip address in the DNS zone with an IP address of my choice). One question though. When the script updates the A record in CF, the cloudflare CF CDN is disabled by default. I need that to be enabled all the time. Any ideea how to do that?

jsarenik commented May 8, 2017

Thank you!

I am getting the following result when I run the script:
{"success":false,"errors":[{"code":7003,"message":"Could not route to \/zones\/dns_records, perhaps your object identifier is invalid?"},{"code":7000,"message":"No route for that URI"}],"messages":[],"result":null}

I am attempting to update and A record of a subdomain on an account with multiple domains. Is anyone else getting this error?

Bug what if i like to change more records than 1? For example: www smtp irc ftp and more

@maratmkhitaryan Then why don't you update one and use CNAME for the rest?

I'm having the same error as @Kiendeleo user above, but only when the script runs on Cron, if I run manually it updates properly.

Run on Cron:
{"success":false,"errors":[{"code":7003,"message":"Could not route to \/zones\/dns_records, perhaps your object identifier is invalid?"},{"code":7000,"message":"No route for that URI"}],"messages":[],"result":null}

Manually executing:
{"result":{"id":"XXXXXXXXXXXXXXXX","type":"A","name":"XXX.XXX.XX","content":"177.XXX.XX.188","proxiable":true,"proxied":false,"ttl":1,"locked":false,"zone_id":"XXXXXXXXXXXXXXXXX","zone_name":"ideias.pw","modified_on":"2017-09-18T11:31:10.657205Z","created_on":"2017-09-18T11:31:10.657205Z","meta":{"auto_added":false}},"success":true,"errors":[],"messages":[]}

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