Featured image of post 3 ways to set up DynDNS with CloudFlare

3 ways to set up DynDNS with CloudFlare

Ever thought about running your own dynamic DNS system without relying on third-party providers? Well, me too, not going to lie.

Shout-out to all the selfhosters around there!

Ever thought about running your own dynamic DNS system without relying on third-party providers? Well, me too, not going to lie.

Luckily, if you own a domain and have a CloudFlare account, there’s a few things we can do about that. Today, we are bringing a bunch of different approaches to face this scenario, so let’s get down to it.

General assumptions

  • Have your own domain
  • Set up your domain’s DNS with CloudFlare
  • Have a CloudFlare account with DNS management capabilities
  • This task works with IPv4 only (for now)

Classic Linux cronjob with a simple cURL

Specific assumptions

  • Have access to a Linux host (no root permissions needed)
  • Have the jq package installed

Being this one the simplest way to achieve our goal, just save the following script to a file:

DNS_ZONE='<your DNS zone ID here>'
DNS_RECORD='<your DNS record ID here>'
AUTH_KEY='<your CloudFlare API token here>'
EMAIL_ADDRESS='<your CloudFlare's email account here>'
DNS_RECORD_NAME="\"<your.domain.tld here>\""
CURRENT_IP_ADDRESS="\"$(curl -s ip.me)\""
CURRENT_DNS_VALUE=$(curl -sX GET "https://api.cloudflare.com/client/v4/zones/${DNS_ZONE}/dns_records/${DNS_RECORD}" -H "Content-Type:application/json" -H "X-Auth-Key:${AUTH_KEY}" -H "X-Auth-Email:${EMAIL_ADDRESS}" | jq '.result["content"]')

if [ ${CURRENT_DNS_VALUE} != ${CURRENT_IP_ADDRESS} ]; then
	curl -sX PUT "https://api.cloudflare.com/client/v4/zones/${DNS_ZONE}/dns_records/${DNS_RECORD}" -H "X-Auth-Email:${EMAIL_ADDRESS}" -H "X-Auth-Key:${AUTH_KEY}" -H "Content-Type:application/json" --data '{"type":"A","name":'${DNS_RECORD_NAME}',"content":'${CURRENT_IP_ADDRESS}'}' > /dev/null
fi

Let’s say we name this file as cf-ddns.bash and save it to /opt. Then make it executable:

chmod +x /opt/cf-ddns.bash

And now let’s create a cron job that will run it every hour. Open the crontab editor with:

crontab -e

Then simply add the following line:

@hourly /bin/bash /opt/cf-ddns.bash

It is worth mentioning that the Linux machine where you are setting up this task must be behind the same NAT or on the same local network from where you want your DNS record to point to its public IP address.

Kubernetes cronjob

Specific assumptions:

  • Run a Kubernetes cluster (pretty obvious right?)

Why not!?

If you are running a self-hosted Kubernetes cluster you can just go all the way down and make use of the native cronjob feature it provides.

In this case you would only have to apply the following manifest, which creates a pod that will run the previous script we defined but it will be managed at a cluster level. (don’t forget to update the variable values according to yours!) :

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: cf-dyndns
  namespace: default
spec:
  schedule: "@hourly"
  successfulJobsHistoryLimit: 0
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: cf-dyndns
            image: bash:latest
            imagePullPolicy: IfNotPresent
            restartPolicy: OnFailure
            command:
            - /usr/local/bin/bash
            - -c
            - apk add --update curl jq &>/dev/null ; DNS_ZONE='<your DNS zone ID here>' DNS_RECORD='<your DNS record ID here>' AUTH_KEY='<your CloudFlare API token here>' EMAIL_ADDRESS='<your CloudFlare account's email here>' DNS_RECORD_NAME="\"<your.domain.tld here>\"" CURRENT_IP_ADDRESS="\"$(curl -s ip.me)\"" CURRENT_DNS_VALUE=$(curl -sX GET "https://api.cloudflare.com/client/v4/zones/${DNS_ZONE}/dns_records/${DNS_RECORD}" -H "Content-Type:application/json" -H "X-Auth-Key:${AUTH_KEY}" -H "X-Auth-Email:${EMAIL_ADDRESS}" | jq '.result["content"]'); if [ ${CURRENT_DNS_VALUE} != ${CURRENT_IP_ADDRESS} ] ; then curl -sX PUT "https://api.cloudflare.com/client/v4/zones/${DNS_ZONE}/dns_records/${DNS_RECORD}" -H "X-Auth-Email:${EMAIL_ADDRESS}" -H "X-Auth-Key:${AUTH_KEY}" -H "Content-Type:application/json" --data '{"type":"A","name":'${DNS_RECORD_NAME}',"content":'${CURRENT_IP_ADDRESS}'}' > /dev/null; fi && echo OK || echo ERROR

You can always check its status by running:

kubectl get cronjob

Drone CI pipeline

Specific assumptions:

  • Run a Drone CI instance
  • A git repository to interact with through Drone

Another way to automate your DynDNS setup could be defining the task into a CI pipeline, and for that purpose this time we will be using Drone CI to do so.

Although Drone provides a specific plugin to interact with CloudFlare’s API with ease, in this case we will be using a basic cURL image so we can get the DynDNS logic working in a pipeline inside our .drone.yml file.

There is a very specific reason for this: as we are dealing with dynamic IP addresses that may change over time, there are some limitations with Drone when it comes to sharing variable between steps in a pipeline.

Thus the workaround we are proposing here to bypass that limitation consists of having the ability to assign to the CURRENT_IP_ADDRESS variable a value at the very time the same pipeline’s step is running, not before.

In other words, not inheriting the contents from a pipeline-level variable but to gather its value from inside the same container that will also handle its content to update our DNS record itself on the next command it runs:

---
kind: pipeline
type: kubernetes # or "docker" if you don't have a kubernetes runner configured!
name: cf-ddns

steps:
- name: Update DNS record
  image: curlimages/curl
  environment:
    AUTH_KEY:
      from_secret: cloudflare_token
    EMAIL_ADDRESS:
      from_secret: cloudflare_email
    DNS_ZONE_ID:
      from_secret: cloudflare_zone_id
    DNS_RECORD_ID:
      from_secret: cloudflare_record_id
    DNS_RECORD_NAME: <your.domain.tld here>
  commands:
    - export CURRENT_IP_ADDRESS="$(curl -s ip.me)"
    - curl -sX PUT "https://api.cloudflare.com/client/v4/zones/$DNS_ZONE_ID/dns_records/$DNS_RECORD_ID" -H "X-Auth-Email:$EMAIL_ADDRESS" -H "X-Auth-Key:$AUTH_KEY" -H "Content-Type:application/json" --data "{\"type\":\"A\",\"name\":\"$DNS_RECORD_NAME\",\"content\":\"$CURRENT_IP_ADDRESS\"}

trigger:
  event:
  - cron

As you can see we are here using secrets to provide the multiple pipeline-level variable contents too, so don’t forget to create those secrets manually inside the repository options through the Drone web interface.

Once that’s done, the last thing we need is to create the cron job that will trigger the pipeline. The cron job options are easily found in the web UI as well, but if you’d rather using the cli you can always run something like this:

drone cron add "myuser/myrepo" "cf-ddns" '@hourly'

Worth to point out, this last method gets rid of the original principle: “if the IP address has changed, then overwrite the DNS record with the current IP address” that was present on the first two examples, and just runs the HTTP request to overwrite the DNS record with whatever IP address it gathers each time, no matter if it is a new one or not.

That’s all for now. Wish it was useful!

Cheers.

Built with Hugo