Skip to content

Instantly share code, notes, and snippets.

@Tras2
Last active November 28, 2024 08:15
Show Gist options
  • Save Tras2/cba88201b17d765ec065ccbedfb16d9a to your computer and use it in GitHub Desktop.
Save Tras2/cba88201b17d765ec065ccbedfb16d9a to your computer and use it in GitHub Desktop.
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
@megvadulthangya
Copy link

megvadulthangya commented Nov 27, 2021

That was an user error. Its working, but changing to my proxied settings to dns only, if i change the end of the file the proxied false to proxied true i guess its stays on proxied...

IPV6? Thats not changed.

But, how can i setup to work with pppoe... Run when reconnects and gets new ip? Can someone help to make that script? Crontab maybe?

or not...

/etc/ppp/ip-up.d/0clampmss ?
Put that this row:
bash /etc/ppp/ip-up.d/cloudflare-ddns-update.sh
And put there that script?

@g-vdn
Copy link

g-vdn commented Dec 5, 2021

FIX: Removing the "| jq" at the end fixed the issue, script running fine and updating the IP on cloudflare. Not sure on what the jq does, except for formatting the output into a more readable one for the user, but it seems that it is crashing the script when run in crontab.

Having troubles to run this script in crontab. Any ideas on how to do this? Running manually works great (with the modifications presented in this thread, changing proxied to true and changing to bearer auth). Fails at updating the record:

