Instantly share code, notes, and snippets.

Embed
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
@rasmusbe

This comment has been minimized.

Show comment
Hide comment
@rasmusbe

rasmusbe May 12, 2015

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

rasmusbe commented May 12, 2015

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

@tomdavidson

This comment has been minimized.

Show comment
Hide comment
@tomdavidson

tomdavidson Jul 19, 2015

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

tomdavidson commented Jul 19, 2015

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

@b0bcarlson

This comment has been minimized.

Show comment
Hide comment
@b0bcarlson

b0bcarlson Jan 11, 2016

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

b0bcarlson commented Jan 11, 2016

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

@nekoyokoshima

This comment has been minimized.

Show comment
Hide comment
@nekoyokoshima

nekoyokoshima Feb 9, 2016

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)

nekoyokoshima commented Feb 9, 2016

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)

@Gurkengewuerz

This comment has been minimized.

Show comment
Hide comment
@Gurkengewuerz

Gurkengewuerz Mar 8, 2016

Thank's for sharing!

Gurkengewuerz commented Mar 8, 2016

Thank's for sharing!

@gstuartj

This comment has been minimized.

Show comment
Hide comment
@gstuartj

gstuartj 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/

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

This comment has been minimized.

Show comment
Hide comment
@Noino

Noino 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

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
@thordin9

This comment has been minimized.

Show comment
Hide comment
@thordin9

thordin9 Dec 24, 2016

very useful, thanks for sharing.

thordin9 commented Dec 24, 2016

very useful, thanks for sharing.

@Wohlraj

This comment has been minimized.

Show comment
Hide comment
@Wohlraj

Wohlraj Dec 24, 2016

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

Wohlraj commented Dec 24, 2016

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

@shurelia

This comment has been minimized.

Show comment
Hide comment
@shurelia

shurelia Jan 17, 2017

Thanks for this!

shurelia commented Jan 17, 2017

Thanks for this!

@mitchins

This comment has been minimized.

Show comment
Hide comment
@mitchins

mitchins Feb 19, 2017

This worked for me, thanks a lot!

mitchins commented Feb 19, 2017

This worked for me, thanks a lot!

@PsychoTea

This comment has been minimized.

Show comment
Hide comment
@PsychoTea

PsychoTea 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

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

This comment has been minimized.

Show comment
Hide comment
@tmkasun

tmkasun 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 👍

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 👍

@bogdanstoica35

This comment has been minimized.

Show comment
Hide comment
@bogdanstoica35

bogdanstoica35 Apr 8, 2017

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?

bogdanstoica35 commented Apr 8, 2017

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

This comment has been minimized.

Show comment
Hide comment
@jsarenik

jsarenik May 8, 2017

Thank you!

jsarenik commented May 8, 2017

Thank you!

@Kiendeleo

This comment has been minimized.

Show comment
Hide comment
@Kiendeleo

Kiendeleo Jun 15, 2017

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?

Kiendeleo commented Jun 15, 2017

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?

@ghost

This comment has been minimized.

Show comment
Hide comment
@ghost

ghost Sep 1, 2017

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

ghost commented Sep 1, 2017

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

@sandrosdj

This comment has been minimized.

Show comment
Hide comment
@sandrosdj

sandrosdj Sep 17, 2017

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

sandrosdj commented Sep 17, 2017

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

@danieluramg

This comment has been minimized.

Show comment
Hide comment
@danieluramg

danieluramg Sep 18, 2017

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":[]}

danieluramg commented Sep 18, 2017

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":[]}

@beykansen

This comment has been minimized.

Show comment
Hide comment
@beykansen

beykansen Nov 28, 2017

Added proxied true which is for enabling http proxy. Also updated with @nekoyokoshima answer. Final working version (for me) is below:

#!/bin/bash

# Created by benkulbertis/cloudflare-update-record.sh
# 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\",\"proxied\":true,\"name\":\"$record_name\",\"content\":\"$ip\"}")

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

beykansen commented Nov 28, 2017

Added proxied true which is for enabling http proxy. Also updated with @nekoyokoshima answer. Final working version (for me) is below:

#!/bin/bash

# Created by benkulbertis/cloudflare-update-record.sh
# 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\",\"proxied\":true,\"name\":\"$record_name\",\"content\":\"$ip\"}")

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
@ptts

