动态域名 DDNS:用 API 自动更新解析
家庭宽带的公网 IP 经常变,手动改解析不现实。思路很简单:
- 定时获取当前公网 IPv4。
- 查询 DNSPod 上对应的 A 记录。
- IP 变了就调用 API 修改。
下面给两种做法:现成工具和自写脚本。
一、先准备 API 密钥
- 登录腾讯云访问管理控制台:console.cloud.tencent.com/cam/capi。
- 新建密钥,得到
SecretId和SecretKey。 - 建议创建子用户,只授予 DNSPod 相关权限(如
QcloudDNSPodFullAccess,或自定义只含 DNSPod 读写的策略),不要用主账号密钥。 - 记录密钥后妥善保存,
SecretKey只在创建时显示一次。
安全要求:
- 不要提交到 Git 仓库。
- 不要写在公开的教程/截图里。
- 定期轮换;怀疑泄露立即禁用。
二、方案 A:现成工具 ddns-go
适合不想写代码的场景:
- 在需要更新解析的机器上安装 ddns-go。
- 打开它的 Web 界面(默认
http://IP:9876)。 - DNS 服务商选择
DNSPod,填入腾讯云 API 密钥。 - 添加任务:域名填
example.com,子域名填home,获取 IP 方式选「通过接口获取 IPv4」。 - 更新间隔建议 5 分钟,保存。
- 在 DNSPod 控制台确认
home.example.com的 A 记录已经变成当前公网 IP。
不同版本的 ddns-go 字段名称可能略有差异,以界面提示为准。它的优势是带 Web UI、支持多平台、能同时更新 IPv4/IPv6。
三、方案 B:Python 脚本 + systemd
适合需要自己控制逻辑、接入监控/日志的场景。
1. 安装 SDK
python3 -m pip install --upgrade tencentcloud-sdk-python
国内网络如果慢,可以用腾讯云镜像:
python3 -m pip install --upgrade tencentcloud-sdk-python \
-i https://mirrors.cloud.tencent.com/pypi/simple
2. 保存脚本
保存为 /usr/local/bin/dnspod-ddns.py:
#!/usr/bin/env python3
"""把 DNSPod 上指定子域的 A 记录更新为当前公网 IPv4。"""
import os
import sys
import urllib.request
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.dnspod.v20210323 import dnspod_client, models
DOMAIN = os.environ.get("DDNS_DOMAIN", "example.com")
SUBDOMAIN = os.environ.get("DDNS_SUBDOMAIN", "home")
RECORD_LINE = os.environ.get("DDNS_LINE", "默认")
TTL = int(os.environ.get("DDNS_TTL", "600"))
IP_URLS = (
"https://4.ipw.cn",
"https://api.ipify.org",
"https://ifconfig.me/ip",
)
def get_public_ipv4() -> str:
last_error = None
for url in IP_URLS:
try:
with urllib.request.urlopen(url, timeout=8) as resp:
ip = resp.read().decode("utf-8").strip()
except Exception as exc: # 网络失败时换下一个接口
last_error = exc
continue
parts = ip.split(".")
if len(parts) == 4 and all(p.isdigit() and 0 <= int(p) <= 255 for p in parts):
return ip
last_error = ValueError(f"invalid ip: {ip!r}")
raise RuntimeError(f"无法获取公网 IPv4: {last_error}")
def build_client() -> dnspod_client.DnspodClient:
cred = credential.Credential(
os.environ["TENCENTCLOUD_SECRET_ID"],
os.environ["TENCENTCLOUD_SECRET_KEY"],
)
http_profile = HttpProfile()
http_profile.endpoint = "dnspod.tencentcloudapi.com"
client_profile = ClientProfile()
client_profile.httpProfile = http_profile
return dnspod_client.DnspodClient(cred, "", client_profile)
def main() -> None:
ip = get_public_ipv4()
client = build_client()
req = models.DescribeRecordListRequest()
req.Domain = DOMAIN
req.Subdomain = SUBDOMAIN
req.RecordType = "A"
resp = client.DescribeRecordList(req)
records = [r for r in (resp.RecordList or []) if r.Line == RECORD_LINE]
if not records:
create_req = models.CreateRecordRequest()
create_req.Domain = DOMAIN
create_req.SubDomain = SUBDOMAIN
create_req.RecordType = "A"
create_req.RecordLine = RECORD_LINE
create_req.Value = ip
create_req.TTL = TTL
client.CreateRecord(create_req)
print(f"created {SUBDOMAIN}.{DOMAIN} -> {ip}")
return
record = records[0]
if record.Value == ip:
print(f"unchanged {SUBDOMAIN}.{DOMAIN} -> {ip}")
return
modify_req = models.ModifyRecordRequest()
modify_req.Domain = DOMAIN
modify_req.RecordId = record.RecordId
modify_req.SubDomain = SUBDOMAIN
modify_req.RecordType = "A"
modify_req.RecordLine = RECORD_LINE
modify_req.Value = ip
modify_req.TTL = TTL
client.ModifyRecord(modify_req)
print(f"updated {SUBDOMAIN}.{DOMAIN}: {record.Value} -> {ip}")
if __name__ == "__main__":
try:
main()
except Exception as exc:
print(f"ddns failed: {exc}", file=sys.stderr)
sys.exit(1)
赋予执行权限:
chmod 755 /usr/local/bin/dnspod-ddns.py
3. 配置密钥
创建 /etc/dnspod-ddns.env:
TENCENTCLOUD_SECRET_ID=你的SecretId
TENCENTCLOUD_SECRET_KEY=你的SecretKey
DDNS_DOMAIN=example.com
DDNS_SUBDOMAIN=home
DDNS_LINE=默认
DDNS_TTL=600
收紧权限:
chmod 600 /etc/dnspod-ddns.env
先手动跑一次:
set -a
. /etc/dnspod-ddns.env
set +a
/usr/local/bin/dnspod-ddns.py
输出 created 或 updated 即成功。
4. 配置 systemd 定时任务
创建 /etc/systemd/system/dnspod-ddns.service:
[Unit]
Description=DNSPod DDNS update
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/dnspod-ddns.env
ExecStart=/usr/local/bin/dnspod-ddns.py
创建 /etc/systemd/system/dnspod-ddns.timer:
[Unit]
Description=Run DNSPod DDNS every 5 minutes
[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
Persistent=true
[Install]
WantedBy=timers.target
启用:
systemctl daemon-reload
systemctl enable --now dnspod-ddns.timer
systemctl list-timers dnspod-ddns.timer
查看日志:
journalctl -u dnspod-ddns.service -n 50 --no-pager
5. 验证
dig +short A home.example.com
curl -s https://4.ipw.cn
两个结果应一致。注意 TTL 是 600 秒,修改后各地递归 DNS 最多可能缓存 10 分钟。
四、方案 C:OpenWrt
OpenWrt 可以用 ddns-scripts:
opkg update
opkg install ddns-scripts curl
在 LuCI 的「服务 → 动态 DNS」中新增任务:
- 服务提供商:
DNSPod(不同版本可能显示为dnspod.cn或dnspod.com) - 域名:
example.com - 主机名:
home - 用户名/密码:填 DNSPod 的 API Token(不同版本可能要求 ID + Token,或腾讯云 SecretId/SecretKey)
- 接口:
wan - 检查间隔:5 分钟
旧版 ddns-scripts 的 DNSPod 插件使用老接口,可能提示鉴权失败。如果配置不成功,可以直接在路由器上装 Python 跑方案 B,或用 ddns-go 的 OpenWrt 包。
五、常见问题
脚本报 AuthFailure
- 密钥填错、密钥被禁用、子用户没有 DNSPod 权限。
- 确认用的是腾讯云 API 密钥,不是 DNSPod 旧版 API Token。
报 InvalidParameter.DomainInvalid
Domain 要填主域名 example.com,不是 home.example.com。子域填在 SubDomain / Subdomain 里。
报 LimitExceeded.RecordTtlLimit
TTL 超出套餐允许范围。免费版一般最低 600 秒,保持 600 即可。
记录一直不更新
- 先手动执行脚本看输出。
- 检查
DDNS_LINE是否为「默认」,线路不匹配会查不到记录。 - 确认机器获取到的是公网 IPv4,不是
100.64.x.x(运营商级 NAT)、192.168.x.x或10.x.x.x。 - 检查定时器是否真的在跑:
systemctl list-timers。
会不会频繁触发 API 限流
5 分钟一次很低频,正常不会。脚本只在 IP 变化时才调用修改接口。
相关阅读: