使用 ddns-go 与 acme.sh 实现群晖 NAS 动态解析与 SSL 证书全自动续期部署

前言:架构分工与避坑认知

在家庭宽带环境下通过公网安全访问 NAS,必须由两个核心组件协同工作:

flowchart LR
    subgraph Access["外网访问"]
        Client["外网访客 / 客户端"]
    end

    subgraph Cloud["云端托管"]
        CF["Cloudflare DNS<br/>(权威解析)"]
    end

    subgraph Synology["群晖 NAS (家庭宽带)"]
        direction TB
        DDNS["ddns-go (动态解析)"]
        NAS["群晖 NAS (Nginx 反代)"]
        ACME["acme.sh (证书自动化)"]
    end

    Client -->|"1. 查询解析"| CF
    Client ==>|"2. HTTPS 直连"| NAS
    DDNS -->|"实时同步 IP"| CF
    ACME -->|"DNS-01 验证"| CF
    ACME ==>|"3. 部署证书"| NAS
  1. ddns-go(动态域名解析):家庭宽带公网 IP 是动态变化的。ddns-go 负责实时探测本地公网 IP 并同步更新 Cloudflare DNS 记录,保证域名始终指向正确的家宽地址
  2. acme.sh(SSL 证书自动化):负责通过 DNS API 自动申请公网受信任的 Let’s Encrypt 泛域名证书,保证数据传输加密且浏览器不报安全警告

常见大坑:为什么 Cloudflare Origin 证书会报“不安全”?

Cloudflare 控制台生成的 15 年期 Origin CA 证书 仅供 Cloudflare 边缘节点到源站之间加密(需开启橙色小云朵 CDN 代理)。

  • 如果你的访问带了自定义高位端口(例如 50001 等非 Cloudflare 免费 CDN 支持的端口),流量是直连 NAS 的;
  • 此时浏览器直接与 NAS 握手,收到不受信任的 Origin 证书就会立刻弹出 “您的连接不是私密连接 / 证书不受信任” 的警告;
  • 正解:直连必须使用 Let’s EncryptZeroSSL 签发的公网证书。

1. 准备条件与获取 Cloudflare API Token

1.1 准备条件

  1. 自己的域名已托管在 Cloudflare(例如 yourdomain.com)。
  2. 家庭宽带具备公网 IPv4 或 IPv6,路由器已做好端口映射(NAT)。
  3. 群晖开启 SSH 终端功能。

1.2 创建 Cloudflare API Token

为了让 ddns-goacme.sh 能够自动修改 DNS 记录:

  1. 登录 Cloudflare 控制台
  2. 点击右上角头像 -> 我的个人资料 -> API 令牌 -> 创建令牌
  3. 选择 编辑区域 DNS (Edit zone DNS) 模板。
  4. 权限确认包含:
    • 区域 - DNS - 编辑
    • 区域 - 区域 - 读取
  5. 资源范围选择目标域名(例如 yourdomain.com)。
  6. 生成后妥善复制保存该 Token。

2. 第一步:部署 ddns-go 实现动态解析

推荐使用 Docker(Container Manager)快速部署 ddns-go

1
2
3
4
5
6
docker run -d \
--name ddns-go \
--restart=always \
--net=host \
-v /volume1/docker/ddns-go:/root \
jeessy/ddns-go

配置 ddns-go

  1. 浏览器打开 http://<NAS局域网IP>:9876
  2. DNS 服务商选择 Cloudflare,填入刚刚生成的 API Token。
  3. 在 IPv4 / IPv6 域名配置处填写你要解析的二级域名,例如:
    1
    nas.yourdomain.com
  4. 点击保存,观察日志提示 [Cloudflare] 添加/更新成功 即可。此时域名解析已打通。

3. 第二步:环境配置与安装 acme.sh

通过 SSH 登录群晖,进行 acme.sh 的安装与初始化。

3.1 一键安装(避坑:无 crontab 报错)

群晖默认普通用户没有 crontab,直接安装可能会提示 Pre-check failed。需追加 --force 参数:

1
2
curl https://get.acme.sh | sh -s email=your@example.com --force
source ~/.bashrc

3.2 切换默认 CA 机构为 Let’s Encrypt

acme.sh 默认使用 ZeroSSL,国内网络连接其 API 容易出现 curl error code: 7 超时。推荐切换为连通性更好的 Let’s Encrypt:

1
2
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
~/.acme.sh/acme.sh --register-account -m your@example.com

4. 第三步:申请 Let’s Encrypt 泛域名证书

导入 Cloudflare API Token,并发起申请(同时覆盖根域名和全部二级泛域名):

1
2
3
4
5
# 导入 API Token (临时环境变量,脚本会自动持久化到配置中)
export CF_Token="你的Cloudflare_API_Token"

# 发起申请 (泛域名 *.yourdomain.com + 主域名)
~/.acme.sh/acme.sh --issue --dns dns_cf -d yourdomain.com -d *.yourdomain.com

[!NOTE]
脚本会自动调用 Cloudflare API 创建两条 _acme-challenge TXT 记录,等待 20~60 秒 DNS 生效后完成验证,验证通过后自动删除临时 TXT 记录。最后输出 Cert success 即表示证书申请成功!


5. 第四步:全自动续期与部署(群晖任务计划零凭据方案)

很多教程推荐使用管理员账号的 API Deploy Hook,但这存在在配置文件中明文暴露管理员密码的安全隐患。

