本文详解DDNS工作原理、主流方案对比,以及在各种场景下的实战配置。
前言
家里的宽带IP是动态的,每次重启光猫IP就变了。想从外面访问家里的服务器,怎么办?
DDNS(Dynamic DNS) 就是解决这个问题的:自动检测IP变化,自动更新域名解析。
今天来聊聊各种DDNS方案的选择和配置。
一、DDNS原理
1.1 动态IP的问题
运营商给家庭宽带分配的通常是动态公网IP:
- IP地址会变化(重启、租期到期)
- 无法用固定IP访问
1.2 DDNS如何解决
┌─────────────┐ IP变化 ┌─────────────┐
│ 家庭路由 │ ──检测到──→ │ DDNS客户端 │
│ IP:1.2.3.4 │ │ │
└─────────────┘ └──────┬──────┘
│ 调用API更新
↓
┌─────────────┐
│ DNS服务商 │
│ example.com │
│ → 1.2.3.4 │
└─────────────┘
工作流程:
- DDNS客户端定期检测本机公网IP
- 发现IP变化后,调用DNS服务商API
- 更新域名的A记录
- 域名始终指向当前IP
二、DDNS方案对比
2.1 免费DDNS服务
| 服务 | 域名 | 优点 | 缺点 |
|---|---|---|---|
| No-IP | xxx.ddns.net | 免费、客户端多 | 需每月确认 |
| DuckDNS | xxx.duckdns.org | 完全免费 | 域名不好看 |
| Dynu | xxx.dynu.net | 免费、功能全 | 速度一般 |
| FreeDNS | 多种子域名 | 选择多 | 稳定性一般 |
2.2 国内DNS服务商
| 服务商 | 支持DDNS | API调用 | 适合场景 |
|---|---|---|---|
| 阿里云DNS | ✅ | 有官方API | 有自己域名 |
| 腾讯云DNS | ✅ | 有官方API | 有自己域名 |
| Cloudflare | ✅ | 有API | 海外访问 |
| 华为云DNS | ✅ | 有API | 华为云用户 |
2.3 路由器内置
很多路由器/光猫内置DDNS功能:
- 花生壳
- No-IP
- 自定义
三、阿里云DDNS配置
3.1 前置条件
- 有一个域名(托管在阿里云)
- 创建AccessKey
3.2 获取AccessKey
- 登录阿里云控制台
- 右上角头像 → AccessKey管理
- 创建AccessKey,保存好ID和Secret
3.3 使用Shell脚本
bash
#!/bin/bash
# aliyun-ddns.sh
# 配置区
ACCESS_KEY_ID="your_access_key_id"
ACCESS_KEY_SECRET="your_access_key_secret"
DOMAIN="example.com"
SUBDOMAIN="home" # 完整域名是 home.example.com
# 获取当前公网IP
get_current_ip() {
curl -s http://ip.3322.net || \
curl -s http://ifconfig.me || \
curl -s http://ipinfo.io/ip
}
# 获取当前DNS记录
get_dns_ip() {
aliyun alidns DescribeDomainRecords \
--DomainName "$DOMAIN" \
--RRKeyWord "$SUBDOMAIN" \
--Type "A" 2>/dev/null | \
jq -r '.DomainRecords.Record[0].Value'
}
# 更新DNS记录
update_dns() {
local record_id=$1
local ip=$2
aliyun alidns UpdateDomainRecord \
--RecordId "$record_id" \
--RR "$SUBDOMAIN" \
--Type "A" \
--Value "$ip"
}
# 主逻辑
current_ip=$(get_current_ip)
dns_ip=$(get_dns_ip)
if [ "$current_ip" != "$dns_ip" ]; then
echo "$(date): IP changed from $dns_ip to $current_ip"
record_id=$(aliyun alidns DescribeDomainRecords \
--DomainName "$DOMAIN" \
--RRKeyWord "$SUBDOMAIN" | \
jq -r '.DomainRecords.Record[0].RecordId')
update_dns "$record_id" "$current_ip"
echo "DNS updated successfully"
else
echo "$(date): IP unchanged ($current_ip)"
fi
3.4 使用第三方工具(推荐)
ddns-go 是一个优秀的开源DDNS工具:
bash
# 下载
wget https://github.com/jeessy2/ddns-go/releases/download/v5.6.0/ddns-go_5.6.0_linux_x86_64.tar.gz
tar xf ddns-go*.tar.gz
# 运行
./ddns-go
# 访问 http://localhost:9876 配置
ddns-go优点:
- Web界面配置
- 支持多种DNS服务商
- 支持IPv6
- 支持Webhook通知
四、腾讯云DDNS配置
4.1 获取密钥
- 登录腾讯云控制台
- 访问密钥管理
- 创建API密钥
4.2 Python脚本
python
#!/usr/bin/env python3
# tencent_ddns.py
import json
import requests
from tencentcloud.common import credential
from tencentcloud.dnspod.v20210323 import dnspod_client, models
# 配置
SECRET_ID = "your_secret_id"
SECRET_KEY = "your_secret_key"
DOMAIN = "example.com"
SUBDOMAIN = "home"
def get_current_ip():
"""获取当前公网IP"""
services = [
'http://ip.3322.net',
'http://ifconfig.me',
'http://ipinfo.io/ip'
]
for service in services:
try:
resp = requests.get(service, timeout=5)
return resp.text.strip()
except:
continue
return None
def get_dns_record():
"""获取DNS记录"""
cred = credential.Credential(SECRET_ID, SECRET_KEY)
client = dnspod_client.DnspodClient(cred, "")
req = models.DescribeRecordListRequest()
req.Domain = DOMAIN
req.Subdomain = SUBDOMAIN
resp = client.DescribeRecordList(req)
if resp.RecordList:
return resp.RecordList[0]
return None
def update_dns_record(record_id, ip):
"""更新DNS记录"""
cred = credential.Credential(SECRET_ID, SECRET_KEY)
client = dnspod_client.DnspodClient(cred, "")
req = models.ModifyRecordRequest()
req.Domain = DOMAIN
req.RecordId = record_id
req.SubDomain = SUBDOMAIN
req.RecordType = "A"
req.RecordLine = "默认"
req.Value = ip
client.ModifyRecord(req)
print(f"Updated {SUBDOMAIN}.{DOMAIN} to {ip}")
if __name__ == "__main__":
current_ip = get_current_ip()
record = get_dns_record()
if record and record.Value != current_ip:
update_dns_record(record.RecordId, current_ip)
else:
print(f"IP unchanged: {current_ip}")
4.3 安装依赖
bash
pip install tencentcloud-sdk-python requests
五、Cloudflare DDNS配置
5.1 获取API Token
- 登录Cloudflare
- My Profile → API Tokens
- Create Token → Edit zone DNS
5.2 使用Shell脚本
bash
#!/bin/bash
# cloudflare-ddns.sh
# 配置
CF_API_TOKEN="your_api_token"
CF_ZONE_ID="your_zone_id"
CF_RECORD_NAME="home.example.com"
# 获取当前IP
CURRENT_IP=$(curl -s http://ipinfo.io/ip)
# 获取DNS记录ID
RECORD_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records?name=$CF_RECORD_NAME" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" | jq -r '.result[0].id')
# 获取当前DNS记录的IP
DNS_IP=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records/$RECORD_ID" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" | jq -r '.result.content')
# 更新DNS
if [ "$CURRENT_IP" != "$DNS_IP" ]; then
echo "Updating DNS: $DNS_IP -> $CURRENT_IP"
curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records/$RECORD_ID" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"$CF_RECORD_NAME\",\"content\":\"$CURRENT_IP\",\"ttl\":120,\"proxied\":false}"
echo "DNS updated"
else
echo "IP unchanged: $CURRENT_IP"
fi
六、定时任务配置
6.1 Cron定时执行
bash
# 编辑crontab
crontab -e
# 每5分钟检查一次
*/5 * * * * /path/to/ddns-script.sh >> /var/log/ddns.log 2>&1
6.2 Systemd Timer
bash
# /etc/systemd/system/ddns.service
[Unit]
Description=DDNS Update Service
[Service]
Type=oneshot
ExecStart=/path/to/ddns-script.sh
# /etc/systemd/system/ddns.timer
[Unit]
Description=DDNS Update Timer
[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
[Install]
WantedBy=timers.target
bash
systemctl enable ddns.timer
systemctl start ddns.timer
七、路由器DDNS配置
7.1 OpenWrt内置DDNS
bash
# 安装ddns脚本
opkg update
opkg install ddns-scripts luci-app-ddns
# Web界面配置
# Services → Dynamic DNS
7.2 常见路由器
华硕路由器:
- 外部网络 → DDNS → 选择服务商配置
小米路由器:
- 高级设置 → DDNS → 配置账号
TP-Link:
- 应用管理 → DDNS → 配置
八、没有公网IP怎么办
8.1 检测是否有公网IP
bash
# 方法1:比较内外网IP
# 路由器WAN口IP
route -n | grep UG
# 公网IP
curl ip.3322.net
# 如果两个IP不一样,说明没有公网IP
8.2 替代方案
如果运营商没有给公网IP(大内网),DDNS就没用了。替代方案:
-
向运营商申请公网IP:打客服电话,说要监控/远程办公
-
使用组网软件:如星空组网,不需要公网IP也能远程访问
- 安装客户端
- 登录同一账号
- 通过虚拟IP访问
-
内网穿透:frp、ngrok等
九、DDNS + 端口映射
有了DDNS域名,还需要配置端口映射才能访问内网服务:
互联网 → home.example.com:8080 → 路由器:8080 → 内网服务器:80
路由器端口映射配置:
- 外部端口:8080
- 内部IP:192.168.1.100
- 内部端口:80
- 协议:TCP
安全建议:
- 不要映射22端口到公网
- 使用非标准端口
- 配合fail2ban等安全措施
- 或者用组网软件替代端口映射,更安全
十、总结
DDNS方案选择建议:
| 场景 | 推荐方案 |
|---|---|
| 快速体验 | DuckDNS(完全免费) |
| 有自己域名 | ddns-go + 阿里云/腾讯云 |
| 海外访问 | Cloudflare |
| 路由器方案 | OpenWrt ddns-scripts |
| 无公网IP | 组网软件(如星空组网) |
DDNS虽然能解决动态IP问题,但还要配合端口映射使用,有一定安全风险。如果不想折腾,直接用组网软件可能是更简单的选择。
参考资料
- ddns-go: https://github.com/jeessy2/ddns-go
- 阿里云DNS API: https://help.aliyun.com/document_detail/29739.html
- Cloudflare API: https://api.cloudflare.com/
💡 提示:DDNS更新有延迟(取决于TTL设置),建议TTL设置短一些(如120秒)。但太短会增加DNS查询次数。