跳到主要内容

动态域名 DDNS:用 API 自动更新解析

家庭宽带的公网 IP 经常变,手动改解析不现实。思路很简单:

  1. 定时获取当前公网 IPv4。
  2. 查询 DNSPod 上对应的 A 记录。
  3. IP 变了就调用 API 修改。

下面给两种做法:现成工具和自写脚本。

一、先准备 API 密钥​

  1. 登录腾讯云访问管理控制台:console.cloud.tencent.com/cam/capi。
  2. 新建密钥,得到 SecretId 和 SecretKey。
  3. 建议创建子用户,只授予 DNSPod 相关权限(如 QcloudDNSPodFullAccess,或自定义只含 DNSPod 读写的策略),不要用主账号密钥。
  4. 记录密钥后妥善保存,SecretKey 只在创建时显示一次。

安全要求:

  • 不要提交到 Git 仓库。
  • 不要写在公开的教程/截图里。
  • 定期轮换;怀疑泄露立即禁用。

二、方案 A:现成工具 ddns-go​

适合不想写代码的场景:

  1. 在需要更新解析的机器上安装 ddns-go。
  2. 打开它的 Web 界面(默认 http://IP:9876)。
  3. DNS 服务商选择 DNSPod,填入腾讯云 API 密钥。
  4. 添加任务:域名填 example.com,子域名填 home,获取 IP 方式选「通过接口获取 IPv4」。
  5. 更新间隔建议 5 分钟,保存。
  6. 在 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 变化时才调用修改接口。

相关阅读: