What's new

Cloudflare ddns-start script

  • SNBForums Code of Conduct

    SNBForums is a community for everyone, no matter what their level of experience.

    Please be tolerant and patient of others, especially newcomers. We are all here to share and learn!

    The rules are simple: Be patient, be nice, be helpful or be gone!

Eodyne

New Around Here
So I've checked every Cloudflare ddns thread here and have tried multiple different scripts and nothing seems to be working. I keep getting Request Errors and failure to update my cloudflare ddns record.

Is there any simple walkthrough or script that is confirmed to work? I've looked into DNSoMatic but it has apparently been folded into OpenDNS and no longer provides the services it once did. You can't sign-up for it anymore (should probably be removed from the interface)
 
Why Cloudflare if it works in OpenDNS why not use it. I have no need for Cloudflare and I won't use it. I use QUAD9.
 
Because CloudFlare is my domain provider, I want to run everything as internally as possible, and I use an in network DNS server via a pi-hole and unbound.

I'm not asking how to use Cloudflare's public DNS servers (1.1.1.1 etc.) but for a working custom ddns-start script.
 
I wrote my own for a number of reasons after getting errors while trying to use others' scripts.

A few features:
Doesn't require any additional software (i.e. entware, jq, python, etc) to be installed beyond what is installed on recent versions of asus-merlin by default.
Autodetects the zoneid and recordid.
Can update ipv4, ipv6, or both, with just a single easy to understand setting change.
Doesn't force an update if one is not required.
I can simply copy and paste this script in full to update multiple domains, and the only setting I have to change to do it is to specify the domain/subdomain I'm wanting to update (since it uses that to autodetect the zone & recordids).

It's massively overengineered with the goal of eliminating dependencies and being easily configurable.



Bash:
#!/bin/sh

# Cloudflare API credentials and domain details
CLOUDFLARE_EMAIL="cloudflare-email@gmail.com"
CLOUDFLARE_API_KEY="cloudflare-api-key" 
FULL_DOMAIN_NAME="yourdomain.com"  # Set to "www.yourdomain.com" for subdomain (e.g., www.yourdomain.com) or "yourdomain.com" for root domain
UPDATE_TYPE="both"  # Options: "ipv4", "ipv6", "both"
LOG_FILE="/tmp/cloudflare_ddns.log"
IP_FILE_IPV4="/tmp/cloudflare_current_ip"
IP_FILE_IPV6="/tmp/cloudflare_current_ip6"

echo "Starting Cloudflare DDNS update script for ${FULL_DOMAIN_NAME}..."

# Extract DOMAIN_NAME from FULL_DOMAIN_NAME
DOMAIN_NAME=$(echo $FULL_DOMAIN_NAME | awk -F. '{print $(NF-1)"."$NF}')

# Fetch Cloudflare Zone ID
echo "Fetching Cloudflare Zone ID..."
ZONE_RESPONSE=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${DOMAIN_NAME}" \
    -H "X-Auth-Email: ${CLOUDFLARE_EMAIL}" \
    -H "X-Auth-Key: ${CLOUDFLARE_API_KEY}" \
    -H "Content-Type: application/json")

CLOUDFLARE_ZONE_ID=$(echo $ZONE_RESPONSE | grep -o '"id":"[^"]*' | head -n 1 | grep -o '[^"]*$')

if [ -z "$CLOUDFLARE_ZONE_ID" ]; then
  echo "Failed to fetch Cloudflare Zone ID."
  exit 1
fi
echo "Cloudflare Zone ID is ${CLOUDFLARE_ZONE_ID}"

