Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

kanetzach commented Jun 25, 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

@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

kanetzach commented Jul 12, 2019

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

MohaBeacon commented Jul 24, 2019

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

strontiumss commented Dec 20, 2019

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

pedrof1gueiredo commented Feb 29, 2020

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

Afr0king commented Nov 25, 2020

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

sunrisepi commented Feb 22, 2021

Thank you @Firsh, works great!

@mrbscott
Copy link

mrbscott commented Jul 31, 2021

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

heygambo commented Sep 28, 2021

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

@LilToaster69
Copy link

LilToaster69 commented Dec 18, 2021

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.

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