Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A bash script to update a Cloudflare DNS A record with the external IP of the source machine
#!/bin/bash
# A bash script to update a Cloudflare DNS A record with the external IP of the source machine
# Used to provide DDNS service for my home
# Needs the DNS record pre-creating on Cloudflare
# Proxy - uncomment and provide details if using a proxy
#export https_proxy=http://<proxyuser>:<proxypassword>@<proxyip>:<proxyport>
# Cloudflare zone is the zone which holds the record
zone=example.com
# dnsrecord is the A record which will be updated
dnsrecord=www.example.com
## Cloudflare authentication details
## keep these private
cloudflare_auth_email=me@cloudflare.com
cloudflare_auth_key=1234567890abcdef1234567890abcdef
# Get the current external IP address
ip=$(curl -s -X GET https://checkip.amazonaws.com)
echo "Current IP is $ip"
if host $dnsrecord 1.1.1.1 | grep "has address" | grep "$ip"; then
echo "$dnsrecord is currently set to $ip; no changes needed"
exit
fi
# if here, the dns record needs updating
# get the zone id for the requested zone
zoneid=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone&status=active" \
-H "X-Auth-Email: $cloudflare_auth_email" \
-H "X-Auth-Key: $cloudflare_auth_key" \
-H "Content-Type: application/json" | jq -r '{"result"}[] | .[0] | .id')
echo "Zoneid for $zone is $zoneid"
# get the dns record id
dnsrecordid=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records?type=A&name=$dnsrecord" \
-H "X-Auth-Email: $cloudflare_auth_email" \
-H "X-Auth-Key: $cloudflare_auth_key" \
-H "Content-Type: application/json" | jq -r '{"result"}[] | .[0] | .id')
echo "DNSrecordid for $dnsrecord is $dnsrecordid"
# update the record
curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records/$dnsrecordid" \
-H "X-Auth-Email: $cloudflare_auth_email" \
-H "X-Auth-Key: $cloudflare_auth_key" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"$dnsrecord\",\"content\":\"$ip\",\"ttl\":1,\"proxied\":false}" | jq
@GerBreOwn

This comment has been minimized.

Copy link

@GerBreOwn GerBreOwn commented Jul 9, 2019

I currently have 3 domains recorded on Cloudflare. Do you have any suggestions on how I can use this script to update all 3 at the same time?
Thanks,
Gerald S. Brown

@Tras2

This comment has been minimized.

Copy link
Owner Author

@Tras2 Tras2 commented Jul 9, 2019

I currently have 3 domains recorded on Cloudflare. Do you have any suggestions on how I can use this script to update all 3 at the same time?
Thanks,
Gerald S. Brown

I'm far from an expert on Linux systems, but my guess would be 1 of 2 ways:-

  1. Quick way
    a) make 3 copies of the script
    b) modified for each of your domains
    c) run them via 3 separate cron jobs or via 1 cron job which runs a master script to run the other 3

  2. Long (better) way
    a) modify the script to accept parameters and then do as 1c passing in the parameters for each of your domains

Hope that helps

@foobarhl

This comment has been minimized.

Copy link

@foobarhl foobarhl commented Aug 25, 2019

Posted a forked version that uses bearer tokens from the new beta APi tokens interface. Much nicer than using a global key.

RE: multiple zones, you could wrap it in a loop that reads a list set in /etc/default/cloudflare-zones or some such. We don't have a specific need for that, but feel free to reach out if you need any custom coding. Thanks!

@dsebastien

This comment has been minimized.

Copy link

@dsebastien dsebastien commented Jan 3, 2020

For those like me who might end up here, API tokens can be created from here: https://dash.cloudflare.com/profile/api-tokens
And be used like this:

curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
     -H "Authorization: Bearer YOUR_TOKEN_GOES_HERE" \
     -H "Content-Type:application/json"
@raman3366

This comment has been minimized.

Copy link

@raman3366 raman3366 commented Jan 26, 2020

Nice working bash script. For those who get jq not found error during command execution, install jq using the following command:

