Skip to content

Instantly share code, notes, and snippets.

@Firsh
Last active December 15, 2024 22:29
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@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
@kanetzach
Copy link

This happens
lwp-cloudflare-dyndns.sh: 33: [: ipaddress: unexpected operator
lwp-cloudflare-dyndns.sh: 39: [: 2: unexpected operator
lwp-cloudflare-dyndns.sh: 51: lwp-cloudflare-dyndns.sh: [[: not found
IP changed to: ipaddress

I changed my real ip for ipaddress

@Firsh
Copy link
Author

Firsh commented Jul 11, 2019

We even started using this script on Ubuntu in an AWS EC2 instance and works fine. What have you tried and how were you using it to have run into that error?

@kanetzach
Copy link

Raspbian 10 (Buster), probably it's because of that. A similar thing happened with another cloudflare ddns script but I fixed that through shellcheck.

@MohaBeacon
Copy link

working good but can't enable proxy via ddns

@chitypo
Copy link

chitypo commented Aug 25, 2019

enable proxy

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\",\"proxied\":true}")

@tarasis
Copy link

tarasis commented Sep 23, 2019

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}

@Firsh
Copy link
Author

Firsh commented Oct 1, 2019

Hi, it still works fine for me. From the docs: https://api.cloudflare.com/#accounts-update-account It tells us to use PUT, and these permissions are required: #organization:edit not sure where those are set though.

@ILAsoft
Copy link

ILAsoft commented Nov 30, 2019

This happens
lwp-cloudflare-dyndns.sh: 33: [: ipaddress: unexpected operator
lwp-cloudflare-dyndns.sh: 39: [: 2: unexpected operator
lwp-cloudflare-dyndns.sh: 51: lwp-cloudflare-dyndns.sh: [[: not found
IP changed to: ipaddress

I changed my real ip for ipaddress

This is most likely due to running as an SH vs BASH command. Try running like so: "bash lwp-cloudflare-dyndns.sh".

@strontiumss
Copy link

Works great on RPi with buster. Thanks!

@natelandau
Copy link

natelandau commented Jan 10, 2020

FWIW, getting the same error

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

No idea how to fix it yet. Would appreciate it if someone could point out how to do so...

@pedrof1gueiredo
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.

@heygambo
Copy link

heygambo commented Mar 17, 2020

Managed to get it to work:
https://gist.github.com/heygambo/6a6180d62469002297b2ec63406b7d37

  • Updated it to work with Cloudflare API tokens that can be limited.
  • Fixed id parsing for the identifiers

@giovanniferretti
Copy link

giovanniferretti commented Aug 24, 2020

For whoever's interested in using API Tokens instead of API Keys (which makes it possible to fine-tune the access rights of a given API Token) the headers of the GET and PUT requests sent using curl can be changed as follows:

delete X-Auth-Email: and X-Auth-Key:
add Authorization: Bearer <your API Token>

Only permission necessary for the API Token is Zone.DNS in my experience

@Afr0king
Copy link

I am really confused. I am trying to get this script running on a raspberry pi4 running raspbian buster. And when I try to execute lwp-cloudflare-dyndns.sh with the bash command I get:
lwp-cloudflare-dyndns.sh: line 6: $'\r': command not found
lwp-cloudflare-dyndns.sh: line 20: $'\r': command not found
lwp-cloudflare-dyndns.sh: line 25: $'\r': command not found
lwp-cloudflare-dyndns.sh: line 31: $'\r': command not found
lwp-cloudflare-dyndns.sh: line 34: cd: $'/home/pi\r': No such file or directory
lwp-cloudflare-dyndns.sh: line 35: $'\r': command not found
lwp-cloudflare-dyndns.sh: line 36: syntax error near unexpected token $'{\r'' 'wp-cloudflare-dyndns.sh: line 36: log() {

What is going on? Where is it reading the \r into the tokens?
I did not change anything except of changing the domain and login data..

@Firsh
Copy link
Author

Firsh commented Nov 26, 2020

Seems to be the way newlines were handled on your system, that may or may not be because of how you copied or downloaded the source. \r is a carriage return or new line character.

@sunrisepi
Copy link

Thank you @Firsh, works great!

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

@produktive
Copy link

I implemented logrotate. This will rotate the log files each day for 7 days before deleting the oldest one. Then you don't have a giant growing log file for eternity. Before running the program:

sudo mkdir /var/log/cloudflare
sudo chown $USER:$USER /var/log/cloudflare/
touch /var/log/cloudflare/cloudflare.log
nano /etc/logrotate.d/cloudflare

Enter in /etc/logrotate.d/cloudflare file:

/var/log/cloudflare/cloudflare.log {
	rotate 7
	daily
}

Save the file. Then change directory back to where this script (lwp-cloudflare-dyndns.sh) is located. For me, ~/cloudflare. Finally:
ln -s cloudflare.log /var/log/cloudflare/cloudflare.log

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