# Get the current public IPv4 address if needed
if [ "$UPDATE_TYPE" = "ipv4" ] || [ "$UPDATE_TYPE" = "both" ]; then
  echo "Fetching current public IPv4 address..."
  CURRENT_IP=$(curl -s http://checkip.amazonaws.com)
  if [ -z "$CURRENT_IP" ]; then
    echo "Failed to fetch current public IPv4 address."
    exit 1
  fi
  echo "Current public IPv4 address is ${CURRENT_IP}"
fi

# Get the current public IPv6 address if needed
if [ "$UPDATE_TYPE" = "ipv6" ] || [ "$UPDATE_TYPE" = "both" ]; then
  echo "Fetching current public IPv6 address..."
  CURRENT_IP6=$(curl -s http://ipv6.icanhazip.com)
  if [ -z "$CURRENT_IP6" ]; then
    echo "Failed to fetch current public IPv6 address."
    exit 1
  fi
  echo "Current public IPv6 address is ${CURRENT_IP6}"
fi

# Get the Cloudflare Record ID for IPv4 if needed
if [ "$UPDATE_TYPE" = "ipv4" ] || [ "$UPDATE_TYPE" = "both" ]; then
  echo "Fetching Cloudflare Record ID for IPv4..."
  RECORD_RESPONSE_IPV4=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/dns_records?name=${FULL_DOMAIN_NAME}&type=A" \
       -H "X-Auth-Email: ${CLOUDFLARE_EMAIL}" \
       -H "X-Auth-Key: ${CLOUDFLARE_API_KEY}" \
       -H "Content-Type: application/json")

  CLOUDFLARE_RECORD_ID_IPV4=$(echo $RECORD_RESPONSE_IPV4 | grep -o '"id":"[^"]*' | head -n 1 | grep -o '[^"]*$')

  if [ -z "$CLOUDFLARE_RECORD_ID_IPV4" ]; then
    echo "Failed to fetch Cloudflare Record ID for IPv4."
    echo "Response was: $RECORD_RESPONSE_IPV4"
    exit 1
  fi
  echo "Cloudflare Record ID for IPv4 is ${CLOUDFLARE_RECORD_ID_IPV4}"
fi

# Get the Cloudflare Record ID for IPv6 if needed
if [ "$UPDATE_TYPE" = "ipv6" ] || [ "$UPDATE_TYPE" = "both" ]; then
  echo "Fetching Cloudflare Record ID for IPv6..."
  RECORD_RESPONSE_IPV6=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/dns_records?name=${FULL_DOMAIN_NAME}&type=AAAA" \
       -H "X-Auth-Email: ${CLOUDFLARE_EMAIL}" \
       -H "X-Auth-Key: ${CLOUDFLARE_API_KEY}" \
       -H "Content-Type: application/json")

  CLOUDFLARE_RECORD_ID_IPV6=$(echo $RECORD_RESPONSE_IPV6 | grep -o '"id":"[^"]*' | head -n 1 | grep -o '[^"]*$')

  if [ -z "$CLOUDFLARE_RECORD_ID_IPV6" ]; then
    echo "Failed to fetch Cloudflare Record ID for IPv6."
    echo "Response was: $RECORD_RESPONSE_IPV6"
    exit 1
  fi
  echo "Cloudflare Record ID for IPv6 is ${CLOUDFLARE_RECORD_ID_IPV6}"
fi

# Compare with the previous IPv4 address if needed
if [ "$UPDATE_TYPE" = "ipv4" ] || [ "$UPDATE_TYPE" = "both" ]; then
  if [ -f "$IP_FILE_IPV4" ]; then
    LAST_IP=$(cat "$IP_FILE_IPV4")
  else
    LAST_IP=""
  fi

  if [ "$CURRENT_IP" != "$LAST_IP" ]; then
    echo "IPv4 address has changed. Updating Cloudflare DNS record..."
    RESPONSE=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/dns_records/${CLOUDFLARE_RECORD_ID_IPV4}" \
         -H "X-Auth-Email: ${CLOUDFLARE_EMAIL}" \
         -H "X-Auth-Key: ${CLOUDFLARE_API_KEY}" \
         -H "Content-Type: application/json" \
         --data '{"type":"A","name":"'${FULL_DOMAIN_NAME}'","content":"'${CURRENT_IP}'","ttl":120,"proxied":false}')

    if echo "$RESPONSE" | grep -q '"success":true'; then
      echo "IPv4 DNS record updated successfully."
      echo "$CURRENT_IP" > "$IP_FILE_IPV4"
      UPDATE_REQUIRED=true
    else
      echo "Failed to update IPv4 DNS record. Response: $RESPONSE"
    fi

    echo "$(date) - IPv4: $RESPONSE" >> "$LOG_FILE"
  else
    echo "IPv4 address has not changed. No update needed."
  fi
fi

# Compare with the previous IPv6 address if needed
if [ "$UPDATE_TYPE" = "ipv6" ] || [ "$UPDATE_TYPE" = "both" ]; then
  if [ -f "$IP_FILE_IPV6" ]; then
    LAST_IP6=$(cat "$IP_FILE_IPV6")
  else
    LAST_IP6=""
  fi

  if [ "$CURRENT_IP6" != "$LAST_IP6" ]; then
    echo "IPv6 address has changed. Updating Cloudflare DNS record..."
    RESPONSE=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/dns_records/${CLOUDFLARE_RECORD_ID_IPV6}" \
         -H "X-Auth-Email: ${CLOUDFLARE_EMAIL}" \
         -H "X-Auth-Key: ${CLOUDFLARE_API_KEY}" \
         -H "Content-Type: application/json" \
         --data '{"type":"AAAA","name":"'${FULL_DOMAIN_NAME}'","content":"'${CURRENT_IP6}'","ttl":120,"proxied":false}')

    if echo "$RESPONSE" | grep -q '"success":true'; then
      echo "IPv6 DNS record updated successfully."
      echo "$CURRENT_IP6" > "$IP_FILE_IPV6"
      UPDATE_REQUIRED=true
    else
      echo "Failed to update IPv6 DNS record. Response: $RESPONSE"
    fi

    echo "$(date) - IPv6: $RESPONSE" >> "$LOG_FILE"
  else
    echo "IPv6 address has not changed. No update needed."
  fi
fi

if [ "$UPDATE_REQUIRED" = false ]; then
  echo "No updates were required for either IPv4 or IPv6."
fi

echo "Cloudflare DDNS update script completed."

exit 0
 
Thanks so much for this!

I use scoped Cloudflare API tokens instead of a global API key, and made some small modifications to your script to account for that: (confirmed working)

Bash:
#!/bin/sh

# Cloudflare API credentials and domain details
CLOUDFLARE_TOKEN="api-token"
FULL_DOMAIN_NAME="yourdomain.com"  # Set to "www.yourdomain.com" for subdomain (e.g., www.yourdomain.com) or "yourdomain.com" for root domain
UPDATE_TYPE="both"  # Options: "ipv4", "ipv6", "both"
LOG_FILE="/tmp/cloudflare_ddns.log"
IP_FILE_IPV4="/tmp/cloudflare_current_ip"
IP_FILE_IPV6="/tmp/cloudflare_current_ip6"

echo "Starting Cloudflare DDNS update script for ${FULL_DOMAIN_NAME}..."

# Extract DOMAIN_NAME from FULL_DOMAIN_NAME
DOMAIN_NAME=$(echo $FULL_DOMAIN_NAME | awk -F. '{print $(NF-1)"."$NF}')

# Fetch Cloudflare Zone ID
echo "Fetching Cloudflare Zone ID..."
ZONE_RESPONSE=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${DOMAIN_NAME}" \
    -H "Authorization: Bearer ${CLOUDFLARE_TOKEN}" \
    -H "Content-Type: application/json")

CLOUDFLARE_ZONE_ID=$(echo $ZONE_RESPONSE | grep -o '"id":"[^"]*' | head -n 1 | grep -o '[^"]*$')

if [ -z "$CLOUDFLARE_ZONE_ID" ]; then
  echo "Failed to fetch Cloudflare Zone ID."
  exit 1
fi
echo "Cloudflare Zone ID is ${CLOUDFLARE_ZONE_ID}"

# Get the current public IPv4 address if needed
if [ "$UPDATE_TYPE" = "ipv4" ] || [ "$UPDATE_TYPE" = "both" ]; then
  echo "Fetching current public IPv4 address..."
  CURRENT_IP=$(curl -s http://checkip.amazonaws.com)
  if [ -z "$CURRENT_IP" ]; then
    echo "Failed to fetch current public IPv4 address."
    exit 1
  fi
  echo "Current public IPv4 address is ${CURRENT_IP}"
fi

# Get the current public IPv6 address if needed
if [ "$UPDATE_TYPE" = "ipv6" ] || [ "$UPDATE_TYPE" = "both" ]; then
  echo "Fetching current public IPv6 address..."
  CURRENT_IP6=$(curl -s http://ipv6.icanhazip.com)
  if [ -z "$CURRENT_IP6" ]; then
    echo "Failed to fetch current public IPv6 address."
    exit 1
  fi
  echo "Current public IPv6 address is ${CURRENT_IP6}"
fi

# Get the Cloudflare Record ID for IPv4 if needed
if [ "$UPDATE_TYPE" = "ipv4" ] || [ "$UPDATE_TYPE" = "both" ]; then
  echo "Fetching Cloudflare Record ID for IPv4..."
  RECORD_RESPONSE_IPV4=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/dns_records?name=${FULL_DOMAIN_NAME}&type=A" \
       -H "Authorization: Bearer ${CLOUDFLARE_TOKEN}" \
       -H "Content-Type: application/json")

  CLOUDFLARE_RECORD_ID_IPV4=$(echo $RECORD_RESPONSE_IPV4 | grep -o '"id":"[^"]*' | head -n 1 | grep -o '[^"]*$')

  if [ -z "$CLOUDFLARE_RECORD_ID_IPV4" ]; then
    echo "Failed to fetch Cloudflare Record ID for IPv4."
    echo "Response was: $RECORD_RESPONSE_IPV4"
    exit 1
  fi
  echo "Cloudflare Record ID for IPv4 is ${CLOUDFLARE_RECORD_ID_IPV4}"
fi

# Get the Cloudflare Record ID for IPv6 if needed
if [ "$UPDATE_TYPE" = "ipv6" ] || [ "$UPDATE_TYPE" = "both" ]; then
  echo "Fetching Cloudflare Record ID for IPv6..."
  RECORD_RESPONSE_IPV6=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/dns_records?name=${FULL_DOMAIN_NAME}&type=AAAA" \
       -H "Authorization: Bearer ${CLOUDFLARE_TOKEN}" \
       -H "Content-Type: application/json")

  CLOUDFLARE_RECORD_ID_IPV6=$(echo $RECORD_RESPONSE_IPV6 | grep -o '"id":"[^"]*' | head -n 1 | grep -o '[^"]*$')

  if [ -z "$CLOUDFLARE_RECORD_ID_IPV6" ]; then
    echo "Failed to fetch Cloudflare Record ID for IPv6."
    echo "Response was: $RECORD_RESPONSE_IPV6"
    exit 1
  fi
  echo "Cloudflare Record ID for IPv6 is ${CLOUDFLARE_RECORD_ID_IPV6}"
fi

# Compare with the previous IPv4 address if needed
if [ "$UPDATE_TYPE" = "ipv4" ] || [ "$UPDATE_TYPE" = "both" ]; then
  if [ -f "$IP_FILE_IPV4" ]; then
    LAST_IP=$(cat "$IP_FILE_IPV4")
  else
    LAST_IP=""
  fi

  if [ "$CURRENT_IP" != "$LAST_IP" ]; then
    echo "IPv4 address has changed. Updating Cloudflare DNS record..."
    RESPONSE=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/dns_records/${CLOUDFLARE_RECORD_ID_IPV4}" \
         -H "Authorization: Bearer ${CLOUDFLARE_TOKEN}" \
         -H "Content-Type: application/json" \
         --data '{"type":"A","name":"'${FULL_DOMAIN_NAME}'","content":"'${CURRENT_IP}'","ttl":120,"proxied":false}')

    if echo "$RESPONSE" | grep -q '"success":true'; then
      echo "IPv4 DNS record updated successfully."
      echo "$CURRENT_IP" > "$IP_FILE_IPV4"
      UPDATE_REQUIRED=true
    else
      echo "Failed to update IPv4 DNS record. Response: $RESPONSE"
    fi

    echo "$(date) - IPv4: $RESPONSE" >> "$LOG_FILE"
  else
    echo "IPv4 address has not changed. No update needed."
  fi
fi

# Compare with the previous IPv6 address if needed
if [ "$UPDATE_TYPE" = "ipv6" ] || [ "$UPDATE_TYPE" = "both" ]; then
  if [ -f "$IP_FILE_IPV6" ]; then
    LAST_IP6=$(cat "$IP_FILE_IPV6")
  else
    LAST_IP6=""
  fi

  if [ "$CURRENT_IP6" != "$LAST_IP6" ]; then
    echo "IPv6 address has changed. Updating Cloudflare DNS record..."
    RESPONSE=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/dns_records/${CLOUDFLARE_RECORD_ID_IPV6}" \
         -H "Authorization: Bearer ${CLOUDFLARE_TOKEN}" \
         -H "Content-Type: application/json" \
         --data '{"type":"AAAA","name":"'${FULL_DOMAIN_NAME}'","content":"'${CURRENT_IP6}'","ttl":120,"proxied":false}')
    if echo "$RESPONSE" | grep -q '"success":true'; then
      echo "IPv6 DNS record updated successfully."
      echo "$CURRENT_IP6" > "$IP_FILE_IPV6"
      UPDATE_REQUIRED=true
    else
      echo "Failed to update IPv6 DNS record. Response: $RESPONSE"
    fi

    echo "$(date) - IPv6: $RESPONSE" >> "$LOG_FILE"
  else
    echo "IPv6 address has not changed. No update needed."
  fi
fi

if [ "$UPDATE_REQUIRED" = false ]; then
  echo "No updates were required for either IPv4 or IPv6."
fi

echo "Cloudflare DDNS update script completed."

exit 0
 
Also note that if the script reports a failure getting the record ID for IPv6, it might be because you haven't set an AAAA record in the first place—I fixed it by manually adding one for the first time to my Cloudflare zone, and after that the script worked fine. (Presumably, the same would be the case if you hadn't set an A record on your domain either.)
 

Similar threads

Latest threads

Support SNBForums w/ Amazon

If you'd like to support SNBForums, just use this link and buy anything on Amazon. Thanks!

Sign Up For SNBForums Daily Digest

Get an update of what's new every day delivered to your mailbox. Sign up here!
Top