Setting up Dynamic DNS through Cloudflare


A self-hosted server typically isn’t assigned a static public IP address, which means that its IPs change periodically, preventing stable access to the server. While it is quite difficult requiring your ISP (internet service provider) to fix your IP address, it is practical to bind your IP to a domain, and keep the bound IP updated whenever it changes. That is the idea of DDNS, dynamic domain name system.

I have tried two methods realizing DDNS, First I tried a DDNS provider ( with ddclient; later I found that Cloudflare provides APIs to update DNS, which means that you only need to write a shell script to check your IP periodically and update the DNS using the API when necessary. + ddclient

First you need to visit, sign up for an account, then you could create a domain ending with like, or something, which points to your real IP address. After you’ve created a DDNS domain, turn to the instruction page, where a snippet for ddclient set-up is provided below like this:

Then we need to set up ddclient to keep the IP updated. The GitHub page for ddclient is here Just follow the instructions in the readme file for installation.

After that, you could set your main domain a CNAME of the DDNS domain. By doing so, when someone tries to visit your main domain, the DNS provider redirects the request to the DDNS domain, which returns the real IP address.

This method works, but it has two drawbacks. You need a intermediate DDNS domain which is a drawback of the speed. Also, there are some problems for ddclient to handle IPv6 address.


I didn’t start using Cloudflare because of the DDNS; instead, Cloudflare does not indicate it has a ready-for-use DDNS service. I started using it because of its CDN (content distribution network) service, which globally distributes your website so that the visiting performance would be improved. I’ll talk about the CDN maybe later in another blog.

It was after I switched my DNS server to Cloudflare did I realize ddclient is also compatible with Cloudflare as well, so I modified my ddclient setup for Cloudflare, eliminating the intermediate domain.

Later I found that Cloudflare provides APIs to update DNS records, which means that ddclient is no longer needed as well.

My solution

The following method mainly refers to the blog by Rohan Jain. however it was written 6 years ago, some of the links and codes are no longer available; plus, I made some minor modifications to the original content so that it fits my current situation.

The API documentation is here:

You need to put a curl request as follows:

curl --request PUT \
  --url \
  --header 'Content-Type: application/json' \
  --header 'X-Auth-Email: ' \
  --data '{
  "content": "your_ip_address",
  "name": "",
  "proxied": true,
  "type": "AAAA",

There are some key parameters to take care. I’ll break them down in the following:

Zone Identifier

Enter the dashboard of your domain on Cloudflare, usually with a URL like “<your_account_id>/<your_domain>”, it’s on the bottom-right side, above your account id. Both of them is a 32-digit string, composed of numbers and lowercase letters.

Or you can refer to the official doc:

DNS Record Identifier

It’s a bit hard to get the DNS record id, that is, the “identifier” in the curl request. To get this, we need another API to list them all. Here is the documentation:

Similarly, we also need to put a curl request:

curl --request GET \
  --url \
  --header 'Content-Type: application/json' \
  --header 'X-Auth-Email: '

The zone identifier is what I’ve stated above. Then you need to find a method for authentication. Simply put, there are two methods, global API, or API token for a special purpose. Here as we only need to run this command once, there is no need to generate an API token, so I’ve chosen to use the global API key. You can find it here:

Then add two lines in the curl request:

  --header 'X-Auth-Email: [email protected]'
  --header 'X-Auth-Key: your_global_api_key'

Run it, then you would be able to find the DNS record id in the response message, it’s also a 32-digit string the same format as your zone id.

Authentication and API Token

I just mentioned the global API key. However, as the script updating IP address would be stored in my server and be automatically executed, it becomes dangerous to use the global key. So we need to create a token specially for this. It’s like the one-time password for POP3/IMAP in mail services.

In the very same page,, “Create Token” -> “Edit zone DNS”. Or you could refer to the official doc:

Then the authentication would be written like this:

 --header "Authorization: Bearer your_api_token"

If you would like to discover more about authentication, the documentation is here:

Way to Obtain your Current IP

In short, there are two methods: from your device, or from the web. As I have no public IPv4 address, plus all IPv6 address are public, there is no need to use the web method if your server is in a pure IPv6 environment. The specific command vary depending on your device. You can ask ChatGPT to write a command using ip addr plus grep with regex to extract your IP. Here is my code:

ip addr show dev eth0 | grep -o -P '(?<=inet6\s)[\w:]+' | sed -n '1p'

The whole script would be placed at /usr/local/bin/

#/usr/bin/env sh

# Docs:

# Get the Zone ID from:<account_id>/<>

# Get the existing identifier for DNS entry:

# Use API token for authenticate:

# Desired domain name

# Get previous IP address

# Get current IP address
_IP=$(ip addr show dev eth0 | grep -o -P '(?<=inet6\s)[\w:]+' | sed -n '1p')

# Get current time for logging
_CURRENT_TIME=$(TZ="Asia/Taipei" date +"%Y-%m-%d %H:%M:%S")

# If new/previous IPs match, no need for an update.
if [ "$_IP" = "$_PREV_IP" ]; then
    exit 0

    cat <<EOF
  "content": "$_IP",
  "name": "$DOMAIN_NAME",
  "proxied": true,
  "type": "AAAA",
  "ttl": 1

curl --request PUT \
    --url$ZONE_IDENTIFIER/dns_records/$IDENTIFIER \
    --header 'Content-Type: application/json' \
    --header "Authorization: Bearer $API_TOKEN" \
    --data "$_UPDATE" >/tmp/cloudflare-ddns-update.json &&
    echo $_IP >$_PREV_IP_FILE

echo "[$_CURRENT_TIME]: IP changed to [$_IP]." >>/tmp/cloudflare-ddns-ip-updates.log

Note we make a comparison to the IP last updated before trying to make another update. If the IP did not change, then we don’t need to send a curl request. A previous IP file needs to be created before running the script for the first time:

echo $(ip addr show dev eth0 | grep -o -P '(?<=inet6\s)[\w:]+' | sed -n '1p') >/tmp/previous-ip.txt

Configure the Service

Next, write a .service file in /etc/systemd/system/cloudflare-ddns.service

Description=Update DNS entry for this host to current IP

ExecStart=/bin/sh /usr/local/bin/

Try to start the service by:

sudo systemctl start cloudflare-ddns.service
# Check the results:
sudo journalctl -u cloudflare-ddns

Set up a Timer

I placed my .timer file in /etc/systemd/system/cloudflare-ddns.timer, so that the original .sh script would be automatically executed every 2 minutes.

Description=Update DNS entry in cloudflare every 2 minutes



To enable the timer, run:

sudo systemctl enable cloudflare-ddns.timer

This makes the timer start automatically when the server reboots. It does not mean that this service is started right now, so we need to force it start:

sudo systemctl start cloudflare-ddns.timer

Any changes applied

If you applied changes to your script, the timer unit should be reloaded and restarted:

sudo systemctl daemon-reload
sudo systemctl restart cloudflare-ddns.timer
sudo systemctl restart cloudflare-ddns.service


Leave a Reply