动态IP环境下要保持域名始终指向正确的服务器地址,Cloudflare提供的API让这一切变得简单可控。相比于依赖路由器自带的DDNS客户端,自己编写更新脚本可以完全掌控刷新逻辑、错误处理和日志记录,同时还能利用Cloudflare的CDN网络隐藏真实IP。整个方案的核心思路是:定时检测本机公网IP,发现变化后调用Cloudflare API更新对应的DNS记录。

一、准备工作:获取API Token与必要参数
要让脚本能够操作你的DNS记录,首先需要在Cloudflare控制台创建一个API Token。登录Cloudflare仪表板后,点击右上角头像图标,进入My Profile页面,找到API Tokens选项卡。点击Create Token按钮,选择Create Custom Token,这里不需要使用Global API Key,因为Global API Key拥有账户的全部权限,一旦泄露风险极大。
在权限设置区域,选择Zone类型的DNS资源,授予Edit编辑权限。Zone Resources区域选择Include Specific zone,然后从下拉菜单中挑选你要管理的域名。这样创建出来的Token只能操作指定域名的DNS记录,即使脚本文件被泄露,攻击者也无法触及其他资源。创建完成后系统会显示一长串Token字符串,这个字符串只显示一次,务必立即保存到安全位置。
除了API Token之外,还需要获取Zone ID和Record ID。Zone ID可以在域名概述页面的右下角API区域找到,直接复制即可。Record ID需要通过API查询获取,可以使用curl命令调用List DNS Records接口,在返回的JSON数据中找到目标A记录的id字段值。下面是查询记录的命令示例:
curl -s -X GET "https://api.cloudflare.com/client/v4/zones/你的ZoneID/dns_records?name=ddns.ipipp.com&type=A" \ -H "Authorization: Bearer 你的APIToken" \ -H "Content-Type: application/json"
返回的JSON中result数组里每条记录都包含一个id字段,把这个值记录下来,后续更新脚本会用到。如果你有多个子域名需要动态解析,每个子域名都对应一个独立的Record ID,需要分别查询保存。建议将这些参数统一放在一个配置文件中管理,方便后续维护和多域名扩展。
二、编写核心脚本:检测IP变化并调用API更新
脚本的核心逻辑分为三步:获取当前公网IP、读取上次记录的IP、比较两者是否一致。如果IP发生变化,就调用Cloudflare API的PATCH接口更新A记录。获取公网IP有多种方式,最常用的是通过外部服务查询,比如访问ipinfo.io或ifconfig.me。这种方式依赖第三方服务可用性,如果这些服务宕机脚本就会失效。更稳妥的做法是同时查询多个源,取第一个有效结果。
下面是一个完整的Shell脚本示例,包含了IP获取、变化检测、API调用和日志记录功能:
#!/bin/bash
# Cloudflare DDNS 自动更新脚本
# ===== 配置区域 =====
API_TOKEN="你的APIToken"
ZONE_ID="你的ZoneID"
RECORD_ID="你的RecordID"
RECORD_NAME="ddns.ipipp.com"
TTL=120
PROXIED=false
# IP缓存文件路径
IP_CACHE="/var/log/cloudflare_ddns/ip_cache.txt"
# 日志文件路径
LOG_FILE="/var/log/cloudflare_ddns/ddns.log"
# 获取当前公网IP
get_current_ip() {
local ip=""
# 依次尝试多个IP查询服务
for url in "https://ipinfo.io/ip" "https://ifconfig.me" "https://api.ipify.org"; do
ip=$(curl -s --connect-timeout 5 "$url" 2>/dev/null | grep -oE '^[0-9]{1,3}(\.[0-9]{1,3}){3}$')
if [ -n "$ip" ]; then
echo "$ip"
return 0
fi
done
return 1
}
# 获取缓存的IP
get_cached_ip() {
if [ -f "$IP_CACHE" ]; then
cat "$IP_CACHE"
else
echo ""
fi
}
# 更新Cloudflare DNS记录
update_dns_record() {
local new_ip="$1"
local response
response=$(curl -s -X PATCH \
"https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${RECORD_ID}" \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"${RECORD_NAME}\",\"content\":\"${new_ip}\",\"ttl\":${TTL},\"proxied\":${PROXIED}}")
local success
success=$(echo "$response" | grep -o '"success":true')
if [ -n "$success" ]; then
return 0
else
return 1
fi
}
# 主逻辑
main() {
local current_ip cached_ip
current_ip=$(get_current_ip)
if [ -z "$current_ip" ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 错误:无法获取公网IP" >> "$LOG_FILE"
exit 1
fi
cached_ip=$(get_cached_ip)
if [ "$current_ip" = "$cached_ip" ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] IP未变化:${current_ip}" >> "$LOG_FILE"
exit 0
fi
# IP发生变化,执行更新
if update_dns_record "$current_ip"; then
echo "$current_ip" > "$IP_CACHE"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 更新成功:${cached_ip} -> ${current_ip}" >> "$LOG_FILE"
else
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 更新失败:API返回错误" >> "$LOG_FILE"
exit 1
fi
}
main这个脚本的设计有几个关键点值得注意。首先是IP缓存机制,通过将上次成功更新的IP写入文件,下次运行时只需读取文件比较即可,避免每次都查询DNS记录。其次是多源IP查询策略,当第一个服务不可用时自动切换到备用源,大幅提升了脚本在恶劣网络环境下的可靠性。最后是日志记录,每次运行都会写入时间戳和操作结果,方便后续排查问题。
关于TTL值的设置,如果域名开启了Cloudflare代理(橙色云朵),TTL会自动设为Auto,手动设置的值不生效。只有当代理关闭(灰色云朵)时,TTL设置才有效。建议将TTL设置为120秒,这样IP变化后最多两分钟全球DNS就会刷新,对于动态IP场景来说响应速度足够快。如果对实时性要求更高,可以设为60秒,但要注意Cloudflare对A记录最低TTL的限制。
三、配置定时任务与容错机制
脚本编写完成后,需要通过cron定时任务让它自动运行。在Linux系统上执行crontab -e命令编辑定时任务,添加一行配置即可。建议设置为每五分钟执行一次,频率太高会给IP查询服务造成压力,频率太低又可能导致IP变化后长时间无法访问。下面是cron配置示例:
# 每五分钟执行一次DDNS更新 */5 * * * * /bin/bash /usr/local/bin/cloudflare_ddns.sh >> /var/log/cloudflare_ddns/cron.log 2>&1
除了基本的定时执行,还需要考虑几个容错场景。第一是网络抖动导致IP查询失败,脚本中已经通过多源查询和超时设置处理了这个问题。第二是Cloudflare API临时不可用,可以在update_dns_record函数中增加重试逻辑,连续失败三次才判定为真正失败。第三是并发冲突,如果上一次执行还没结束下一次就启动了,可以通过flock文件锁来防止重复执行。
下面是加入文件锁和重试机制的改进版本关键片段:
# 使用文件锁防止并发执行
LOCK_FILE="/var/lock/cloudflare_ddns.lock"
exec 200>"$LOCK_FILE"
flock -n 200 || {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 警告:另一个实例正在运行,退出" >> "$LOG_FILE"
exit 0
}
# 带重试的DNS更新函数
update_with_retry() {
local new_ip="$1"
local max_retries=3
local retry=0
while [ $retry -lt $max_retries ]; do
if update_dns_record "$new_ip"; then
return 0
fi
retry=$((retry + 1))
sleep 5
done
return 1
}文件锁通过flock命令实现,200是文件描述符编号,-n参数表示获取锁失败时立即返回而不等待。这样当cron触发新一次执行时,如果上一次还没跑完,新进程会直接退出,不会产生冲突。重试机制在API调用失败时等待5秒后重试,最多尝试三次,能够有效应对Cloudflare API偶发的5xx错误。
对于运行在OpenWrt路由器或NAS设备上的场景,这些设备通常使用BusyBox提供的精简版Shell,flock命令可能不可用。此时可以用PID文件锁替代方案:脚本启动时检查PID文件是否存在且对应进程仍在运行,如果存在就退出,否则写入当前PID继续执行。虽然不如flock可靠,但在资源受限的嵌入式设备上是一个实用的折中方案。
最后建议定期检查日志文件大小,避免长期运行后日志撑满磁盘。可以在脚本末尾加一行简单的日志轮转逻辑,当日志超过一定行数时只保留最新的部分。或者配置logrotate工具统一管理,这样更加规范。整个方案搭建完成后,你的动态域名解析服务就能稳定运行,IP变化后几分钟内自动完成更新,无需任何人工干预。
Cloudflare DDNS动态域名解析Shell脚本修改时间:2026-08-25 06:31:13