Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Cloudflare Client API v4 DDNS IP Update Using Bash Script (IPv4/ IPv6)

Cloudflare DDNS using Bash Script with Crontab/ Systemd Timer

This script will check if external IP is changed or not and will update the external IP of A or AAAA record in Cloudflare DNS using API token/ global API key method.

Script Requirements

How to Use it?

  1. Quick Tips:
  • Location: Put the cfddns.sh file to anyhwere you like. E.g., /home/scripts. If SELinux is set to enforcing then copy the file to usr/local/bin.
  • Interface: Edit interface to an active connection. You can find out interface by simply running ip a or ifconfig, if you find anything like eth0, enp1s0, wlan0, wlp1s0 etc. this is the interface. Keep in mind that, you have to know which interface you want to use for this script. It will only work with an interface which can get a public IP.
  1. For Crontab:
  • Open terminal.
  • Give the file execute permission, type/ copy sudo chmod +x /location/cfddns.sh press Enter.
  • Open crontab, type/ copy crontab -e press Enter.
  • If you want to run the job every minute, type/ copy * * * * * /location/cfddns.sh.
  • If you want to run the job every 5 minute, type/ copy 5 * * * * /location/cfddns.sh.
  • If you want more time flexibility then goto this link.
  • After setting cron press Esc and type :wq then press Enter.
  • Setting up cron is completed!
  1. For Systemd Timer:
  • Open terminal.
  • Give the file execute permission, type/ copy sudo chmod +x /location/cfddns.sh press Enter.
  • Create a systemd service unit, type/ copy vi /etc/systemd/system/cfddns.service press Enter.
  • Copy all of the content from cfddns.service down below.
  • After setting service unit press Esc and type :wq then press Enter.
  • Create a systemd timer unit at the same location of service unit, type/ copy vi /etc/systemd/system/cfddns.timer press Enter.
  • Copy all of the content from cfddns.timer down below.
  • If you want to run the timer unit every minute, edit/ copy *:0/1 (This is given on the cfddns.timer file).
  • If you want to run the timer unit every 5 minute, edit/ copy *:0/5.
  • If you want more time flexibility then goto this link.
  • After setting timer unit press Esc and type :wq then press Enter.
  • Reload systemd, type/ copy sudo systemctl daemon-reload.
  • Enable timer unit, type/ copy sudo systemctl enable cfddns.timer.
  • Start timer unit, type/ copy sudo systemctl start cfddns.timer.
  • Setting up systemd timer is completed!

How it Works?

This is a verbal representation of the script explaining how the script works.

Steps:

  1. Script will start executing and shows [CF DDNS] IP CHECK INITIATED FOR example.com....

  2. Now it will check for INTERFACE CONNECTIVITY and shows [CF DDNS] CHECKING FOR INTERFACE CONNECTIVITY....

  • If selected interface is disconnected physically then it will show [CF DDNS] INTERFACE IS UNAVAILABLE! and exit.
  • If selected interface is connected physically but it is not active then it will show [CF DDNS] INTERFACE IS DISCONNECTED! and exit.
  • If selected interface is connected physically and it is active then it will show [CF DDNS] INTERFACE IS CONNECTED! and go to next step.
  1. Now it will check for INTERNET AVAILABILITY and shows [CF DDNS] CHECKING FOR INTERNET AVAILABILITY....
  • If selected interface has internet then it will show [CF DDNS] INTERNET IS AVAILABLE! and go to next step.
  • If selected interface has no internet then it will show [CF DDNS] INTERNET IS UNAVAILABLE! and exit.
  1. Now it will check for SAVED IP and show [CF DDNS] GETTING SAVED IP....
  • If you run this script for the first time then it will show SAVED IP: NONE.
  • If you run this again then it will show SAVED IP: 1.2.3.4.
  1. Now it will check for CURRENT IP and show [CF DDNS] CHECKING FOR NEW IP....
  • If selected interface public IP is unchanged and valid then it will show [CF DDNS] NO NEW IP DETECTED! and exit.
  • If selected interface public IP is changed and valid then it will show [CF DDNS] NEW IP DETECTED! and go to next step.
  • If selected interface can not check for CURRENT IP due to curl error/ IP is invalid then it will show [CF DDNS] CHECKING FOR NEW IP FAILED! and exit.
  1. Now it will check for zone_id, record_id. if it get's new IP from Step 5 it will show [CF DDNS] CHECKING FOR ZONE & RECORD ID's....
  • If zone_id and record_id is found then it will show [CF DDNS] ZONE & RECORD ID'S FOUND! and go to next step.
  • If zone_id and record_id is not found then it will show [CF DDNS] ZONE & RECORD ID'S NOT FOUND!, [CF DDNS] GETTING ZONE & RECORD ID'S.... After getting the id's it will show [CF DDNS] ZONE & RECORD ID'S SAVED! and go to next step.
  1. Now it will check for IP change. if it get's new IP from Step 5 it will show [CF DDNS] UPDATING IP....
  • If it failed to update the IP then it will show [CF DDNS] IP UPDATE FAILED! DUMPING RESULTS: errormessage, [CF DDNS] SAVING LOG... and exit.
  • If it succeeded to update the IP then it will show [CF DDNS] IP UPDATED TO: 1.2.3.4, [CF DDNS] SAVING IP... , [CF DDNS] SAVING LOG... and exit.

