Skip to content

Instantly share code, notes, and snippets.

@Tras2
Last active July 22, 2025 07:13
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
[email protected]
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
@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/

@i8degrees
Copy link

@pilang I chose your script above the others from this gist! :-)

My fork of pilang repo contains two major new enhancements:

  • When logfile is set to a non-empty value, some basic sanity checks are done; an attempt at creating the given directory path and then the final file path is done at the top of the script. Failure results in the script halting with an error message.

  • ipv4_command and ipv6_command can now optionally be an array. This is for the sake of redundancy -- in case one of the given URLs is offline or such. Additionally, upon the first error seen in the array looping, the script halts with an error message.

Shortly after checking out your script, I was scratching my head, wondering why the script exited without any output, nor error. Even after I had the correct user, token and so forth, it was still exiting w/o output. It turns out that I had given curl an address that it could not find the right certificate for. This is why I began improving the script with the latter feature mention (command arrays). With my improvements, it should gracefully handle this and other error conditions.

Cheers,
Jeff

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