ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

腾讯云DDNS脚本实战:动态公网IP自动更新域名解析

腾讯云DDNS脚本实战:动态公网IP自动更新域名解析 简介这份资源是一套腾讯云DDNS自动更新脚本面向家中宽带拥有公网IP、需要远程访问NAS、个人网站或家庭自动化系统的用户。当运营商重新分配IP导致地址变化时脚本会定期检测并调用腾讯云接口更新域名解析记录让固定域名始终指向最新公网IP避免远程服务因IP漂移而中断。压缩包内共1个文件为sh脚本体积约2KB可直接在Linux环境下配合crontab定时执行也可参考其逻辑移植到Windows任务计划中。脚本围绕API密钥、域名ID与子域名等参数进行配置核心流程涵盖获取当前公网IP、比对解析记录、发起更新请求等环节便于读者理解DDNS自动化原理并快速落地。目前已有4240人学习下载适合具备基础命令行操作能力、希望低成本搭建稳定公网入口的进阶用户参考使用。1. 腾讯云 DDNS 脚本家里公网 IP 变了域名怎么自己跟上家里宽带拿到公网 IPv4 之后第一件让人上头的事就是明明昨天还能用域名连回家里的 NAS今天一出门就失联了。原因不复杂——运营商给家庭宽带的公网 IP 大多是动态的重拨、断电、局端调整都会换一个地址而你在腾讯云 DNSPod 里配的那条 A 记录还停在旧 IP 上。腾讯云 DDNS 脚本要解决的就是这件事让一台家里的设备定时去问「我现在对外的公网 IP 是多少」一旦发现和域名解析记录不一致就调用腾讯云 DNSPod 的 API 把记录改掉。它适合有公网 IPv4、在腾讯云托管域名、又不想每次手动登录控制台改记录的人。整条链路只有三样东西一个能跑脚本的常驻设备、一对腾讯云 API 密钥、一条可被修改的解析记录。把这三样凑齐剩下的就是脚本逻辑和定时任务后面几章我会把每一步拆到能直接抄。2. 动手前先把三件事定死公网 IP、密钥、解析记录2.1 先确认你拿到的是不是真公网 IPv4这一步不确认后面全白干。很多宽带所谓的「公网」其实是运营商大内网出口 IP 和你在路由器上看到的 WAN IP 根本不是一回事。判断方法很直接在路由器管理页看 WAN 口 IP再去任意一个查 IP 的页面看自己对外显示的 IP两者一致才说明你拿到的是可被外部访问的公网 IPv4。如果不一致DDNS 脚本改得再勤也没用因为那个地址压根不通向你家里。常见做法是登录路由器后台找到 WAN 状态那一栏记下 IP、子网掩码、网关。然后在家里的电脑上访问一个显示本机公网 IP 的接口对比两个值。一致就继续不一致就得先找运营商确认。河北省申请电信家庭宽带动态公网 IPv4 的流程各地不同有的直接给有的要报装时说明用途这块以当地营业厅口径为准脚本层面帮不上忙。提示确认公网 IP 时最好用家里网络本身去查不要用手机流量查否则对比的是两个完全不同的出口。2.2 在腾讯云 DNSPod 建一条专用解析记录不要拿主域名或者已经在用的记录去试脚本改错了会影响线上服务。正确做法是新建一条子域名记录比如home.你的域名.com类型选 A记录值先随便填一个当前 IPTTL 设成 600 秒。TTL 太大会导致 IP 变了之后外部还要等很久才生效太小又会让解析请求变多600 秒是家庭场景比较稳的折中。建好之后记下三样东西主域名例如yourdomain.com、子域名前缀例如home、记录 ID。记录 ID 可以在 DNSPod 控制台该条记录的详情里看到也可以后面用 API 查。脚本改记录时靠的就是「域名 子域名 记录 ID」这三个定位参数缺一个都可能改错行。2.3 申请一对最小权限的 API 密钥腾讯云 API 密钥在访问管理控制台创建拿到 SecretId 和 SecretKey。这里有个血泪经验不要用主账号密钥也不要用带全权限的子账号密钥。DNSPod 有独立的权限策略新建一个子用户只授予「DNSPod 相关读写」这一项把密钥范围压到最小。密钥一旦泄露别人最多只能动你的解析记录动不了你账号里的服务器和账单。密钥不要硬编码在脚本里然后传到公开仓库这是最常见的翻车方式。我一般会把它放在一个只有 root 可读的配置文件里权限设成 600脚本运行时读取。下面这段就是配置文件的写法字段名和后面脚本里的变量一一对应。# /etc/ddns/config.env 权限必须是 600 # 腾讯云 API 密钥来自访问管理控制台 SECRET_ID你的SecretId SECRET_KEY你的SecretKey # 解析记录定位三要素 DOMAINyourdomain.com SUB_DOMAINhome RECORD_ID123456789 # 记录类型与 TTL RECORD_TYPEA TTL600字段说明SECRET_ID和SECRET_KEY是调用腾讯云 API 的凭证DOMAIN是主域名不带子前缀SUB_DOMAIN是子域名前缀如果记录就是主域名本身这里填RECORD_ID是那条 A 记录的唯一编号TTL单位是秒。配置文件建好后执行chmod 600 /etc/ddns/config.env再确认属主是 root。3. 脚本怎么写查当前 IP、比对、调 API 改记录3.1 用 shell 拉取本机公网 IP 并做格式校验脚本第一步是拿到当前公网 IP。常见做法是请求一个返回纯文本 IP 的接口拿到之后必须做格式校验因为网络抖动时接口可能返回一段 HTML 错误页直接拿去比对会误判。下面这段用 shell 实现正则只放行标准 IPv4。#!/bin/bash # 拉取当前公网 IP失败重试三次 get_public_ip() { local ip for i in 1 2 3; do # 接口返回纯文本 IP超时 5 秒 ip$(curl -s --max-time 5 https://api.ipify.org) # 严格校验 IPv4 格式不匹配就重试 if [[ $ip ~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then echo $ip return 0 fi sleep 2 done return 1 }逻辑说明curl -s静默请求--max-time 5防止接口卡死拖住整个脚本正则^([0-9]{1,3}\.){3}[0-9]{1,3}$只允许四段数字加点能挡掉大部分异常返回循环三次是给临时网络抖动留后悔药。参数上超时时间可以按你家网络质量调2 到 10 秒都合理重试次数不建议超过 5 次否则一次执行拖太久会和下一次定时任务重叠。3.2 查 DNSPod 现有记录值只在变化时才改拿到当前 IP 之后不要无脑调修改接口。先查一次现有记录值相同就退出不同才改。这样能大幅减少 API 调用次数也避免频繁修改触发风控。腾讯云 DNSPod 的 API 走的是签名鉴权签名算法是 TC3-HMAC-SHA256手写容易出错我一般用官方 SDK 或者封装好的签名函数。# 查询现有解析记录值需要先完成 TC3 签名 # 这里展示请求体构造签名部分由 sign 函数生成 query_record() { local body body$(cat EOF { Domain: ${DOMAIN}, RecordId: ${RECORD_ID} } EOF ) # 调用 DescribeRecord返回 JSON curl -s -X POST https://dnspod.tencentcloudapi.com \ -H Content-Type: application/json \ -H Authorization: ${AUTH_HEADER} \ -H X-TC-Action: DescribeRecord \ -H X-TC-Version: 2021-03-23 \ -H X-TC-Region: ap-guangzhou \ -d $body }逻辑说明X-TC-Action指定调用的接口名查记录是DescribeRecord改记录是ModifyRecordX-TC-Version是接口版本写错会直接报错Authorization头里放的是签名结果由 SecretId、SecretKey、时间戳和请求体共同算出。参数上Domain填主域名RecordId填配置里那条记录的编号。返回的 JSON 里RecordValue字段就是当前解析值拿它和get_public_ip的结果比对即可。3.3 调 ModifyRecord 更新记录并验证生效比对发现不一致时调ModifyRecord把记录值改成新 IP。改完之后不要立刻认为成功等几秒再查一次确认返回的RecordValue已经是新 IP才算真正落地。# 更新解析记录 update_record() { local new_ip$1 local body body$(cat EOF { Domain: ${DOMAIN}, RecordId: ${RECORD_ID}, SubDomain: ${SUB_DOMAIN}, RecordType: ${RECORD_TYPE}, RecordLine: 默认, Value: ${new_ip}, TTL: ${TTL} } EOF ) curl -s -X POST https://dnspod.tencentcloudapi.com \ -H Content-Type: application/json \ -H Authorization: ${AUTH_HEADER} \ -H X-TC-Action: ModifyRecord \ -H X-TC-Version: 2021-03-23 \ -H X-TC-Region: ap-guangzhou \ -d $body }逻辑说明SubDomain、RecordType、RecordLine三个字段必须和原记录一致RecordLine填「默认」对应默认线路填错会把记录挪到别的线路上去Value是新 IPTTL保持和配置一致。参数上RecordLine如果原记录不是默认线路要按实际填比如「电信」「联通」。改完后建议再调一次DescribeRecord做校验确认值已更新。3.4 用 crontab 定时执行并加锁防重叠脚本写好了得让它自己跑。Linux 下用 crontab每 5 分钟执行一次比较合适太频繁没必要太稀疏会导致 IP 变了之后失联时间过长。关键是加锁防止上一次还没跑完下一次又起来。# 编辑 crontabcrontab -e # 每 5 分钟执行一次日志追加到文件 */5 * * * * /usr/bin/flock -n /tmp/ddns.lock /etc/ddns/ddns.sh /var/log/ddns.log 21逻辑说明flock -n拿不到锁就直接退出避免两个实例同时改记录 /var/log/ddns.log 21把标准输出和错误都写进日志出问题时能查。参数上*/5是分钟字段想改成 10 分钟就写*/10。日志文件要定期清理不然时间长了会撑爆磁盘可以配一个 logrotate 或者脚本里自己截断。4. 避坑与排查这几个地方最容易翻车4.1 现象脚本报签名错误提示 AuthFailure原因TC3 签名对时间戳敏感服务器时间偏差超过几分钟就会验签失败另外请求体里的字段顺序、空格、换行都会影响签名结果。解决先date看服务器时间和标准时间差超过 60 秒就装 ntp 同步签名用的请求体必须和实际发送的完全一致建议把 body 存成变量签名和发送都用同一个变量不要手写两遍。4.2 现象记录改了但外部访问还是旧 IP原因本地 DNS 缓存或者 TTL 还没过期。解决先确认DescribeRecord返回的已经是新值然后在外部网络用dig home.你的域名.com查实际解析结果。如果解析已经是新 IP 但访问不通问题就不在 DDNS而在端口映射或防火墙别在脚本上继续折腾。4.3 现象脚本偶尔把记录改成内网 IP 或错误值原因拉取公网 IP 的接口返回了异常内容而脚本没做校验就写进去了。解决get_public_ip里的正则校验不能省另外可以在写入前加一层判断如果新 IP 是10.、172.16到172.31、192.168.开头直接拒绝并记日志这些明显是内网地址。4.4 现象crontab 里手动执行正常定时执行不生效原因crontab 的环境变量和登录 shell 不一样PATH里可能找不到curl或者配置文件路径写的是相对路径。解决脚本里所有命令用绝对路径比如/usr/bin/curl配置文件也用绝对路径/etc/ddns/config.env在 crontab 顶部显式设置PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin。4.5 现象API 调用被限频返回 RequestLimitExceeded原因脚本执行太频繁或者每次执行都无条件调修改接口。解决把间隔调到 5 分钟以上并且坚持「先查后改」值没变就不调ModifyRecord。如果家里网络特别不稳定导致 IP 频繁跳变可以考虑加一个「连续两次检测到同一新 IP 才更新」的防抖逻辑。5. 进阶把 DDNS 做成能自证健康的小服务脚本能跑通只是及格线真正省心的是让它能自己报告状态。我一般会在脚本末尾加一段健康检查把最近一次成功更新的时间、当前 IP、执行结果写进一个状态文件再用一个极简的 HTTP 服务把它暴露出来这样在外面用手机就能看到 DDNS 到底活着没有。下面这段用 Python 起一个只读状态页配合前面的 shell 脚本使用。# /etc/ddns/status_server.py from http.server import BaseHTTPRequestHandler, HTTPServer import json, os STATUS_FILE /var/lib/ddns/status.json class Handler(BaseHTTPRequestHandler): def do_GET(self): # 只暴露 /status 路径其他一律 404 if self.path ! /status: self.send_response(404) self.end_headers() return try: with open(STATUS_FILE, r) as f: data json.load(f) except Exception: data {ok: False, msg: status file unreadable} body json.dumps(data, ensure_asciiFalse).encode() self.send_response(200) self.send_header(Content-Type, application/json) self.send_header(Content-Length, str(len(body))) self.end_headers() self.wfile.write(body) if __name__ __main__: # 只监听内网地址不要直接暴露到公网 HTTPServer((127.0.0.1, 8088), Handler).serve_forever()逻辑说明do_GET只处理/status其他路径返回 404减少暴露面状态文件读不到时返回ok: false方便外部监控判断。参数上监听地址写127.0.0.1是故意的要对外访问就通过反向代理加一层认证别把这个端口直接映射出去。shell 脚本每次执行完把结果写进/var/lib/ddns/status.json字段建议包含ok、ip、updated_at、last_error。验证方法很简单手动改一次解析记录值等一个执行周期看状态文件里的updated_at有没有变、ip是不是当前公网 IP再用dig确认解析已跟上。三步都对这套 DDNS 就算真正立住了。我自己踩过最深的坑是早期图省事把密钥写进脚本又传了仓库后来全部改成配置文件加最小权限子账号这个习惯一直保留到现在。希望帮到你。本文还有配套的精品资源点击获取
返回列表