
1. 为什么用Cloudflare API做动态解析DDNS的另一种解法1.1 你先遇到的是哪种“IP没变域名却废了”如果你折腾过自建网站、NAS远程访问、或者在家里跑过任何需要从公网访问的服务大概率会遇到这个场景昨天域名还好好的今天一刷新打不开了查了一圈发现是家里宽带出口IP变了。运营商给家庭宽带的公网IP大多是动态的可能几天、几小时、甚至每次重拨都会换一次。IP一变域名还指着旧的地址服务自然就断了。这时候最常见的做法是去用现成的DDNS服务比如路由器里自带的某些厂商动态解析或者去某云平台申请一个二级域名。但这类方案有几个让人不太舒服的地方一是二级域名长度感人、不够体面二是很多服务商要收费或者限频三是有时候解析生效慢、TTL又长IP换完小半天才恢复。我自己早期被这种“恢复慢”坑过好多次后来干脆把目光转向了一个更可控的方案——Cloudflare API。标题里这句话“使用Cloudflare API动态解析域名IP”翻译成人话就是当本机公网IP变化时脚本自动调用Cloudflare的API接口把域名对应的DNS记录更新成最新IP。整个过程不需要人工登录后台也不需要路由器支持什么特定DDNS协议只要你能跑一个脚本或者一个容器就能实现真正意义上的“域名永远指向你当前IP”。这个方案适合谁来用简单列几类在家跑NAS、想要稳定远程访问的人手上有自己的域名、希望子域名直连家里宽带的人在内网机器上做端口映射、需要一个固定入口的开发者以及所有不想被DDNS服务商绑定、想掌握解析全流程的控制派用户。核心要求只有一个你的域名DNS托管在Cloudflare上别的都不难。1.2 Cloudflare API在这条链路里扮演什么角色先理清一个概念Cloudflare不只是一个CDN厂商它本质上是一个超级强大的DNS托管平台。只要你把域名的NS记录切到Cloudflare那么域名的DNS解析就完全由它来接管。这时候你想改任何一条解析记录无非两种方式登录网页后台点鼠标或者调用API接口。动态解析的核心矛盾是“IP会变而人不可能天天盯着后台改”。API的价值在于把“查看当前IP、比对解析记录、发起更新”这个流程自动化。Cloudflare开放了一套完整的REST API通过HTTP请求就能操作DNS记录包括查询、新增、修改、删除。这意味着你可以写一个非常小的脚本跑在任何一个能联网的设备上每5分钟检查一次IP发现变了就调用API更新没变就继续睡觉。有个词叫“声明式管理”你不需要告诉Cloudflare“我的IP是多少”你只需要告诉它“我的域名应该解析到当前这个公网IP”剩下的比对和执行逻辑都由你的脚本完成。API在这里就是一个标准化的操作入口它把DNS记录的增删改查变成了几个curl命令或几行Python代码这也是它比在网页后台点鼠标更适合做自动化的根本原因。1.3 与传统DDNS服务对比选它到底图什么我见过很多人在讨论动态解析时第一反应是“路由器里的DDNS不香吗”。香确实香但有几个维度上的差异值得你认真对比一下。我先说结论如果你只是想让一个临时域名能访问到家里路由器自带的DDNS完全够用但如果你对域名可控性、解析速度、子域名数量有要求Cloudflare API这条路几乎没有短板。对比项传统DDNS服务Cloudflare API动态解析域名形式通常是服务商提供的二级域名或免费域名你自己注册的完整域名更新频率取决于服务商策略有的最小间隔还不让改完全自己控制1分钟一次都可以解析生效TTL有时不受控制生效慢TTL可调低配合Cloudflare后端基本秒级生效子域名数量一般有限制理论上无限成本有些收费免费版有限制API本身免费只需域名续费可控性服务商说了算全部在自己手里接口文档公开学习价值几乎为零能搞懂DNS原理、API调用、自动化流程说白了Cloudflare API这条路更像“正规军”打法。你把域名、解析记录、API令牌都牢牢攥在自己手里更新逻辑是自己写的出了问题也知道去哪排查。传统DDNS更像“方便面”饿的时候真管用但你不会想看它的配料表。后面我写的所有内容都是基于自己实际跑了大半年这个方案踩过的坑和经验总结出来的可以直接照着抄作业。2. 动手前的准备域名托管、Zone ID与API Token2.1 把域名的DNS托管切到Cloudflare这一步是整个方案的地基如果你的域名目前不是由Cloudflare做权威DNS服务器那后面的API操作根本无从谈起。切DNS托管本身不复杂核心动作就两个把域名的Nameserver改成Cloudflare给你的那两条等全球同步生效。具体操作流程是在Cloudflare控制台添加站点Add a Site输入你的裸域名比如example.com然后选择免费版Plan就够了动态解析用不到付费项。Cloudflare会扫描你现有的DNS记录扫描到的会自动带过来然后给你分配两条类似xxx.ns.cloudflare.com的Nameserver地址。这时候你登录域名注册商的后台把原来的NS记录替换成这两条保存即可。这里有个比较容易踩的坑在改NS之前最好先把现有DNS记录全部核对一遍。Cloudflare的扫描功能虽然能抓取大部分记录但偶尔会把记录类型搞错尤其是别名类、邮件相关的MX记录。我的做法是先把原解析导出一份备份再对照Cloudflare扫描结果一条一条核对。改完NS之后一般几分钟到几小时不等就能生效Cloudflare控制台会显示站点状态变为Active。这里多等一会不要刚改完就急着重启设备否则可能出现解析空档期。2.2 用API Token而不是Global Key这是我觉得必须单拎出来强调的一点因为真的有人拿Global API Key写进脚本里明文存在服务器上风险非常大。Cloudflare提供了两种API认证方式一种是Global API Key一串固定的密钥权限是整个账号级等于拿了这串东西就能操作你账号下所有域名、所有记录另一种是API Token可以自定义权限范围和作用域比如只给某个域名的DNS编辑权限只允许读取和更新不允许删除。我的建议非常明确一律使用API Token绝对不要把Global Key放进任何脚本或配置文件里。API Token的好处是可以做到“最小权限”即使某台设备上的配置泄露了攻击者能做的也只是帮你改解析记录影响面可控。而且Token可以随时吊销重建不用像Global Key那样出事了还得改账号密码。创建一个API Token的路径是Cloudflare控制台右上角个人头像 → My Profile → API Tokens → Create Token。这里选“Edit zone DNS”模板然后在Zone Resources里限定为你的那个域名相当于指定了操作范围。生成后会得到一串以英文字母和数字组成的Token这串Token只在创建时显示一次一定要当场保存好不然只能重新生成。我个人的习惯是把Token放到环境变量或者专门的配置文件里并且给配置文件设置600权限不写死到脚本源码中。2.3 确认你要更新的记录基本信息在开始调API之前你需要想清楚一个问题你要动态更新的记录叫什么名字、什么类型。最常见的是A记录用于把子域名指向IPv4地址。如果你有IPv6的公网地址那对应的就是AAAA记录。绝大多数家庭宽带场景下A记录就够了。我建议的命名方式是单独开一个子域名来做动态解析比如home.example.com或者ddns.example.com不要直接拿裸域名去动态更新。原因很简单裸域名通常还承载着邮件解析MX、网站CDN等用途你把它变成动态IP后一来影响面大二来如果哪天脚本出bug把记录改错了牵连所有服务。子域名单独管理出问题顶多影响这一个入口。这个子域名的初始记录可以先建一条假的比如127.0.0.1等脚本写好后会自动把它更新成真实IP。这样做的目的是让后端“存在一条记录”方便在第一步用API查询记录ID。后面我会讲到更新记录需要带上Record ID如果记录不存在则需要走创建逻辑。3. 核心API调用细节从查Zone到改Record完整链路3.1 第一步拿到Zone IDCloudflare API的绝大多数操作都要带上Zone ID这是你整个域名在Cloudflare内部的唯一标识。怎么拿两种方式一是直接在Cloudflare控制台的域名概览页面看右下角会出现一串32位十六进制的ID二是调用API查询适应自动化场景。如果你要写全自动脚本建议用API动态获取Zone ID这样就算换了账号或域名脚本也能自适应。请求格式如下curl -X GET https://api.cloudflare.com/client/v4/zones?nameexample.com \ -H Authorization: Bearer YOUR_API_TOKEN \ -H Content-Type: application/json这里需要注意一点请求头里不是X-Auth-Key而是Authorization: Bearer这是新版Token认证的固定写法。返回的JSON里result数组中的id字段就是Zone ID。如果返回了多个zone你可以在URL参数里加上nameexample.com精确过滤避免取错。这里补充一下为什么需要区分Token的认证头Cloudflare同时兼容旧版Global Key使用X-Auth-EmailX-Auth-Key和新版Token使用Authorization: Bearer两个别混用。如果你用的是API Token加老字段会一直报Authenticated user expected之类的错误排查起来也容易绕弯路。3.2 第二步查目标DNS记录ID拿到Zone ID之后下一步是找到你想动态更新的那条DNS记录因为更新接口需要的是RECORD_ID而不是域名名字。这一步很多人第一次会踩坑以为PUT请求里直接放子域名名称就行了实际上Cloudflare要求你提供记录的ID。所以脚本逻辑里必须有一个“先查询、拿ID、再更新”的过程。查询DNS记录的请求是这样的curl -X GET https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records?typeAnamehome.example.com \ -H Authorization: Bearer YOUR_API_TOKEN \ -H Content-Type: application/jsonURL参数里的type和name都可以加也可以不加但为了精准建议都带上。返回结果里result字段是一个数组数组元素包含id、type、name、content、proxied、ttl这些字段。id就是要缓存下来的记录IDcontent是当前解析值脚本里会拿它和最新公网IP做比对。还有一个细节值得注意查询接口默认只返回前20条记录默认分页大小是20最大100如果你的域名下记录特别多需要加上per_page100参数。不过针对单条子域名的精确查询通常不会触发分页问题这个参数算是防御性写法。3.3 第三步更新记录的请求体与参数取舍有了Zone ID和Record ID更新记录就很简单了。Cloudflare的更新接口是PUT请求路径是/zones/{zone_id}/dns_records/{record_id}请求体长这样curl -X PUT https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records/RECORD_ID \ -H Authorization: Bearer YOUR_API_TOKEN \ -H Content-Type: application/json \ --data { type: A, name: home.example.com, content: 203.0.113.10, ttl: 120, proxied: false }四个核心字段的含义要弄清楚。type是记录类型A对应IPv4AAAA对应IPv6name是子域名的完整名称content是新的IP地址也就是你从公网获取到的当前出口IPttl是解析记录的生效时间单位秒120表示两分钟如果想更快可以让它自动但建议不要低于60太激进的TTL在部分递归解析器上反而会被忽略。这里特别说一下proxied字段。很多人第一次看到这个参数会疑惑Cloudflare不是主打CDN吗开启代理不是更好吗如果你跑动态解析只是为了远程访问家里服务建议把它设为false也就是DNS only模式。因为一旦开启ProxiedCloudflare的CDN节点会接管流量它缓存的是“你更新时传入的IP”当IP变化时如果CDN节点还没更新访问反而会被缓存到旧IP造成服务中断。如果你想用Cloudflare隐藏服务器真实IP、做CDN加速当然可以单独开另一条记录不要和动态解析混在一起。请求返回的JSON里有个success字段true代表成功false代表失败失败时errors数组里会带错误码和描述。脚本一定要对success字段做判断而不是只看HTTP状态码。因为很多时候HTTP 200但success是false说明业务逻辑上出错了比如记录不存在、内容格式非法等。3.4 关于Proxy模式与TTL的两个坑上面提到proxied建议设为false这里再展开说说为什么。用Cloudflare API更新记录时如果你不小心把proxied设成了true而你的源站IP是家庭宽带的动态IP会遇到一个很尴尬的情况CDN节点可能会把错误码缓存下来比如源站返回503或超时这个错误响应在节点上会缓存一段时间期间你就算更新了IP用户访问到的还是CDN缓存里的错误页面。这种问题排查起来非常隐蔽从域名解析看IP明明是对的但网站就是打不开最后发现是CDN缓存搞的鬼。TTL这块也有个反直觉的点Cloudflare免费版虽然允许自定义TTL值但最小值不能低于60秒实际上后台可选项最低是60再低就得用Enterprise而且它的TTL并不是“过了这个时间全世界立刻刷新”而是“递归解析器在这个时间之后重新去权威服务器查询”。也就是说你TTL设成120不代表客户端访问一定会在2分钟内拿到新IP中间还有本地DNS缓存、浏览器缓存、系统缓存好几层。所以动态解析的推荐做法是TTL设120或300不要设1小时以上否则IP更新了解析要很久才能恢复。4. 写一个能自动跑的更新脚本Python版4.1 获取公网IP的几种方式与稳定性对比更新DNS记录的前提是知道当前公网IP是多少。获取公网IP的免费接口网上有很多但稳定性和可用性参差不齐。我实测下来推荐两个相对可靠的一是https://api.ipify.org这个服务专注做IP查询响应快支持IPv4和IPv6加参数?formatjson拿到JSON格式缺点是偶尔在国内网络环境下延迟高。二是https://www.cloudflare.com/cdn-cgi/trace这是Cloudflare自家提供的调试接口返回一段纯文本里面ip字段就是你的出口IP。用Cloudflare自己的接口来给Cloudflare API做数据源逻辑上也算自洽而且这个接口被墙的概率极低、稳定性很高。还有一个判断公网IP的小技巧你拿到的IP能不能直接连回来取决于你的宽带是不是真正的公网IP而不是运营商大内网IP。怎么判断最简单的方法是看IP段如果拿到的IP是100.64.x.x到100.127.x.x这个区间大概率被运营商CGNAT了这种场景下动态解析域名也没用因为你根本没有公网入口。我遇到过不少用户折腾半天脚本没问题最后发现是运营商没给公网IPv4。4.2 完整脚本先查再比再更新理解了上面的原理下面直接给出一个我自己在用的Python脚本。脚本的设计思路是“状态机”获取当前IP → 查询现有记录 → 和现有记录比对 → 相同则跳过、不同才更新。这个设计能显著减少无意义的API调用次数避免触发Cloudflare的频率限制。脚本支持A记录和AAAA记录只需要把RECORD_TYPE改成对应值即可。#!/usr/bin/env python3 Cloudflare 动态解析脚本 逻辑获取本机公网IP - 查询Cloudflare现有DNS记录 - 比对 - 有变化则更新 使用前需要设置环境变量 CF_API_TOKEN: 你的Cloudflare API TokenZone.DNS编辑权限 CF_ZONE_NAME: 你的域名例如 example.com CF_RECORD_NAME: 要动态更新的子域名例如 home.example.com CF_RECORD_TYPE: 记录类型A 或 AAAA默认 A import json import os import sys import urllib.request API_BASE https://api.cloudflare.com/client/v4 def get_public_ip(record_typeA): 获取本机公网IP优先用Cloudflare官方trace接口失败则降级到ipify try: req urllib.request.Request(https://www.cloudflare.com/cdn-cgi/trace) with urllib.request.urlopen(req, timeout10) as resp: content resp.read().decode(utf-8) lines content.strip().split(\n) data {line.split()[0]: line.split()[1] for line in lines if in line} ip data.get(ip, ) if ip and (: not in ip or record_type AAAA) and (: in ip or record_type A): return ip except Exception as e: print(f[WARN] cloudflare trace 接口获取IP失败: {e}) # 降级方案ipify url https://api.ipify.org?formatjson if record_type AAAA: url https://api6.ipify.org?formatjson try: req urllib.request.Request(url) with urllib.request.urlopen(req, timeout10) as resp: payload json.loads(resp.read().decode(utf-8)) return payload[ip] except Exception as e: print(f[ERROR] 获取公网IP失败: {e}) return None def api_request(method, path, dataNone): token os.environ.get(CF_API_TOKEN) if not token: print([ERROR] 缺少环境变量 CF_API_TOKEN) sys.exit(1) url API_BASE path headers { Authorization: Bearer token, Content-Type: application/json, } body json.dumps(data).encode(utf-8) if data else None req urllib.request.Request(url, databody, headersheaders, methodmethod) try: with urllib.request.urlopen(req, timeout15) as resp: return json.loads(resp.read().decode(utf-8)) except urllib.error.HTTPError as e: err_body e.read().decode(utf-8, errorsreplace) print(f[ERROR] API HTTP {e.code}: {err_body}) return {success: False, errors: [{message: err_body}]} except Exception as e: print(f[ERROR] 网络请求异常: {e}) return {success: False, errors: [{message: str(e)}]} def get_zone_id(zone_name): result api_request(GET, f/zones?name{zone_name}) if result.get(success) and result.get(result): return result[result][0][id] print([ERROR] 查询Zone ID失败) return None def get_dns_record(zone_id, record_name, record_type): path f/zones/{zone_id}/dns_records?type{record_type}name{record_name} result api_request(GET, path) if result.get(success) and result.get(result): records result[result] if records: return records[0] return None def update_dns_record(zone_id, record_id, record_name, record_type, new_ip): data { type: record_type, name: record_name, content: new_ip, ttl: 120, proxied: False, } path f/zones/{zone_id}/dns_records/{record_id} result api_request(PUT, path, datadata) return result.get(success, False) def create_dns_record(zone_id, record_name, record_type, new_ip): data { type: record_type, name: record_name, content: new_ip, ttl: 120, proxied: False, } path f/zones/{zone_id}/dns_records result api_request(POST, path, datadata) return result.get(success, False) def main(): zone_name os.environ.get(CF_ZONE_NAME) record_name os.environ.get(CF_RECORD_NAME) record_type os.environ.get(CF_RECORD_TYPE, A).upper() if not zone_name or not record_name: print(请设置环境变量 CF_ZONE_NAME 和 CF_RECORD_NAME) sys.exit(1) new_ip get_public_ip(record_type) if not new_ip: print([ERROR] 没有获取到公网IP放弃本次更新) sys.exit(1) print(f[INFO] 当前公网IP: {new_ip}) zone_id get_zone_id(zone_name) if not zone_id: sys.exit(1) print(f[INFO] Zone ID: {zone_id}) record get_dns_record(zone_id, record_name, record_type) if record: current_ip record.get(content, ) if current_ip new_ip: print(f[INFO] IP未变化无需更新: {current_ip}) return print(f[INFO] IP变化: {current_ip} - {new_ip}执行更新) ok update_dns_record(zone_id, record[id], record_name, record_type, new_ip) if ok: print([INFO] 更新成功) else: print([ERROR] 更新失败) sys.exit(1) else: print(f[INFO] 记录不存在尝试创建: {record_name} - {new_ip}) ok create_dns_record(zone_id, record_name, record_type, new_ip) if ok: print([INFO] 创建成功) else: print([ERROR] 创建失败) sys.exit(1) if __name__ __main__: main()运行方式很简单设置好环境变量后直接执行export CF_API_TOKEN你的Token export CF_ZONE_NAMEexample.com export CF_RECORD_NAMEhome.example.com export CF_RECORD_TYPEA python3 cf_ddns.py脚本里的create_dns_record是个防御性设计如果你之前在Cloudflare后台没建过这条记录脚本会自动帮你创建防止出现“记录不存在导致更新失败”的尴尬。实测下来这个逻辑对第一次部署特别友好你不用先去后台建占位记录了。4.3 接入cron定时任务与日志脚本写好之后下一步是让它按固定周期自动执行。最常见的方式是系统crontab把检查频率设置为5分钟一次对Cloudflare的免费API配额来说完全够用。添加一行crontab条目*/5 * * * * cd /path/to/script python3 cf_ddns.py /var/log/cf_ddns.log 21这里我加了一个日志重定向把标准输出和错误都追加到同一个日志文件。建议日志文件所在的目录要有足够的磁盘空间并且定期清理因为每次执行都会生成几行日志跑上几个月也会积累不少数据。更稳妥的做法是交给logrotate去管或者干脆在cron里加上 /var/log/cf_ddns.log 21后每个月定期截断一下。定时任务的使用心得如果你用的是笔记本或者临时设备跑这个脚本注意别让设备休眠。我见过有人在树莓派上跑了一年没管非常稳定也有人放在主力电脑上电脑一合盖就跑不了最后发现“动态解析突然不管用了”是因为设备睡眠了。有条件的话放到NAS的定时任务里、软路由里、或者一个常年开机的Linux小主机上这些场景下稳定性会好很多。4.4 多域名、多记录一键扩展如果家里不止一台设备或者你有多个子域名需要动态解析不需要分别跑多个脚本。改一下脚本让它支持循环更新即可。核心思路是把记录定义放到一个配置文件里循环调API。举个例子我有一台主力NAS用home.example.com一台游戏主机用game.example.com脚本逻辑只要从配置里读到这两个名字逐个执行get_dns_record和update_dns_record就行。唯一要注意的是不同设备如果放在同一个局域网里它们的公网出口IP其实是同一个。这种情况下你完全不需要为每个设备建一条独立记录而是把所有服务映射到同一条动态解析记录上再通过路由器端口转发区分不同服务。只是外部入口统一走home.example.com访问NAS就转发到内网NAS的IP访问游戏主机就转发到游戏主机的IP。如果确实有多条记录要批量更新比如同时更新IPv4和IPv6或者多个公网出口可以在脚本里引入一个循环RECORDS [ {name: home.example.com, type: A}, {name: home.example.com, type: AAAA}, {name: game.example.com, type: A}, ] for rec in RECORDS: ip get_public_ip(rec[type]) record get_dns_record(zone_id, rec[name], rec[type]) ...这样一次运行就能把多个记录都检查一遍不需要为每条记录单独挂一个cron任务也更方便查看整体日志。批量操作时注意别把Cloudflare的API速率限制打穿免费版是每5分钟1200次请求按这个脚本的逻辑每次执行每个记录最多2个GET1个PUT哪怕50条记录都远远够用。5. 常见问题与排查技巧实录5.1 认证相关401/403/9109先说最常见的一类认证失败。API返回401 Unauthorized时基本可以断定Token有问题要么写错了、要么复制的过程中多了空格。403 Forbidden说明Token有效但权限不足检查一下Token有没有给到对应域名的DNS编辑权限。还有一个特殊的错误码9109这个在Cloudflare文档里表示“权限不够但请求被接受了”实际排查时经常是Token的Zone Resources范围没有包含当前域名。我建议用下面这个对照表快速定位认证类问题这些都是我实际踩过的现象典型原因处理方式HTTP 401Token错误或未设置检查环境变量重新复制TokenHTTP 403Token权限不足到API Token页面确认资源范围error_code 9109Zone资源没被给到该域名编辑Token把Zone Resources改为All zones或指定域名HTTP 429请求太频繁触发限流拉长检查间隔增加本地缓存逻辑碰到认证问题别急着改代码先用curl手动请求一次/zones接口确认Token本身是否有效。这个简单的二分法能帮你快速把问题定位到“是Token配置问题”还是“代码逻辑问题”。5.2 请求格式相关400/9106另一类高频问题是请求格式错误。HTTP 400 Bad Request通常意味着请求体里字段类型不对最常见的是proxied和ttl字段传了字符串而不是布尔/整数。Cloudflare的API对JSON类型要求很严格你传proxied: false它能给你解析失败必须传proxied: false。Python的json.dumps默认会把Python布尔值转成JSON的true/false所以一般不会踩坑如果你用模板字符串手写请求体就要特别小心。另外一个比较隐蔽的错误是9106Cloudflare返回的信息通常是“DNS record is invalid”这个错误会在你尝试更新AAAA记录时把content字段传成了IPv4地址或者反过来。所以脚本里最好加一个IP格式校验避免因为IP获取接口返回的数据类型不匹配导致提交了非法内容。我在自己的脚本里加了:是否为分隔符的判断就是为了把IPv4和IPv6区分开。5.3 记录未生效/解析不一致的几个原因脚本显示更新成功了但等了半天域名还是解析到旧IP。这类问题最坑因为它往往不是脚本的问题而是缓存链路的问题。首先检查你的本地设备DNS缓存Windows上可以ipconfig /flushdnsLinux上重启systemd-resolved或者用resolvectl flush-caches。其次检查你用的公共DNS服务器是否已经更新直接dig 8.8.8.8 home.example.com看返回结果如果权威服务器已经返回新IP公共DNS还没更新那大概率是TTL还没过期。这里还要提醒一个很多人忽略的现象如果你开了路由器或者浏览器的DNS over HTTPSDoH功能你实际查询走的可能不是默认DNS这会导致你看到的结果和其他人不一样。排查时把DoH临时关掉或者直接使用Cloudflare的https://cloudflare-dns.com/dns-query去查询看一下会更接近权威结果。最后一种是“记录确实更新了但端口不通”。这种情况要分开看解析到新IP但不是你想的那个IP说明你拿到的公网IP不是真正的公网IP极有可能是被运营商大内网了解析到的IP正确但访问不通说明路由器端口转发没配好或者运营商封了入站端口。动态解析只保证“域名指向对IP”不保证“服务可访问”两者要分开排查。5.4 API调用频率、配额与缓存记录最后聊一下API配额和本地缓存。Cloudflare免费版API速率限制是每5分钟1200次请求一般动态解析脚本完全摸不到这个上限。但如果你写脚本时逻辑不够严谨、每次执行都无脑更新即使IP没变积少成多也可能在极端情况下触发限流。我的经验是尽量把“变更检测”放在本地做IP没变就不调更新接口这既是负责任的API使用习惯也是让日志更干净的好方法。另外可以把查询到的Record ID缓存到本地文件下次直接读取省去每次都调GET接口查ID的步骤。但缓存会带来一个隐患如果有人在Cloudflare后台手动删除了这条记录缓存里的Record ID就是过期的更新时返回404。所以脚本要做好回退逻辑404时重新查询一次记录ID如果查不到就尝试创建记录。我个人在实际操作中的体会是这个方案的关键不在于脚本写得多花哨而在于把“IP怎么取、记录怎么查、变化怎么判、异常怎么退”这几个环节想清楚。Cloudflare API文档写得很规范但真正的坑往往藏在“你以为返回成功就完事了”和“缓存与真实状态之间的时间差”里。把这两个点解决掉整套动态解析系统就能非常省心地长期跑着几乎不需要人工干预。最后再分享一个小技巧新写的脚本先手动跑一次把日志完整读一遍再配上定时任务这样就算中途出问题你也能很快定位是取IP挂了、查记录挂了还是更新接口挂了。