动态 DNS:家用宽带也能有个固定域名

家庭宽带的公网 IP 会变(重拨或运营商定期回收)。DDNS 让你用一个固定域名访问它,每次 IP 变了自动更新解析。

一、先确认有没有公网 IP

很多宽带其实没有公网 IPv4 了(运营商用 CGNAT)。先测:

bash
# 1. 看路由器 WAN 口获取的 IP
# 登录路由器管理界面看

# 2. 看外网看到的 IP
curl -s ifconfig.me
curl -s ip.sb

# 3. 两者一致 → 有公网 IP
# 路由器是 100.x / 10.x / 172.16-31.x → 大概率是内网(CGNAT)

如果没有公网 IPv4:

  • 打电话给运营商申请(部分可申请到,且可能收费);
  • 用 IPv6(现在家宽普遍有 IPv6,且是公网);
  • 改用内网穿透(frp / Cloudflare Tunnel)或组网(Tailscale)。

二、DDNS 的原理

bash
定时检查本机公网 IP
  → 与 DNS 记录里的值比较
  → 不同则调用 API 更新 A/AAAA 记录

TTL 要设短(60~300 秒),否则 IP 变了要等很久才生效。

三、用 Cloudflare API 自己实现

准备

  1. 在 Cloudflare 控制台创建 API Token(权限:Zone.DNS 编辑);
  2. 记下 Zone ID(域名概览页右下角);
  3. 先在控制台手动建一条 A 记录(比如 home.bzii.cn)。

脚本

bash
#!/usr/bin/env bash
# /opt/scripts/ddns.sh
set -euo pipefail

CF_TOKEN="你的API Token"
ZONE_ID="你的Zone ID"
RECORD_NAME="home.bzii.cn"
TTL=120

# 1. 获取当前公网 IP
IP=$(curl -s --max-time 10 https://api.ipify.org)
if [ -z "$IP" ]; then echo "获取 IP 失败"; exit 1; fi

# 2. 查 DNS 里的记录 ID 和当前值
RESP=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=A&name=$RECORD_NAME" \
  -H "Authorization: Bearer $CF_TOKEN" -H "Content-Type: application/json")

REC_ID=$(echo "$RESP" | jq -r ".result[0].id")
CUR_IP=$(echo "$RESP" | jq -r ".result[0].content")

# 3. 相同就不动
if [ "$IP" = "$CUR_IP" ]; then
  echo "[$(date)] IP 未变: $IP"
  exit 0
fi

# 4. 不同则更新
curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$REC_ID" \
  -H "Authorization: Bearer $CF_TOKEN" \
  -H "Content-Type: application/json" \
  --data "{\"content\":\"$IP\",\"ttl\":$TTL}" > /dev/null

echo "[$(date)] 已更新 $RECORD_NAME: $CUR_IP → $IP"
bash
chmod +x /opt/scripts/ddns.sh
sudo /opt/scripts/ddns.sh
dig +short home.bzii.cn

定时执行

bash
crontab -e
*/5 * * * * /opt/scripts/ddns.sh >> /var/log/ddns.log 2>&1

四、IPv6 版本

家宽的 IPv6 通常每个设备都有一个公网地址,而且前缀也会变。做法类似,但要更新 AAAA 记录:

bash
# 获取本机 IPv6(全球单播地址)
IP6=$(ip -6 addr show scope global | grep inet6 | awk '{print $2}' | cut -d/ -f1 | head -1)

IPv6 地址很长且会变,用 DDNS 管理是唯一实用的方式。好处是不需要 NAT、不需要端口映射

五、路由器自带 DDNS

很多路由器(华硕、小米、OpenWrt)内置 DDNS 客户端,支持花生壳、No-IP、DynDNS 等。

优点:不用自己写脚本,路由器上直接配。 缺点:只能用它们支持的第三方服务(域名不是自己的),或者部分支持 Cloudflare。

OpenWrt 用户可以直接装 ddns-scripts + ddns-scripts-cloudflare

bash
opkg update
opkg install ddns-scripts ddns-scripts-cloudflare luci-app-ddns

六、现成工具:ddclient

bash
sudo apt install -y ddclient
nginx
# /etc/ddclient.conf
protocol=cloudflare, \
use=web, web=https://api.ipify.org, \
zone=www.bzii.cn, \
login=token, \
password=你的API Token, \
home.bzii.cn
bash
sudo systemctl enable --now ddclient
sudo ddclient -verbose -noquiet -force    # 手动触发一次

七、安全注意

把家里的服务暴露到公网,风险不小:

  1. API Token 权限要最小化:只给该域名的 DNS 编辑权限;
  2. 脚本权限 600,别让其他用户读到 token:
bash
chmod 600 /opt/scripts/ddns.sh
  1. 路由器只映射必要端口,且服务本身要有认证;
  2. 用非标准端口(比如外部 2222 → 内部 22);
  3. 定期看日志,确认没有异常访问。

更推荐的做法:根本不暴露,用 Tailscale 组网。 自己远程访问家里的机器,组网方案比 DDNS + 端口映射安全得多,也更简单。

八、什么时候该用 DDNS

场景建议
自己远程访问家里 NAS / 下载机Tailscale 更好
要给别人提供访问(如私人 Git)DDNS + 端口映射
家里跑个小型站点DDNS + 反向代理 + HTTPS
需要固定 IP 收 webhookCloudflare Tunnel

九、排错

现象排查
域名解析到旧 IPTTL 太长,或脚本没跑
脚本报 401Token 错误或权限不足
脚本报 找不到记录记录名写错,或还没手动创建
IP 一直没变但外网访问不了可能是 CGNAT,没有真公网 IP
更新成功但访问不了路由器端口映射没配
bash
# 看脚本日志
tail -20 /var/log/ddns.log

# 确认当前解析
dig +short home.bzii.cn

# 确认实际公网 IP
curl -s ifconfig.me

DDNS 是「没有固定 IP 时的折中方案」。在用它之前,先问一句:我真的需要把服务暴露到公网吗?