Automatically update DNS record

Aziz Fikri Mahmudi
9 min readJun 15, 2023

--

Finally the last story of this series!!! If you haven’t read the last story I wrote, kindly check it, because there’s a step on setting up your free DDNS!

With that being said, let’s take a look at today’s topic!

Image by full vector on Freepik

Automatically update my DNS record

Today we will look at how we will automatically update our DNS record stored in the server. As you’ve seen in the first story in this series, we will update our DNS record periodically based on the diagram below,

First, we need to check our current IP Address. For this to work, I’ll just use a bash script to call an API that provides IP checker,

curl -s https://api4.my-ip.io/ip.txt

after running the script above, you must get an output of your public IP Address, something like this,

# Call a website that provides information about our public IP
curl -s https://api4.my-ip.io/ip.txt
# Output: xxx.xxx.xxx.xxx

Okay, the checking part is done! After that, we need to think about how we gonna store our IP address so that we can compare the previous IP address with the current IP address.

To make things simple, I just gonna echo it into a plaintext file in our $HOME directory,

# This script gonna write down the IP address to a file named
# .last-ip at $HOME directory
echo $(curl -s https://api4.my-ip.io/ip.txt) | tee "$HOME/.last-ip"
# Output: xxx.xxx.xxx.xxx

# Check it again to make sure that we write it correctly
cat $HOME/.last-ip
# Output: xxx.xxx.xxx.xxx

Alright! We manage to store our current IP address in a file; with this, we can read it whenever we want!

Let’s try to implement some logic,

  1. We want to get our current IP address
  2. After we have it, we want to check if our current IP address is different from what we have
  3. If it is the same then just end the program
  4. Else we will update the DNS record on Cloudflare
# Get our current IP address from https://api4.my-ip.io/ip.txt
# and save it into a variable named ipAddr
ip_addr=$(curl -s https://api4.my-ip.io/ip.txt)

# Get our last IP address from $HOME/.last-ip file
# and save it to a variable named lastIpAddr
last_ip_addr=$(cat "$HOME/.last-ip")

# Compare our current IP with our last IP
# if it is the same then end the program
# else we will continue the program
if [ "$ip_addr" == "$last_ip_addr" ]; then
echo "IP Address still not changed"
exit 0
fi

# Echo a message if the IP address is different
echo "IP Address is different!"

Some logic has been implemented! Yay! We are one step closer to our goals!

What we need to do next is update our record on Cloudflare. To do this we need an API token from Cloudflare to access our DNS record from their API.

First, let’s create our API Token! To do this, you can access the “My Profile” menu on the upper right,

Then you can go to the “API Tokens” menu on the left sidebar. You’ll be shown any API Tokens that you’ve created and used until now. Let’s skip the detail and click on the “Create Token” button

You’ll be shown many templates that you can use to create your new API Token, right now let’s choose the “Edit Zone DNS” template

By default, it’ll include permission to access the DNS endpoint on the Zone scope and that is all we need to complete this (long) project! Let’s go to the bottom of the page and click the “Continue to Summary” button!

Don’t forget to select your preferred Zone, because it’ll become an error when we haven’t selected it!

The summary page will show us something like the image below. It contains information about what action our API Token can do in the future. Let’s just click the “Create Token” button and finish this process!

Finally, you’ll get your API token and a script to check that it’s working normally

Don’t worry, the Token has been revoked by the time this story is published :D

When you run the script on your terminal, it should return something like this,

curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-H "Authorization: Bearer <token>" \
-H "Content-Type:application/json"
# Output: {"result":{"id":"<id>","status":"active"},"success":true,"errors":[],"messages":[{"code":10000,"message":"This API Token is valid and active","type":null}]}

Creating API Token accomplished! Yay! One more step closer to our goals! ╥﹏╥

After you got your API Token, let’s continue where we left off!

First, we want to know what is our Zone ID, because we will need it when we proceed to update our DNS record. You guys can run this command in your command line to get it

# Get the list of available zones in our account
curl -s --request GET \
--url 'https://api.cloudflare.com/client/v4/zones?name=<zone_name>' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <token>' | jq
# Output:
# {
# "result": [
# {
# "id": "<zone_id>",
# "name": "<zone_name>",
# ...
# }
# }
# ],
# "result_info": {...},
# "success": true,
# "errors": [],
# "messages": []
# }

Okay, we got our zone ID, let’s find our record ID and wrap things up!

Let’s once again call Cloudflare API to get our record’s ID, after that we can just finish the bash script and we’re done! We can get our record’s ID by calling this API,

# Get the list of records from a zone in our account
curl -s --request GET \
--url 'https://api.cloudflare.com/client/v4/zones/<zone_id>/dns_records?name=<record_name>' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <token>' | jq
# Output:
# {
# "result": [
# {
# "id": "<record_id>",
# "zone_id": "<zone_id>",
# "zone_name": "<zone_name>",
# "name": "<record_name>",
# "type": "A",
# "content": "<ip_v4>", # this field will be filled with the previous IP that we've set
# "proxiable": true,
# "proxied": true,
# "ttl": 1, # 1 is mean that we have an auto TTL
# ...
# }
# ],
# "success": true,
# "errors": [],
# "messages": [],
# "result_info": {...}
# }

Finally! We got all components! Let’s finish our bash script together! What we need on our bash script is to update our existing record’s content with our new public IP address when our public IP is changed. To do that we need to call Cloudflare API which helps us update our record’s content.

To do that we can just run this script,

#!/usr/bin/bash

# Define our token
token="<token>"

# Define our new IP Address
ip_addr=$(curl -s https://api4.my-ip.io/ip.txt)

# Define a variable to store our zone id and record id
zone_id="<zone_id>" # replace this with the one from the API
record_id="<record_id"> # replace this with the one from the API

record_name="<record_name>" # your record name, e.g. afikrim.net

now=$(date +%s) # keep track of when you're updating

# Call Cloudflare API to update our DNS record
# leave proxied and ttl the same as what you got
# from the Cloudflare API when searching your record id
curl --request PUT \
--url "https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records/${record_id}" \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer ${token}" \
--data "{
\"content\": \"${ip_addr}\",
\"name\": \"${record_name}\",
\"proxied\": true,
\"type\": \"A\",
\"comment\": \"Automatically updated at: ${now}\",
\"tags\": [],
\"ttl\": 1
}"

Save it to a file named update-dns.sh and execute it from your terminal by running this command,

bash ./update-dns.sh | jq
# Output:
# {
# "result": {
# "id": "<record_id>",
# "zone_id": "<zone_id>",
# "zone_name": "<zone_name>",
# "name": "<record_name>",
# "type": "A",
# "content": "<current_ip>",
# "proxiable": true,
# "proxied": true,
# "ttl": 1,
# "locked": false,
# "meta": {...},
# "comment": "Automatically updated at: 1686849881",
# ...
# },
# "success": true,
# "errors": [],
# "messages": []
# }

It works!!! Let’s combine the entire script together!

#!/usr/bin/bash

# Get our current IP address from https://api4.my-ip.io/ip.txt
# and save it into a variable named ipAddr
ip_addr=$(curl -s https://api4.my-ip.io/ip.txt)

# Get our last IP address from $HOME/.last-ip file
# and save it to a variable named lastIpAddr
last_ip_addr=$(cat "$HOME/.last-ip")

# Compare our current IP with our last IP
# if it is the same then end the program
# else we will continue the program
if [ "$ip_addr" == "$last_ip_addr" ]; then
echo "IP Address still not changed"
exit 0
fi

# Echo a message if the IP address is different
echo "IP Address is different!"

# White our current IP address as on .last-ip file
echo "${ip_addr}" | tee "$HOME/.last-ip"

# Define our token
token="<token>"

# Define a variable to store our zone id and record id
zone_id="<zone_id>" # replace this with the one from the API
record_id="<record_id>" # replace this with the one from the API

record_name="<record_name>" # your record name, e.g. afikrim.net

now=$(date +%s) # keep track on when you're updating

# Call Cloudflare API to update our DNS record
# leave proxied and ttl the same as what you got
# from the Cloudflare API when searching your record id
curl --request PUT \
--url "https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records/${record_id}" \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer ${token}" \
--data "{
\"content\": \"${ip_addr}\",
\"name\": \"${record_name}\",
\"proxied\": true,
\"type\": \"A\",
\"comment\": \"Automatically updated at: ${now}\",
\"tags\": [],
\"ttl\": 1
}"

Save it again at update-dns.sh and run it like below example,

bash update-dns.sh
cat: /path/to/file/.last-ip: No such file or directory
IP Address is different!
xxx.xxx.xxx.xxx
{"result":{"id":"<record_id>","zone_id":"<zone_id>","zone_name":"<zone_name>","name":"<record_name>","type":"A","content":"<ip_v4>","proxiable":true,"proxied":true,"ttl":1,"locked":false,"meta":{...},"comment":"Automatically updated at: 1686850536",...},"success":true,"errors":[],"messages":[]}

When you run it for the first time you should get something like this…

Finally… It’s done…

Wait up! We haven’t know how to automatically update our record…

Okay, it is simple, you just need to add a cronjob to run the bash script, just run this command,

# Edit crontab on my ubuntu machine
crontab -e

After that, you can just put your script at the end of the file, like this,

# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').
#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h dom mon dow command

* * * * * bash /path/to/file/exec.sh

After you save it make sure to check the crontab status by running systemctl status crontab. If it shows something like what is written below, then it should be fine!

Jun 16 00:42:01 $HOSTNAME cron[649]: ($USER) RELOAD (crontabs/$USER)

Finally, we met the real end here… By completing the entire script and putting it on the cronjob we can mark this series as done! It’s a bit sad that I should part with you guys, but don’t worry, I’ll come back after I found other interesting things!

Thanks for staying till the end! I really appreciate it! Make sure to follow me if you want to keep up to date with what I’ll be posting from now on!

--

--

Aziz Fikri Mahmudi

Passionate Backend Engineer | Backend Engineer at Amartha