sudo apt-get install -y jq

Those who use CNAME records for www and other sub domains, set the following:

dnsrecord=$zone

@madatomanic

This comment has been minimized.

Copy link

@madatomanic madatomanic commented May 13, 2020

Ah I got it working. You have to use "API Key" instead of "API Token" from Cloudflare.

Additionally, it seems the script will fail if the record does not exist yet.

I forked to add logging messages to syslog with timestamps.

Thanks for the awesome script! It saved me having to spend $25 for noip.com or $50 for dyndns.net.

@myfingerhurt

This comment has been minimized.

Copy link

@myfingerhurt myfingerhurt commented Jun 10, 2020

openwrt doesn't have host, this works for me.
nslookup $dnsrecord 1.1.1.1 | grep -i "address" | grep "$ip";

@OnlyHardOfficial

This comment has been minimized.

Copy link

@OnlyHardOfficial OnlyHardOfficial commented Aug 20, 2020

bash cloudflare-ddns-update.sh
cloudflare-ddns-update.sh: line 37: jq: command not found

  • Trying 104.19.193.29:443...
  • Connected to api.cloudflare.com (104.19.193.29) port 443 (#0)
  • ALPN, offering h2
  • ALPN, offering http/1.1
    } [5 bytes data]
  • TLSv1.3 (OUT), TLS handshake, Client hello (1):
    } [512 bytes data]
  • TLSv1.3 (IN), TLS handshake, Server hello (2):
    { [122 bytes data]
  • TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
    { [19 bytes data]
  • TLSv1.3 (IN), TLS handshake, Certificate (11):
    { [2206 bytes data]
  • TLSv1.3 (IN), TLS handshake, CERT verify (15):
    { [79 bytes data]
  • TLSv1.3 (IN), TLS handshake, Finished (20):
    { [52 bytes data]
  • TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
    } [1 bytes data]
  • TLSv1.3 (OUT), TLS handshake, Finished (20):
    } [52 bytes data]
  • SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
  • ALPN, server accepted to use h2
  • Server certificate:
  • subject: C=US; ST=CA; L=San Francisco; O=Cloudflare, Inc.; CN=api.cloudflare.com
  • start date: Jul 20 00:00:00 2020 GMT
  • expire date: Jul 20 12:00:00 2021 GMT
  • subjectAltName: host "api.cloudflare.com" matched cert's "api.cloudflare.com"
  • issuer: C=US; O=Cloudflare, Inc.; CN=Cloudflare Inc ECC CA-3
  • SSL certificate verify ok.
  • Using HTTP2, server supports multi-use
  • Connection state changed (HTTP/2 confirmed)
  • Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
    } [5 bytes data]
  • Using Stream ID: 1 (easy handle 0x55a69181fe40)
    } [5 bytes data]

GET /client/v4/zones/1d04d2011b673a322c222430861e1f83/dns_records?type=A&name=housingdata.pt HTTP/2
Host: api.cloudflare.com
user-agent: curl/7.71.1
accept: /
authorization: Bearer 354b1f71b0c5d2ecfaec484a86d369bef28e0
content-type: application/json

{ [5 bytes data]

  • TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
    { [238 bytes data]
  • TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
    { [238 bytes data]
  • old SSL session ID is stale, removing
    { [5 bytes data]
  • Connection state changed (MAX_CONCURRENT_STREAMS == 256)!
    } [5 bytes data]
    < HTTP/2 400
    < date: Thu, 20 Aug 2020 13:35:10 GMT
    < content-type: application/json
    < set-cookie: __cfduid=d506f6a768326a07c89fc8e072adf40781597930510; expires=Sat, 19-Sep-20 13:35:10 GMT; path=/; domain=.api.cloudflare.com; HttpOnly; SameSite=Lax; Secure
    < cf-ray: 5c5c7ef97f805d2b-LIS
    < set-cookie: __cflb=0H28vgHxwvgAQtjUGU56Rb8iNWZVUvXhiagV9Gtpb81; SameSite=Lax; path=/; expires=Thu, 20-Aug-20 16:05:11 GMT; HttpOnly
    < cf-cache-status: DYNAMIC
    < cf-request-id: 04adadafe700005d2b7f039200000001
    < expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
    < server: cloudflare
    <
    { [77 bytes data]
  • Connection #0 to host api.cloudflare.com left intact
    (23) Failed writing body
    {"success":false,"errors":[{"code":10000,"message":"PUT method with API Tokens is not supported by this API"}]}

Someting about this??

@Skamasle

This comment has been minimized.

Copy link

@Skamasle Skamasle commented Aug 22, 2020

@OnlyHardOfficial You not have JQ installed, jq is a jquery processor

Try apt-get install jq or yum install jq

@bnuby

This comment has been minimized.

Copy link

@bnuby bnuby commented Aug 28, 2020

I have modify the script to allow update multiple dnsrecord at the same time.

#!/bin/bash

# A bash script to update a Cloudflare DNS A record with the external IP of the source machine
# Used to provide DDNS service for my home
# Needs the DNS record pre-creating on Cloudflare

# Proxy - uncomment and provide details if using a proxy
#export https_proxy=http://<proxyuser>:<proxypassword>@<proxyip>:<proxyport>

# Cloudflare zone is the zone which holds the record
zone=example.com
# dnsrecords is the A record which will be updated
dnsrecords=(*.example.com www.example.com home.example.com)

## Cloudflare authentication details
## keep these private
cloudflare_auth_email=example@cloudflare.com
cloudflare_auth_key=examplepassword


# Get the current external IP address
ip=$(curl -s -X GET https://checkip.amazonaws.com)

echo "Current IP is $ip"

# get the zone id for the requested zone
zoneid=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone&status=active" \
  -H "X-Auth-Email: $cloudflare_auth_email" \
  -H "X-Auth-Key: $cloudflare_auth_key" \
  -H "Content-Type: application/json" | jq -r '{"result"}[] | .[0] | .id')

echo "Zoneid for $zone is $zoneid"

# loop dnsrecords
for dnsrecord in ${dnsrecords[@]}
do

  # check is host need to update
  if host $dnsrecord 1.1.1.1 | grep "has address" | grep "$ip"; then
    echo "$dnsrecord is currently set to $ip; no changes needed"
    continue
  fi

  # if here, the dns record needs updating

  # get the dns record id
  dnsrecordid=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records?type=A&name=$dnsrecord" \
    -H "X-Auth-Email: $cloudflare_auth_email" \
    -H "X-Auth-Key: $cloudflare_auth_key" \
    -H "Content-Type: application/json" | jq -r '{"result"}[] | .[0] | .id')

  echo "DNSrecordid for $dnsrecord is $dnsrecordid"

  # update the record
  curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records/$dnsrecordid" \
    -H "X-Auth-Email: $cloudflare_auth_email" \
    -H "X-Auth-Key: $cloudflare_auth_key" \
    -H "Content-Type: application/json" \
    --data "{\"type\":\"A\",\"name\":\"$dnsrecord\",\"content\":\"$ip\",\"ttl\":1,\"proxied\":false}" | jq


done

Hope this help.

@OnlyHardOfficial

This comment has been minimized.

Copy link

@OnlyHardOfficial OnlyHardOfficial commented Aug 30, 2020

Yes....

But, this script have a litle bug. For example, if you have:
A = test.domain.com --> xxx.xxx.xxx.xxx
CNAME= domain.com ---> test.domain.com

This bug will convert all CNAMEs to A records

Stay alert...

@OnlyHardOfficial

This comment has been minimized.

Copy link

@OnlyHardOfficial OnlyHardOfficial commented Aug 30, 2020

Yes....

But, this script have a litle bug. For example, if you have:
A = test.domain.com --> xxx.xxx.xxx.xxx
CNAME= domain.com ---> test.domain.com

This bug will convert all CNAMEs to A records

Stay alert...

@pkgl

This comment has been minimized.

Copy link

@pkgl pkgl commented Sep 15, 2020

Yes....

But, this script have a litle bug. For example, if you have:
A = test.domain.com --> xxx.xxx.xxx.xxx
CNAME= domain.com ---> test.domain.com

This bug will convert all CNAMEs to A records

Stay alert...

Just change the last line

--data "{\"type\":\"A\",\"**name**\":\"$dnsrecord\",\"content\":\"$ip\",\"ttl\":1,\"proxied\":false}" | jq

to

--data "{\"type\":\"**CNAME**\",\"name\":\"$dnsrecord\",\"content\":\"$ip\",\"ttl\":1,\"proxied\":false}" | jq

valid "type" values: A, AAAA, CNAME, TXT, SRV, LOC, MX, NS, SPF, CERT, DNSKEY, DS, NAPTR, SMIMEA, SSHFP, TLSA, URI
read only

https://api.cloudflare.com/#dns-records-for-a-zone-update-dns-record

@mchepukov

This comment has been minimized.

Copy link

@mchepukov mchepukov commented Oct 21, 2020

It's better to use use Bear token, just
replace -H "X-Auth-Key: $cloudflare_auth_key" \ to -H "Authorization: Bearer $cloudflare_auth_key" \

@SteveElliott

This comment has been minimized.

Copy link

@SteveElliott SteveElliott commented Oct 22, 2020

It's better to use use Bear token, just
replace -H "X-Auth-Key: $cloudflare_auth_key" \ to -H "Authorization: Bearer $cloudflare_auth_key" \

this should be
-H "Authorization: Bearer $cloudflare_auth_token"

https://dash.cloudflare.com/profile/api-tokens says:

API Tokens use the standard Authorization: Bearer header for authentication instead of x-auth-email and x-auth-key that API Keys use.

Steve Elliott

@mchepukov

This comment has been minimized.

Copy link

@mchepukov mchepukov commented Oct 22, 2020

I agree, but in this script using $cloudflare_auth_key" as variable. As you say, it's better to rename the variable $cloudflare_auth_key" to $cloudflare_auth_token"

@SteveElliott

This comment has been minimized.

Copy link

@SteveElliott SteveElliott commented Oct 22, 2020

@SteveElliott

This comment has been minimized.

Copy link

@SteveElliott SteveElliott commented Oct 23, 2020

To clarify:

Your Cloudflare dashboard provides values for:

  • Zone ID
  • Account ID
  • API token
  • Global API key
  • Origin CA KEY

The API key is used for the X-Auth-Key: header
The API token is used for the Authorization: Bearer header

Steve

@IIPoliII

This comment has been minimized.

Copy link

@IIPoliII IIPoliII commented Nov 11, 2020

How can I say I don't want it to pass through the Cloudflare proxy?

@nhanledev

This comment has been minimized.

Copy link

@nhanledev nhanledev commented Feb 28, 2021

Thank you for the script, but it has multiple problems when I use it, so I will recap everything you need to know and the updated script down below for those who interest.

Firstly, you would need to install jq by running sudo apt install -y jq


# A bash script to update a Cloudflare DNS A record with the external IP of the source machine
# Used to provide DDNS service for my home
# Needs the DNS record pre-creating on Cloudflare

# Proxy - uncomment and provide details if using a proxy
#export https_proxy=http://<proxyuser>:<proxypassword>@<proxyip>:<proxyport>

# Cloudflare zone is the zone which holds the record
zone=example.com
# dnsrecord is the A record which will be updated
dnsrecord=www.example.com

## Cloudflare authentication details
## keep these private
cloudflare_auth_email=me@cloudflare.com
cloudflare_auth_key=1234567890abcdef1234567890abcdef


# Get the current external IP address
ip=$(curl -s -X GET https://checkip.amazonaws.com)

echo "Current IP is $ip"

if host $dnsrecord 1.1.1.1 | grep "has address" | grep "$ip"; then
  echo "$dnsrecord is currently set to $ip; no changes needed"
  exit
fi

# if here, the dns record needs updating

# get the zone id for the requested zone
zoneid=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone&status=active" \
  -H "X-Auth-Email: $cloudflare_auth_email" \
  -H "Authorization: Bearer $cloudflare_auth_key" \
  -H "Content-Type: application/json" | jq -r '{"result"}[] | .[0] | .id')

echo "Zoneid for $zone is $zoneid"

# get the dns record id
dnsrecordid=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records?type=A&name=$dnsrecord" \
  -H "X-Auth-Email: $cloudflare_auth_email" \
  -H "Authorization: Bearer $cloudflare_auth_key" \
  -H "Content-Type: application/json" | jq -r '{"result"}[] | .[0] | .id')

echo "DNSrecordid for $dnsrecord is $dnsrecordid"

# update the record
curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records/$dnsrecordid" \
  -H "X-Auth-Email: $cloudflare_auth_email" \
  -H "Authorization: Bearer $cloudflare_auth_key" \
  -H "Content-Type: application/json" \
  --data "{\"type\":\"A\",\"name\":\"$dnsrecord\",\"content\":\"$ip\",\"ttl\":1,\"proxied\":false}" | jq
@NanoG6

This comment has been minimized.

Copy link

@NanoG6 NanoG6 commented May 24, 2021

@nhanledev and all,
Forgive me for my ignorance, I only have basic knowledge of bash scripting.
But, shouldn't be after "if" there should be "then"?

if host $dnsrecord 1.1.1.1 | grep "has address" | grep "$ip"; then
  echo "$dnsrecord is currently set to $ip; no changes needed"
  exit
fi

# if here, the dns record needs updating
@nhanledev

This comment has been minimized.

Copy link

@nhanledev nhanledev commented May 24, 2021

@NanoG6 I don’t understand your statement clearly, but what I see from bash if else the statement has “then” inside the block. The code you quoted above also has “then” at the end of the first line.

@NanoG6

This comment has been minimized.

Copy link

@NanoG6 NanoG6 commented May 24, 2021

Yes it has “then”, but isn’t it only for “echo”? Or maybe there should be “else” after the “fi” for updating the record?

@nhanledev

This comment has been minimized.

Copy link

@nhanledev nhanledev commented May 24, 2021

If I understand it correctly, it is just a check of the current IP address of the input domain. If the if statement is true, the whole script is stopped so there is no need to use else here.

@saminaf

This comment has been minimized.

Copy link

@saminaf saminaf commented Jun 3, 2021

Thanks it Worked for me
used Api key instead of Api token
cloudflare_auth_key=1234567890abcdef1234567890abcdef

Then installed jq
apt-get install jq or yum install jq

and finally it was changing proxied to dns only so i changer the false in the last line to true
--data "{"type":"A","name":"$dnsrecord","content":"$ip","ttl":1,"proxied":false}" | jq

changed --data "{"type":"A","name":"$dnsrecord","content":"$ip","ttl":1,"proxied":true}" | jq

now i will try to make for two domin if worked then i will comment.

@saminaf

This comment has been minimized.

Copy link

@saminaf saminaf commented Jun 3, 2021

yes i done i just added second copy say with different name
cloudflare-ddns-update2.sh

and updated the two lines

zone=seconddomain.com
dnsrecord=seconddomain.com

and at the end write down this line to run second file
sh ./cloudflare-ddns-update2.sh

and its done.......

@wiredyeti

This comment has been minimized.

Copy link

@wiredyeti wiredyeti commented Aug 21, 2021

Could something like this be installed on a Ubiquity UDM-Pro if you can SSH into the console??

This is becoming a nightmare just trying to get a DDNS option setup and since my domain is already using cloudfare nameservers something like this seems like the perfect solution!

@Tras2

This comment has been minimized.

Copy link
Owner Author

@Tras2 Tras2 commented Aug 30, 2021

@kenjichanhkg

This comment has been minimized.

Copy link

@kenjichanhkg kenjichanhkg commented Sep 15, 2021

Thanks it works on my Mac mini m1

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