Current IP is xxx.xxx.xxx.xxx
Zone id for mydomain.com is xxxxxxxxxxxxxx
DNS record id for mydomain.com is xxxxxxxxxxxx
jq - commandline JSON processor [version 1.5-1-a5b5cbe]
Usage: jq [options] [file...]

    jq is a tool for processing JSON inputs, applying the
    given filter to its JSON text inputs and producing the
    filter's results as JSON on standard output.
    The simplest filter is ., which is the identity filter,
    copying jq's input to its output unmodified (except for
    formatting).
    For more advanced filters see the jq(1) manpage ("man jq")
    and/or https://stedolan.github.io/jq

    Some of the options include:
     -c             compact instead of pretty-printed output;
     -n             use `null` as the single input value;
     -e             set the exit status code based on the output;
     -s             read (slurp) all inputs into an array; apply filter to it;
     -r             output raw strings, not JSON texts;
     -R             read raw strings, not JSON texts;
     -C             colorize JSON;
     -M             monochrome (don't colorize JSON);
     -S             sort keys of objects on output;
     --tab  use tabs for indentation;
     --arg a v      set variable $a to value <v>;
     --argjson a v  set variable $a to JSON value <v>;
     --slurpfile a f        set variable $a to an array of JSON texts read from <f>;
    See the manpage for more options.

(23) Failed writing body

@jooosh
Copy link

jooosh commented Dec 19, 2021

Thanks @Tras2 for the simple script here and for everyone's comments. I had previously tried getting DDNS updates on Cloudflare with ddclient but kept banging my head against the wall with no success. As @SteveElliott called out, you should use a Cloudflare token. Using this method, an account email address is not required. With that in mind I created a fork that worked for me, and yes, you may get it to work without installing jq but I prefer the JSON output. Hopefully this helps someone else out who stumbles along here. Cheers!

@gadolf66
Copy link

gadolf66 commented Feb 1, 2022

Thank you!
Also thanks @SteveElliot and @jooosh for guiding into the right direction (use of token instead of the API Key)

@namnamir
Copy link

It is a great work, however the authentication of Cloudflare is altered. If someone looks for a working version of this script, I have modified it that could be found here.

#!/bin/bash
# based on https://gist.github.com/Tras2/cba88201b17d765ec065ccbedfb16d9a
# initial data; they need to be filled by the user
## API token
api_token=<YOUR_API_TOKEN>
## email address associated with the Cloudflare account
email=<YOUR_EMAIL>
## the zone (domain) should be modified
zone_name=<YOUR_DOMAIN>
## the dns record (sub-domain) should be modified
dns_record=<YOUR_SUB_DOMAIN>

# get the basic data
ipv4=$(curl -s -X GET -4 https://ifconfig.co)
ipv6=$(curl -s -X GET -6 https://ifconfig.co)
user_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
               -H "Authorization: Bearer $api_token" \
               -H "Content-Type:application/json" \
          | jq -r '{"result"}[] | .id'
         )

echo "Your IPv4 is: $ipv4"
echo "Your IPv6 is: $ipv6"

# check if the user API is valid and the email is correct
if [ $user_id ]
then
    zone_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone_name&status=active" \
                   -H "Content-Type: application/json" \
                   -H "X-Auth-Email: $email" \
                   -H "Authorization: Bearer $api_token" \
              | jq -r '{"result"}[] | .[0] | .id'
             )
    # check if the zone ID is 
    if [ $zone_id ]
    then
        # check if there is any IP version 4
        if [ $ipv4 ]
        then
            dns_record_a_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?type=A&name=$dns_record"  \
                                   -H "Content-Type: application/json" \
                                   -H "X-Auth-Email: $email" \
                                   -H "Authorization: Bearer $api_token"
                             )
            # if the IPv6 exist
            dns_record_a_ip=$(echo $dns_record_a_id |  jq -r '{"result"}[] | .[0] | .content')
            echo "The set IPv4 on Cloudflare (A Record) is:    $dns_record_a_ip"
            if [ $dns_record_a_ip != $ipv4 ]
            then
                # change the A record
                curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$(echo $dns_record_a_id | jq -r '{"result"}[] | .[0] | .id')" \
                     -H "Content-Type: application/json" \
                     -H "X-Auth-Email: $email" \
                     -H "Authorization: Bearer $api_token" \
                     --data "{\"type\":\"A\",\"name\":\"$dns_record\",\"content\":\"$ipv4\",\"ttl\":1,\"proxied\":false}" \
                | jq -r '.errors'
            else
                echo "The current IPv4 and DNS record IPv4 are the same."
            fi
        else
            echo "Could not get your IPv4. Check if you have it; e.g. on https://ifconfig.co"
        fi
            
        # check if there is any IP version 6
        if [ $ipv6 ]
        then
            dns_record_aaaa_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?type=AAAA&name=$dns_record"  \
                                      -H "Content-Type: application/json" \
                                      -H "X-Auth-Email: $email" \
                                      -H "Authorization: Bearer $api_token"
                                )
            # if the IPv6 exist
            dns_record_aaaa_ip=$(echo $dns_record_aaaa_id | jq -r '{"result"}[] | .[0] | .content')
            echo "The set IPv6 on Cloudflare (AAAA Record) is: $dns_record_aaaa_ip"
            if [ $dns_record_aaaa_ip != $ipv6 ]
            then
                # change the AAAA record
                curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$(echo $dns_record_aaaa_id | jq -r '{"result"}[] | .[0] | .id')" \
                     -H "Content-Type: application/json" \
                     -H "X-Auth-Email: $email" \
                     -H "Authorization: Bearer $api_token" \
                     --data "{\"type\":\"AAAA\",\"name\":\"$dns_record\",\"content\":\"$ipv6\",\"ttl\":1,\"proxied\":false}" \
                | jq -r '.errors'
            else
                echo "The current IPv6 and DNS record IPv6 are the same."
            fi
        else
            echo "Could not get your IPv6. Check if you have it; e.g. on https://ifconfig.co"
        fi  
    else
        echo "There is a problem with getting the Zone ID. Check if the Zone Name is correct."
    fi
else
    echo "There is a problem with either the email or the password"
fi

@jstokholm
Copy link

It is a great work, however the authentication of Cloudflare is altered. If someone looks for a working version of this script, I have modified it that could be found here.

#!/bin/bash
# based on https://gist.github.com/Tras2/cba88201b17d765ec065ccbedfb16d9a
# initial data; they need to be filled by the user
## API token
api_token=<YOUR_API_TOKEN>
## email address associated with the Cloudflare account
email=<YOUR_EMAIL>
## the zone (domain) should be modified
zone_name=<YOUR_DOMAIN>
## the dns record (sub-domain) should be modified
dns_record=<YOUR_SUB_DOMAIN>

# get the basic data
ipv4=$(curl -s -X GET -4 https://ifconfig.co)
ipv6=$(curl -s -X GET -6 https://ifconfig.co)
user_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
               -H "Authorization: Bearer $api_token" \
               -H "Content-Type:application/json" \
          | jq -r '{"result"}[] | .id'
         )

echo "Your IPv4 is: $ipv4"
echo "Your IPv6 is: $ipv6"

# check if the user API is valid and the email is correct
if [ $user_id ]
then
    zone_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone_name&status=active" \
                   -H "Content-Type: application/json" \
                   -H "X-Auth-Email: $email" \
                   -H "Authorization: Bearer $api_token" \
              | jq -r '{"result"}[] | .[0] | .id'
             )
    # check if the zone ID is 
    if [ $zone_id ]
    then
        # check if there is any IP version 4
        if [ $ipv4 ]
        then
            dns_record_a_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?type=A&name=$dns_record"  \
                                   -H "Content-Type: application/json" \
                                   -H "X-Auth-Email: $email" \
                                   -H "Authorization: Bearer $api_token"
                             )
            # if the IPv6 exist
            dns_record_a_ip=$(echo $dns_record_a_id |  jq -r '{"result"}[] | .[0] | .content')
            echo "The set IPv4 on Cloudflare (A Record) is:    $dns_record_a_ip"
            if [ $dns_record_a_ip != $ipv4 ]
            then
                # change the A record
                curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$(echo $dns_record_a_id | jq -r '{"result"}[] | .[0] | .id')" \
                     -H "Content-Type: application/json" \
                     -H "X-Auth-Email: $email" \
                     -H "Authorization: Bearer $api_token" \
                     --data "{\"type\":\"A\",\"name\":\"$dns_record\",\"content\":\"$ipv4\",\"ttl\":1,\"proxied\":false}" \
                | jq -r '.errors'
            else
                echo "The current IPv4 and DNS record IPv4 are the same."
            fi
        else
            echo "Could not get your IPv4. Check if you have it; e.g. on https://ifconfig.co"
        fi
            
        # check if there is any IP version 6
        if [ $ipv6 ]
        then
            dns_record_aaaa_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?type=AAAA&name=$dns_record"  \
                                      -H "Content-Type: application/json" \
                                      -H "X-Auth-Email: $email" \
                                      -H "Authorization: Bearer $api_token"
                                )
            # if the IPv6 exist
            dns_record_aaaa_ip=$(echo $dns_record_aaaa_id | jq -r '{"result"}[] | .[0] | .content')
            echo "The set IPv6 on Cloudflare (AAAA Record) is: $dns_record_aaaa_ip"
            if [ $dns_record_aaaa_ip != $ipv6 ]
            then
                # change the AAAA record
                curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$(echo $dns_record_aaaa_id | jq -r '{"result"}[] | .[0] | .id')" \
                     -H "Content-Type: application/json" \
                     -H "X-Auth-Email: $email" \
                     -H "Authorization: Bearer $api_token" \
                     --data "{\"type\":\"AAAA\",\"name\":\"$dns_record\",\"content\":\"$ipv6\",\"ttl\":1,\"proxied\":false}" \
                | jq -r '.errors'
            else
                echo "The current IPv6 and DNS record IPv6 are the same."
            fi
        else
            echo "Could not get your IPv6. Check if you have it; e.g. on https://ifconfig.co"
        fi  
    else
        echo "There is a problem with getting the Zone ID. Check if the Zone Name is correct."
    fi
else
    echo "There is a problem with either the email or the password"
fi

I have tried this updated script after making a api_token from the template.

getting this error and the record is not updated:

./dyndns.sh: line 5: XXX: not found
./dyndns.sh: line 11: home: not found
Your IPv4 is: x.x.x.x
Your IPv6 is: 
The set IPv4 on Cloudflare (A Record) is:    null
[
  {
    "code": 7003,
    "message": "Could not route to /zones/null/dns_records/null, perhaps your object identifier is invalid?"
  },
  {
    "code": 7000,
    "message": "No route for that URI"
  }
]
Could not get your IPv6. Check if you have it; e.g. on https://ifconfig.co

The token works well with Cloudflares test command.

@namnamir
Copy link

The warning Could not get your IPv6. Check if you have it; e.g. on https://ifconfig.co says that you do not have any IPv6. It should work :) check your Cloudflare.

But I will add some lines to give messages that the settings is done.

@jstokholm
Copy link

Thank you very much, I had put something wrong in the text. The script works now even though I don't have an IPv6.

@namnamir
Copy link

namnamir commented Mar 28, 2022

I have just updated the code; thanks to @jstokholm that could be found here.

#!/bin/bash
# based on https://gist.github.com/Tras2/cba88201b17d765ec065ccbedfb16d9a
# initial data; they need to be filled by the user
## API token; e.g. FErsdfklw3er59dUlDce44-3D43dsfs3sddsFoD3
api_token=<YOUR_API_TOKEN>
## the email address associated with the Cloudflare account; e.g. email@gmail.com
email=<YOUR_EMAIL>
## the zone (domain) should be modified; e.g. example.com
zone_name=<YOUR_DOMAIN>
## the dns record (sub-domain) should be modified; e.g. sub.example.com
dns_record=<YOUR_SUB_DOMAIN>

# get the basic data
ipv4=$(curl -s -X GET -4 https://ifconfig.co)
ipv6=$(curl -s -X GET -6 https://ifconfig.co)
user_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
               -H "Authorization: Bearer $api_token" \
               -H "Content-Type:application/json" \
          | jq -r '{"result"}[] | .id'
         )

# write down IPv4 and/or IPv6
if [ $ipv4 ]; then echo -e "\033[0;32m [+] Your public IPv4 address: $ipv4"; else echo -e "\033[0;33m [!] Unable to get any public IPv4 address."; fi
if [ $ipv6 ]; then echo -e "\033[0;32m [+] Your public IPv6 address: $ipv6"; else echo -e "\033[0;33m [!] Unable to get any public IPv6 address."; fi

# check if the user API is valid and the email is correct
if [ $user_id ]
then
    zone_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone_name&status=active" \
                   -H "Content-Type: application/json" \
                   -H "X-Auth-Email: $email" \
                   -H "Authorization: Bearer $api_token" \
              | jq -r '{"result"}[] | .[0] | .id'
             )
    # check if the zone ID is avilable
    if [ $zone_id ]
    then
        # check if there is any IP version 4
        if [ $ipv4 ]
        then
            dns_record_a_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?type=A&name=$dns_record"  \
                                   -H "Content-Type: application/json" \
                                   -H "X-Auth-Email: $email" \
                                   -H "Authorization: Bearer $api_token"
                             )
            # if the IPv4 exist
            dns_record_a_ip=$(echo $dns_record_a_id |  jq -r '{"result"}[] | .[0] | .content')
            if [ $dns_record_a_ip != $ipv4 ]
            then
                # change the A record
                curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$(echo $dns_record_a_id | jq -r '{"result"}[] | .[0] | .id')" \
                     -H "Content-Type: application/json" \
                     -H "X-Auth-Email: $email" \
                     -H "Authorization: Bearer $api_token" \
                     --data "{\"type\":\"A\",\"name\":\"$dns_record\",\"content\":\"$ipv4\",\"ttl\":1,\"proxied\":false}" \
                | jq -r '.errors'
                # write the result
                echo -e "\033[0;32m [+] The IPv4 is successfully set on Cloudflare as the A Record with the value of:    $dns_record_a_ip"
            else
                echo -e "\033[0;37m [~] The current IPv4 and  the existing on on Cloudflare are the same; there is no need to apply it."
            fi
        fi
            
        # check if there is any IP version 6
        if [ $ipv6 ]
        then
            dns_record_aaaa_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?type=AAAA&name=$dns_record"  \
                                      -H "Content-Type: application/json" \
                                      -H "X-Auth-Email: $email" \
                                      -H "Authorization: Bearer $api_token"
                                )
            # if the IPv6 exist
            dns_record_aaaa_ip=$(echo $dns_record_aaaa_id | jq -r '{"result"}[] | .[0] | .content')
            if [ $dns_record_aaaa_ip != $ipv6 ]
            then
                # change the AAAA record
                curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$(echo $dns_record_aaaa_id | jq -r '{"result"}[] | .[0] | .id')" \
                     -H "Content-Type: application/json" \
                     -H "X-Auth-Email: $email" \
                     -H "Authorization: Bearer $api_token" \
                     --data "{\"type\":\"AAAA\",\"name\":\"$dns_record\",\"content\":\"$ipv6\",\"ttl\":1,\"proxied\":false}" \
                | jq -r '.errors'
                # write the result
                echo -e "\033[0;32m [+] The IPv6 is successfully set on Cloudflare as the AAAA Record with the value of: $dns_record_aaaa_ip"
            else
                echo -e "\033[0;37m [~] The current IPv6 address and the existing on on Cloudflare are the same; there is no need to apply it."
            fi
        fi  
    else
        echo -e "\033[0;31m [-] There is a problem with getting the Zone ID (subdomain) or the email address (username). Check them and try again."
    fi
else
    echo -e "\033[0;31m [-] There is a problem with either the API token. Check it and try again."
fi

@matiasba
Copy link

Im just going to leave my take on this script here. It only supports ipv4 but allows to use the https_proxy funcionality of cloudflare.

@kd0034
Copy link

kd0034 commented Feb 8, 2023

Hello guys, can someone tell me how to i find the zone?

@namnamir
Copy link

@kd0034

I would basically your domain(s) that is defined for the API key.

@devinderaujla
Copy link

devinderaujla commented Feb 20, 2023

@namnamir Thank you for the updated script, it works like a charm. I needed to update multiple hostnames and some of those had proxy set to true. I made changes to your script to accommodate. Also, for some reason, the script was timing out on the first section where it grabs the ip from ifconfig so I increased the wait time. It is available here.

@deviationist
Copy link

Thanks, this was all I needed! ddclient was just overly complicated!

@NChaves
Copy link

NChaves commented Mar 3, 2023

Picking up on the above, have made a few changes to fit my requirements.

  • Checks for changes
  • Logs each check
  • Verifies if new "IP" is of correct format (to eliminate errors)
  • Updates multiple entries (proxied and not)
  • Logs each change

Now running a cronjob to check and update my DNS records (proxied and not) when there is an IP change, see this repo if looking for similar.
Full article can be found here.

@fuggla
Copy link

fuggla commented Oct 2, 2023

Stripped down IPv4 version based on previous scripts. You'll need jq and curl.

#!/usr/bin/env bash
api_token=<CF token>
email=<CF account email>
zone_name=<DNS zone>
dns_record=<A record FQDN>

set -e

user_id=$(curl -s \
	-X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
	-H "Authorization: Bearer $api_token" \
	-H "Content-Type:application/json" \
	| jq -r '{"result"}[] | .id')

zone_id=$(curl -s \
	-X GET "https://api.cloudflare.com/client/v4/zones?name=$zone_name&status=active" \
	-H "Content-Type: application/json" \
	-H "X-Auth-Email: $email" \
	-H "Authorization: Bearer $api_token" \
	| jq -r '{"result"}[] | .[0] | .id')

record_data=$(curl -s \
	-X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?type=A&name=$dns_record"  \
	-H "Content-Type: application/json" \
	-H "X-Auth-Email: $email" \
	-H "Authorization: Bearer $api_token")

record_id=$(jq -r '{"result"}[] | .[0] | .id' <<< $record_data)
cf_ip=$(jq -r '{"result"}[] | .[0] | .content' <<< $record_data)
ext_ip=$(curl -s -X GET -4 https://ifconfig.co)

if [[ $cf_ip != $ext_ip ]]; then
	result=$(curl -s \
		-X PUT "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$record_id" \
		-H "Content-Type: application/json" \
		-H "X-Auth-Email: $email" \
		-H "Authorization: Bearer $api_token" \
		--data "{\"type\":\"A\",\"name\":\"$dns_record\",\"content\":\"$ext_ip\",\"ttl\":1,\"proxied\":false}" \
		| jq .success)
	if [[ $result == "true" ]]; then
		echo "$dns_record updated to: $ext_ip"
		exit 0
	else
		echo "$dns_record update failed"
		exit 1
	fi
else
	echo "$dns_record already up do date"
	exit 0
fi

@pilang
Copy link

pilang commented May 29, 2024

Liked your stripped down version. Added IPV6, config in separate file, logging and many subdomains.

https://github.com/pilang/cloudflare_ddns/

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