使用 acme.sh 与 Cloudflare 实现群晖 NAS SSL 证书自动续期与部署

前言

在家庭宽带环境下托管群晖 NAS 时,直接通过公网访问经常会遇到浏览器提示“不安全”的红色警告。解决此问题的核心在于配置受信任的 SSL 证书。

本文直接切入正题,介绍核心工具:acme.sh。由于国内家庭宽带普遍封锁了 80 端口,无法使用常规手段验证域名所有权。利用 acme.sh 调用 Cloudflare API 自动添加 TXT 记录(DNS-01 验证)是最稳妥的解决方案。

1. 核心思路与准备条件

采用 DNS-01 验证,通过调用域名解析服务商的 API,自动添加一条 TXT 记录证明域名所有权,验证完成后自动删除。

准备条件:

  1. 目标域名(例如 nas.yourdomain.com)已托管在 Cloudflare。
  2. 开启群晖(或局域网内其他 Linux 机器)的 SSH 终端服务。
  3. 关键准备:在群晖控制面板新建一个具有管理员权限,但绝对不开启双重验证 (2FA) 的专用本地账号(例如命名为 ssl_updater),专门用于脚本后台静默登录和部署证书。

2. 获取 Cloudflare API Token

为了让脚本能够自动修改 DNS 记录,需要授予其特定权限。

  1. 登录 Cloudflare 控制台。
  2. 进入右上角头像 -> 我的个人资料 -> API 令牌
  3. 点击 创建令牌,选择 编辑区域 DNS 模板。
  4. 确保包含以下两项权限:
    • 区域 - DNS - 编辑
    • 区域 - 区域 - 读取
  5. 资源范围选择目标域名。
  6. 生成后妥善保存此 Token(仅显示一次)。

3. 环境配置:安装 acme.sh 与避坑

通过 SSH 登录目标机器,使用官方一键命令安装。

3.1 一键安装脚本

1
2
curl [https://get.acme.sh](https://get.acme.sh) | sh -s [email protected]
source ~/.bashrc

3.2 切换默认 CA 机构 (绕过 Error Code 7)

acme.sh 默认使用 ZeroSSL 作为发证机构。由于网络环境限制,连接其 API 易出现 curl error code: 7(连接超时)。建议切换为连通性更好的 Let’s Encrypt,并手动注册账户:

1
2
acme.sh --set-default-ca --server letsencrypt
acme.sh --register-account -m [email protected]

3.3 避坑:跨平台换行符报错 (\r not found)

如果曾通过 Windows 环境下载并上传过脚本文件,可能会遇到 /usr/bin/env: ‘sh\r’: No such file or directory 报错。这是因为 Linux 无法识别 Windows 的换行符 (CRLF)。执行以下命令批量修复格式:

1
find ~/.acme.sh -type f -exec sed -i 's/\r$//' {} +

4. 实战:申请泛域名证书

环境配置完成后,导入获取的 Cloudflare API 信息。脚本会自动记录这些变量,用于后续的全自动续期。

1
2
3
4
5
6
# 1. 导入 API Token
export CF_Token="你的Cloudflare_API_Token"
export CF_Account_ID="你的账户ID"

# 2. 发起申请 (同时申请主域名和泛域名)
acme.sh --issue --dns dns_cf -d yourdomain.com -d *.yourdomain.com

执行后,脚本会自动调用 API 添加 TXT 记录。等待 Let’s Encrypt 验证通过后下载证书,最后清理临时 TXT 记录。输出 Cert success 即代表申请成功。

5. 进阶:全自动推送至群晖 (Deploy Hook)

确保证书能自动更新并应用到群晖系统,需要使用 acme.sh 的 Deploy Hook 功能。

关键步骤:绕过 2FA 验证缓存死锁

如果当前操作的 NAS 账号开启了双重验证,部署时会触发拦截要求输入 OTP 验证码。未来自动续期时无法人工输入,因此必须使用前文创建的无 2FA 账号 ssl_updater

为防止脚本读取旧账号配置缓存,请在终端内强制声明变量并执行部署:

1
2
3
4
5
6
7
8
9
10
11
# 强制声明专用账号密码,覆盖系统缓存
export SYNO_Username="ssl_updater"
export SYNO_Password="设定的复杂密码"

# 仅限远程机器推送给群晖时补充以下三行(群晖本机运行则忽略)
export SYNO_Hostname="192.168.1.100"
export SYNO_Port="5000"
export SYNO_Scheme="http"

# 执行部署命令
acme.sh --deploy -d yourdomain.com --deploy-hook synology_dsm

终端输出 Success 代表 API 调用成功。前往群晖“控制面板 > 安全性 > 证书”即可确认最新证书,系统会自动重启 Nginx 服务使其生效。

6. 常见问题 (Q&A)

Q: 运行 deploy 命令时提示 Unable to authenticate 或依然要求输入 OTP 验证码?
A: acme.sh 已经在该域名的配置文件中缓存了旧账号信息。按照第 5 节的方法,在终端重新 export 新的账号变量并运行部署命令,脚本会自动用新配置覆写旧缓存。

Q: 需要在群晖的“任务计划”里设置定时任务吗?
A: 如果 acme.sh 安装在群晖本机,由于系统更新可能重置底层 crontab,建议在群晖面板的“任务计划”中添加一条每月执行一次的自定义脚本:/root/.acme.sh/acme.sh --cron --home /root/.acme.sh。如果是安装在局域网另一台 Linux 设备上进行远程推送,则不需要此操作,该设备底层的 crontab 会自动处理。

Q: 证书的有效期及续签逻辑是什么?
A: Let’s Encrypt 证书有效期为 90 天。完成上述部署后,脚本会在证书剩余约 30 天时自动触发,执行“重新申请 -> 替换群晖旧证书 -> 重启 Web 服务”的完整流程,无需人工干预。