更推荐的方案是:利用群晖任务计划的原生 root 权限,在本地执行自动更新、文件覆盖与 Nginx 重载。

5.1 创建自动部署脚本

在群晖上创建自动化脚本 /var/services/homes/你的用户名/.acme.sh/renew_and_deploy.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/bin/bash
# ==============================================================================
# 自动续期 Let's Encrypt 证书并部署到群晖系统服务
# ==============================================================================

USER_HOME="/var/services/homes/你的用户名" # 替换为你的家目录路径
ACME_DIR="$USER_HOME/.acme.sh"
DOMAIN="yourdomain.com" # 替换为你的域名
EXPORT_DIR="$USER_HOME/ssl_certs"

echo "[$(date '+%Y-%m-%d %H:%M:%S')] 开始检查 SSL 证书续期..."

# 1. 触发 acme.sh 检查与续期 (未到期自动跳过,到期自动通过 Cloudflare DNS 续期)
"$ACME_DIR/acme.sh" --cron --home "$ACME_DIR"

CERT_PATH="$ACME_DIR/${DOMAIN}_ecc"

if [ -d "$CERT_PATH" ]; then
# 2. 导出备份
mkdir -p "$EXPORT_DIR"
cp -f "$CERT_PATH/${DOMAIN}.cer" "$EXPORT_DIR/cert.pem"
cp -f "$CERT_PATH/${DOMAIN}.key" "$EXPORT_DIR/privkey.pem"
cp -f "$CERT_PATH/ca.cer" "$EXPORT_DIR/chain.pem"
cp -f "$CERT_PATH/fullchain.cer" "$EXPORT_DIR/fullchain.pem"
chmod 644 "$EXPORT_DIR"/*.pem 2>/dev/null || true

# 3. 自动同步覆盖群晖内部系统及反代服务证书
SYNO_CERT_DIR="/usr/syno/etc/certificate"
if [ -d "$SYNO_CERT_DIR" ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 正在同步更新群晖系统证书..."

# 同步默认系统证书
if [ -d "$SYNO_CERT_DIR/system/default" ]; then
cp -f "$CERT_PATH/${DOMAIN}.cer" "$SYNO_CERT_DIR/system/default/cert.pem" 2>/dev/null || true
cp -f "$CERT_PATH/${DOMAIN}.key" "$SYNO_CERT_DIR/system/default/privkey.pem" 2>/dev/null || true
cp -f "$CERT_PATH/fullchain.cer" "$SYNO_CERT_DIR/system/default/fullchain.pem" 2>/dev/null || true
fi

# 同步所有反向代理端口证书
find "$SYNO_CERT_DIR/ReverseProxy" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | while read -r rdir; do
cp -f "$CERT_PATH/${DOMAIN}.cer" "$rdir/cert.pem" 2>/dev/null || true
cp -f "$CERT_PATH/${DOMAIN}.key" "$rdir/privkey.pem" 2>/dev/null || true
cp -f "$CERT_PATH/fullchain.cer" "$rdir/fullchain.pem" 2>/dev/null || true
done

# 4. 平滑重载 Web 服务
if command -v synosystemctl >/dev/null 2>&1; then
synosystemctl reload nginx
elif command -v nginx >/dev/null 2>&1; then
nginx -s reload
fi
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 群晖 Web 服务已重载,新证书生效!"
fi
fi

赋予脚本执行权限:

1
chmod +x ~/.acme.sh/renew_and_deploy.sh

5.2 首次手动导入证书(建立关联)

首次需要先在 DSM 控制面板建立映射关系:

  1. 打开群晖 【控制面板】 -> 【安全性】 -> 【证书】
  2. 点击 【新增】 -> 【添加新证书】 -> 【导入证书】,勾选 【设为默认证书】
    • 私钥:上传 ssl_certs/privkey.pem
    • 证书:上传 ssl_certs/cert.pem
    • 中间证书:上传 ssl_certs/chain.pem
  3. 点击 【设置】,将系统默认以及各个反向代理服务(如 Vaultwarden / DSM)全部选为此证书。

5.3 在群晖「任务计划」中添加定时任务

  1. 打开群晖 【控制面板】 -> 【任务计划】
  2. 【新增】 -> 【计划的任务】 -> 【用户定义的脚本】
    • 常规设置:任务名称填 Auto_Renew_SSL,用户账号必须选择 root
    • 排程:设置为每周运行一次或每月 1 日运行。
    • 任务设置 -> 用户定义的脚本
      1
      bash /var/services/homes/你的用户名/.acme.sh/renew_and_deploy.sh
  3. 点击确定保存。从此每当证书快到期时,脚本会自动续期、覆写群晖系统证书并平滑重载 Nginx,再也无需人工干预!

6. 常见问题 (Q&A)

Q1: 既然申请了泛域名证书,ddns-go 是不是可以关掉了?

解答:绝对不能关!ddns-go 负责把变动的宽带 IP 告诉 Cloudflare(指路);acme.sh 负责让连接具备合法加密证书(通行证)。两者各司其职,缺一不可。

Q2: 为什么以后泛域名下的新子域名不用重新申请证书?

解答:因为申请时包含了 *.yourdomain.com。未来只要在群晖反向代理或 Docker 里新增任何二级子域名服务(如 photos.yourdomain.comvault.yourdomain.com),直接复用该证书即可,立即可用。

Q3: 证书到期前多久会自动续订?

解答:Let’s Encrypt 证书有效期为 90 天。acme.sh 默认会在证书到期前 30 天内自动触发续签流程,保证证书永久处于有效状态。