Skip to content

Instantly share code, notes, and snippets.

@Firsh
Last active January 6, 2024 15:38
Show Gist options
  • Star 89 You must be signed in to star a gist
  • Fork 28 You must be signed in to fork a gist
  • Save Firsh/c9f72970eaae3aec04beb1106cc304bc to your computer and use it in GitHub Desktop.
Save Firsh/c9f72970eaae3aec04beb1106cc304bc to your computer and use it in GitHub Desktop.
Cloudflare as Dynamic DNS
#!/bin/bash
# Cloudflare as Dynamic DNS
# From: https://letswp.io/cloudflare-as-dynamic-dns-raspberry-pi/
# Based on: https://gist.github.com/benkulbertis/fff10759c2391b6618dd/
# Original non-RPi article: https://phillymesh.net/2016/02/23/setting-up-dynamic-dns-for-your-registered-domain-through-cloudflare/
# Update these with real values
auth_email="email@example.com"
auth_key="global_api_key_goes_here"
zone_name="example.com"
record_name="home.example.com"
# Don't touch these
ip=$(curl -s http://ipv4.icanhazip.com)
ip_file="ip.txt"
id_file="cloudflare.ids"
log_file="cloudflare.log"
# Keep files in the same folder when run from cron
current="$(pwd)"
cd "$(dirname "$(readlink -f "$0")")"
log() {
if [ "$1" ]; then
echo -e "[$(date)] - $1" >> $log_file
fi
}
log "Check Initiated"
if [ -f $ip_file ]; then
old_ip=$(cat $ip_file)
if [ $ip == $old_ip ]; then
log "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
@LilToaster69
Copy link

is it possible to use it without a subdomain?

@Firsh
Copy link
Author

Firsh commented Dec 19, 2021

I don't see why not.

@bradmkjr
Copy link

bradmkjr commented May 8, 2022

If anyone is experiencing issues when trying to use this to update the primary A record for a domain, which may have other records associated with the same record_name, for example MX records, I suggest making the following modification:

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":")[^"]*')
Change to
record_identifier=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records?name=$record_name&page=1&per_page=1" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json" | grep -Po '(?<="id":")[^"]*')
I discovered the issue by checking the .ids file and found 5 rows, when there should have only been 2 rows. By limiting to page=1 and per_page=1 it only returns the primary A record, and not MX records.

@Firsh
Copy link
Author

Firsh commented May 8, 2022

Nice, yeah that makes sense to receive a paged list of records if someone has a busy domain and the missing the one you wanted.

@tarasis
Copy link

tarasis commented Sep 11, 2023

No working for me from a Raspberry Pi.

First attempts kept getting a URI error.

~/cf $ sudo ./lwp-cloudflare-dyndns.sh
API UPDATE FAILED. DUMPING RESULTS:
{"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}

After self populating zone_identifier and record_identifier I get this error:

API UPDATE FAILED. DUMPING RESULTS:
{"success":false,"errors":[{"code":7001,"message":"Method PUT not available for that URI."}],"messages":[],"result":null}

Same result when I used a custom API token just for this script:

./lwp-cloudflare-dyndns.sh API UPDATE FAILED. DUMPING RESULTS: {"success":false,"errors":[{"code":7001,"message":"Method PUT not available for that URI."}],"messages":[],"result":null}

Not sure what old me had issues with, but just did this again and it worked. I don't even remember posting that comment. Blimey.

So thanks! (that docker version looks nice too)

@Firsh
Copy link
Author

Firsh commented Sep 12, 2023

Not sure what old me had issues with, but just did this again and it worked. I don't even remember posting that comment. Blimey.

I still use the script to this day, still on RPI, and it works fine. The only thing that will break it is when Cloudflare changes their API, to which we could react.

@Australian
Copy link

@tarasis I wish i could talk to 'old you' because i am getting that same error. as you.

@Australian
Copy link

Getting: API UPDATE FAILED. DUMPING RESULTS: {"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}

Any idea? It is an account with multiple domains.

Hiya... did you ever get this to work?

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