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

@awkto

This comment has been minimized.

Copy link

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

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