Skip to content

Instantly share code, notes, and snippets.

@Firsh
Last active October 7, 2024 20:16
Show Gist options
  • 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 protected]"
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
@mrbscott
Copy link

As @giovanniferretti suggested, I changed out the email/key for the token. I'm getting a "sh: not: unknown operand" error followed by "IP changed to: [my ip]"

Any suggestions?
`zone_identifier=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone_name" -H "Authorization: Bearer $api_token" -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 "Authorization: Bearer $api_token" -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 "Authorization: Bearer $api_token" -H "Content-Type: application/json" --data "{"id":"$zone_identifier","type":"A","name":"$record_name","content":"$ip"}")`

@heygambo
Copy link

I've now switched to using this docker container:

version: "3"
services:
  cloudflare-ddns:
    image: oznu/cloudflare-ddns:latest
    restart: unless-stopped
    environment:
      - API_KEY=<cloudflare-api-key>
      - ZONE=example.com
      - SUBDOMAIN=subdomain
      - PROXIED=true

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