Notes

  • Thanks to @benkulbertis and @lifehome!
  • I have written the instructions based on CentOS 8.x.x.
  • Files name are started with 0, 1, 2, 3 because of orderly manner.
  • I am noob to this Scripting Business so if you find any mistakes please comment it below!
  • Keep in mind that whether you use crontab or systemd timer both will create huge size log files! It will be better if you use logrotate to keep the log files at a minimum size.
  • The default cfddns.timer is set to execute the script every minute. Please keep in mind not to spam the API or you will be rate limited.
  • A quote from Cloudflare FAQ:

    All calls through the Cloudflare Client API are rate-limited to 1200 every 5 minutes.

    Link

[Unit]
Description=Cloudflare DDNS Service
After=network.target
[Service]
Type=oneshot
#journalctl will not output debug message
StandardOutput=null
#journalctl will only output error message
StandardError=journal
ExecStart=/location/cfddns.sh
#If you want to add more script to one service then just add more ExecStart
#ExecStart=/location/a.sh
#ExecStart=/location/b.sh
#ExecStart=/location/c.sh
[Install]
WantedBy=multi-user.target
[Unit]
Description=Cloudflare DDNS Timer
[Timer]
OnCalendar=*:0/1
Persistent=true
[Install]
WantedBy=timers.target
#!/bin/bash
# Inspired from:
# benkulbertis/cloudflare-update-record.sh -> (https://gist.github.com/benkulbertis/fff10759c2391b6618dd)
# lifehome/README.md -> (https://gist.github.com/lifehome/eb3f7d798f9bc6720cdc7d2be1238d4f)
# SETTINGS
interface="enp1s0" # Which interface you want to use? E.g., enp1s0 (Ethernet), wlp1s0 (WiFi)
interface_ip_ver="4" # Which IP version you want to use for local IP address? E.g., IPv4 (4), IPv6 (6)
internet_check_link="https://1.1.1.1". # Which website you want to use for checking internet availability? E.g., Google (https://google.com), 1.1.1.1 (https://1.1.1.1)
cur_ip_link="https://api.ipify.org" # Which IP version of public IP address API you want to use? E.g., IPv4 (https://api.ipify.org), IPv6 (https://api6.ipify.org)
# CLOUDFLARE API METHOD
cf_api="token" # How it will communicate with Cloudflare? E.g., API Token (token), Global API Key (gkey)
# CLOUDFLARE API TOKEN (If you are using global API key, no need to put info here)
bearer="a1b2c3d4e5f6g7" # Your cloudflare API token.
# CLOUDFLARE GLOBAL API KEY (If you are using API token, no need to put info here)
auth_email="abc@example.com" # Your cloudflare account email.
auth_key="a1b2c3d4e5f6g7" # Your cloudflare global API key.
# CLOUDFLARE DNS
zone_name="example.com" # Domain you want to include. E.g., example.com
record_name="www.example.com" # Record you want to include. E.g., example.com, www.example.com
record_type="A" # What type of record is it? E.g., IPv4 (A), IPv6 (AAAA)
proxied_value="true" # Do you want to proxy your site through cloudflare? E.g., Orange Cloud (true), Grey Cloud (false)
# CLOUDFLARE DDNS
ip_file="/location/ip.txt" # Edit "/location" and put your path location
ids_file="/location/cfddns.ids" # Edit "/location" and put your path location
log_file="/location/cfddns.log" # Edit "/location" and put your path location
# LOGGER
log()
{
if [[ $1 ]];
then
echo -e "[$(date)] - $1" >> $log_file
fi
}
# SCRIPT START
echo -e "\n[CF DDNS] IP CHECK INITIATED FOR $record_name...\n"
# INTERFACE CONNECTIVITY
echo "[CF DDNS] CHECKING FOR INTERFACE CONNECTIVITY..."
int_gs=$(nmcli d show $interface | awk 'NR == 5 {print $2}')
if [[ $int_gs == 20 ]];
then
echo "[CF DDNS] INTERFACE IS UNAVAILABLE!"
echo -e "[CF DDNS] EXITING...\n"
exit 1
else
if [[ $int_gs == 30 ]];
then
echo "[CF DDNS] INTERFACE IS DISCONNECTED!"
echo -e "[CF DDNS] EXITING...\n"
exit 1
else
if [[ $int_gs == 100 ]];
then
echo -e "[CF DDNS] INTERFACE IS CONNECTED!\n"
fi
fi
fi
# INTERNET AVAILABILITY
echo "[CF DDNS] CHECKING FOR INTERNET AVAILABILITY..."
interface_ip=$(ip -o -$interface_ip_ver addr list $interface | awk '{split ($4, ip, "/"); print ip[1]}')
internet_check=$(wget --bind-address=$interface_ip -q --spider -t 3 -T 5 $internet_check_link)
internet_check_ev=$?
if [[ $internet_check_ev == 0 ]];
then
echo -e "[CF DDNS] INTERNET IS AVAILABLE!\n"
else
echo "[CF DDNS] INTERNET IS UNAVAILABLE!"
echo -e "[CF DDNS] EXITING...\n"
exit 1
fi
# SAVED IP
echo "[CF DDNS] GETTING SAVED IP..."
if [[ -f $ip_file ]];
then
sav_ip=$(cat $ip_file)
echo -e "[CF DDNS] SAVED IP: $sav_ip\n"
else
echo -e "[CF DDNS] SAVED IP: NONE\n"
fi
# CURRENT IP
echo "[CF DDNS] CHECKING FOR NEW IP..."
cur_ip=$(curl --interface $interface_ip -s --retry 3 --connect-timeout 5 $cur_ip_link)
cur_ip_ev=$?
cur_ip_val=$(ipcalc -sc $cur_ip)
cur_ip_val_ev=$?
pre_ip=$sav_ip
if [[ $cur_ip_ev == 0 ]] && [[ $cur_ip_val_ev == 0 ]];
then
if [[ $cur_ip == $pre_ip ]];
then
echo "[CF DDNS] NO NEW IP DETECTED!"
echo -e "[CF DDNS] EXITING...\n"
exit 0
else
echo -e "[CF DDNS] NEW IP DETECTED!\n"
fi
else
echo "[CF DDNS] CHECKING FOR NEW IP FAILED!"
echo -e "[CF DDNS] EXITING...\n"
exit 1
fi
# RETRIEVE/ SAVE zone_id AND record_id
echo "[CF DDNS] CHECKING FOR ZONE & RECORD ID's..."
if [[ -f $ids_file ]] && [[ $(wc -l $ids_file | awk '{print $1}') == 2 ]];
then
echo -e "[CF DDNS] ZONE & RECORD ID'S FOUND!\n"
zone_id=$(head -1 $ids_file)
record_id=$(tail -1 $ids_file)
else
echo "[CF DDNS] ZONE & RECORD ID'S NOT FOUND!"
echo "[CF DDNS] GETTING ZONE & RECORD ID'S..."
# WHEN CLOUDFLARE API TOKEN IS SELECTED
if [[ $cf_api == token ]];
then
zone_id=$(curl --interface $interface_ip -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone_name" -H "Content-Type: application/json" -H "Authorization: Bearer $bearer"| jq -r '.result[].id')
record_id=$(curl --interface $interface_ip -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?type=$record_type&name=$record_name" -H "Content-Type: application/json" -H "Authorization: Bearer $bearer"| jq -r '.result[].id')
else
# WHEN CLOUDFLARE GLOBAL API KEY IS SELECTED
if [[ $cf_api == gkey ]];
then
zone_id=$(curl --interface $interface_ip -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone_name" -H "Content-Type: application/json" -H "X-Auth-Key: $auth_key" -H "X-Auth-Email: $auth_email" | jq -r '.result[].id')
record_id=$(curl --interface $interface_ip -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?type=$record_type&name=$record_name" -H "Content-Type: application/json" -H "X-Auth-Key: $auth_key" -H "X-Auth-Email: $auth_email" | jq -r '.result[].id')
fi
fi
echo "$zone_id" > $ids_file
echo "$record_id" >> $ids_file
echo -e "[CF DDNS] ZONE & RECORD ID'S SAVED!\n"
fi
# UPDATE RECORD
if [[ $cur_ip != $pre_ip ]];
then
echo "[CF DDNS] UPDATING IP..."
# WHEN CLOUDFLARE API TOKEN IS SELECTED
if [[ $cf_api == token ]];
then
update=$(curl --interface $interface_ip -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$record_id" -H "Content-Type: application/json" -H "Authorization: Bearer $bearer" --data "{\"id\":\"$zone_id\",\"type\":\"$record_type\",\"name\":\"$record_name\",\"content\":\"$cur_ip\",\"proxied\":$proxied_value}")
else
# WHEN CLOUDFLARE GLOBAL API KEY IS SELECTED
if [[ $cf_api == gkey ]];
then
update=$(curl --interface $interface_ip -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$record_id" -H "Content-Type: application/json" -H "X-Auth-Key: $auth_key" -H "X-Auth-Email: $auth_email" --data "{\"id\":\"$zone_id\",\"type\":\"$record_type\",\"name\":\"$record_name\",\"content\":\"$cur_ip\",\"proxied\":$proxied_value}")
fi
fi
fi
#UPDATE STATUS
if [[ $update == *"\"success\":false"* ]];
then
message="[CF DDNS] IP UPDATE FAILED! DUMPING RESULTS: \n$update"
echo -e "$message"
echo "[CF DDNS] SAVING LOG..."
log "$message"
echo -e "[CF DDNS] EXITING...\n"
exit 1
else
message="[CF DDNS] IP UPDATED TO: $cur_ip"
echo "$message"
echo "[CF DDNS] SAVING IP..."
echo "$cur_ip" > $ip_file
echo "[CF DDNS] SAVING LOG..."
log "$message"
echo -e "[CF DDNS] EXITING...\n"
exit 0
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.