This comment has been minimized.

Show comment
Hide comment
@ptts

ptts Dec 2, 2017

I'm also getting the same error as @Kiendeleo and @danieluramg namely:

{
   "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
}

These are the files and permissions:

drwxr-xr-x 2 ptts ptts 4096 Dec  2 16:17 .
drwxr-xr-x 5 ptts ptts 4096 Dec  2 16:11 ..
-rw-r--r-- 1 ptts ptts    2 Dec  2 16:13 cloudflare.ids
-rw-r--r-- 1 ptts ptts  666 Dec  2 16:15 cloudflare.log
-rw-rw-rw- 1 ptts ptts 1928 Dec  2 16:15 cloudflare-update-record.sh

I ran the script with bash cloudflare-update-record.sh and sudo bash cloudflare-update-record.sh, both yield the same result.
Any suggestions? Thanks!

ptts commented Dec 2, 2017

I'm also getting the same error as @Kiendeleo and @danieluramg namely:

{
   "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
}

These are the files and permissions:

drwxr-xr-x 2 ptts ptts 4096 Dec  2 16:17 .
drwxr-xr-x 5 ptts ptts 4096 Dec  2 16:11 ..
-rw-r--r-- 1 ptts ptts    2 Dec  2 16:13 cloudflare.ids
-rw-r--r-- 1 ptts ptts  666 Dec  2 16:15 cloudflare.log
-rw-rw-rw- 1 ptts ptts 1928 Dec  2 16:15 cloudflare-update-record.sh

I ran the script with bash cloudflare-update-record.sh and sudo bash cloudflare-update-record.sh, both yield the same result.
Any suggestions? Thanks!

@MachineITSvcs

This comment has been minimized.

Show comment
Hide comment
@MachineITSvcs

MachineITSvcs Jan 1, 2018

Hey, guys. Just published a script I wrote and used in my company to manage my clients' domains on Cloudflare. You can find it here

Contact info is availabe on the page as well for those needing assistance with use.

MachineITSvcs commented Jan 1, 2018

Hey, guys. Just published a script I wrote and used in my company to manage my clients' domains on Cloudflare. You can find it here

Contact info is availabe on the page as well for those needing assistance with use.

@clouddav

This comment has been minimized.

Show comment
Hide comment
@clouddav

clouddav Jul 1, 2018

@beykansen @nekoyokoshima

your script is working fine but only for **www.**my-domain.com and it is not updating my-domain.com IP address.

clouddav commented Jul 1, 2018

@beykansen @nekoyokoshima

your script is working fine but only for **www.**my-domain.com and it is not updating my-domain.com IP address.

@telunc

This comment has been minimized.

Show comment
Hide comment
@telunc

telunc Jul 3, 2018

Any idea how to schedule this script? I'm trying to use crontab, but data isn't logging in cloudflare.log.

*/15 * * * * /path/to/cloudflare-update-record.sh

telunc commented Jul 3, 2018

Any idea how to schedule this script? I'm trying to use crontab, but data isn't logging in cloudflare.log.

*/15 * * * * /path/to/cloudflare-update-record.sh
@lifehome

This comment has been minimized.

Show comment
Hide comment
@lifehome

lifehome Jul 6, 2018

I have tuned the script a bit so no files will be created, also created some systemd unit for automation.

See my version at: https://gist.github.com/lifehome/eb3f7d798f9bc6720cdc7d2be1238d4f

lifehome commented Jul 6, 2018

I have tuned the script a bit so no files will be created, also created some systemd unit for automation.

See my version at: https://gist.github.com/lifehome/eb3f7d798f9bc6720cdc7d2be1238d4f

@channprj

This comment has been minimized.

Show comment
Hide comment
@channprj

channprj Aug 13, 2018

Good Idea! 👍

channprj commented Aug 13, 2018

Good Idea! 👍

@4ft35t

This comment has been minimized.

Show comment
Hide comment
@4ft35t

4ft35t Sep 20, 2018

mod for openwrt, grep of busybox doesn't support -P option
https://gist.github.com/4ft35t/510897486bc6986d19cac45b3b9ca1d0

4ft35t commented Sep 20, 2018

mod for openwrt, grep of busybox doesn't support -P option
https://gist.github.com/4ft35t/510897486bc6986d19cac45b3b9ca1d0

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