原本《群晖 NAS 公网访问配置》系列前四篇文章已经够用了,本系列中的 NAS 服务不会开放到公网上,https 本来是可有可无的。

但是,如果你和本站一样使用了 .dev 域,那么你肯定也会遇到和我一样的问题:只用 http 无法访问服务,浏览器会强制使用 HTTPS

这个问题的背景是:

.dev 是一个正式的通用顶级域(gTLD),由 Google(Charleston Road Registry / Google Registry)运营。

整个 .dev 被放到 HSTS preload 列表里,主流浏览器会把对 .dev 的请求当作必须走 HTTPS 的站点处理——如果没有有效 TLS,浏览器会拒绝/升级连接,导致无法通过 http://yourname.dev 正常打开。也就是说,使用 .dev 发布站点时必须配置有效的证书(Let’s Encrypt 等都可用)。

为了解决这个问题,本篇文章诞生了:本文将教会你如何通过 certd 这个开源项目申请泛域名证书,并通过流水线定时触发 + 脚本导入实现全自动更新,以便在 DSM 面板和 NAS 上的服务都能安全访问。

本文为《群晖 NAS 公网访问配置》系列文章的第五篇。全部文章请参考:

群晖 NAS 公网访问配置(一):配置 DDNS
群晖 NAS 公网访问配置(二):配置 WireGuard
群晖 NAS 公网访问配置(三):配置 Nginx 访问 Docker 服务
群晖 NAS 公网访问配置(四):配置云服务器
群晖 NAS 公网访问配置(五):使用 certd 免费版配置泛域名证书

方案概览

这套方案的核心思路很简单:

  • certd 免费版通过 DNS-01 验证申请泛域名证书。
  • 在流水线里把证书写入固定路径,再用脚本自动导入 DSM。
  • 配置流水线定时触发,证书到期前自动续期并自动导入,实现全自动维护。

前置条件

  • 你已按前四篇完成 DDNS、WireGuard、Nginx 等配置。
  • 域名托管在腾讯云,并且能使用 API 密钥操作 DNS 解析。
  • 群晖已安装 Container Manager(或 Docker 套件),可以运行容器。
  • 先在套件中心安装 Python3 套件,脚本会用 python3(或 python)解析 DSM 返回的数据。
  • 准备一个主域名,支持 *.example.com 泛域名证书。

在群晖部署 certd

certd 官方提供了 Docker 部署方式,群晖上直接用 Container Manager 即可。

  1. 打开 Container Manager,新增项目,选择 docker-compose.yaml
  2. 建议使用下面的精简配置,并把数据目录改成你自己的卷路径。
version: "3.3"
services:
  certd:
    image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
    container_name: certd
    restart: unless-stopped
    volumes:
      - /volume1/docker/certd:/app/data:delegated
    ports:
      - "7001:7001"
      - "7002:7002"
    environment:
      - TZ=Asia/Shanghai
      - certd_koa_hostname=0.0.0.0
  1. 部署完成后访问 http://群晖 IP:7001,使用默认账号 admin/123456 登录,并第一时间修改密码。

在 certd 中申请泛域名证书


1. 添加腾讯云授权

进入 certd 的授权管理,新增腾讯云授权,填写 SecretIdSecretKey
这两个值可以复用你在 DDNS 配置时创建的密钥。

2. 创建证书申请流水线

进入证书流水线,新增 “证书申请(JS 版)”,按下面的方式填写:

  • 域名验证方式:DNS 直接验证。
  • 证书颁发机构:选择 “Let's Encrypt”。
  • DNS 解析服务商:腾讯云。
  • 证书域名:填写主域名和泛域名,例如 example.com*.example.com
  • 更新天数:按需设置(默认值以界面为准),表示证书到期前多少天触发更新。

保存后不需要手动运行,后面设置定时触发后会自动执行。
如果你希望尽快拿到证书,可以把定时触发时间设置为当前时间后几分钟,让它自动跑首轮。

如果失败,多半是 DNS 生效时间不够。可以在流水线里把 “等待解析生效时长” 适当调大,再试一次。

将证书输出到固定路径

为了让脚本能拿到证书文件,需要把证书保存到 NAS 上一个固定目录。certd 的流水线里加一个“主机 - 复制到本机”任务即可。

  1. 打开对应流水线,新增任务 “主机 - 复制到本机”。
  2. 证书类型选择 pem
  3. 路径建议这样填(相对路径会写到 /app/data 下):
    • 全链证书保存路径:tmp/fullchain.pem
    • 私钥保存路径:tmp/private.key
    • 中间证书保存路径:tmp/intermediate.pem

因为我在 docker-compose.yaml 里把 /volume1/docker/certd 映射到容器的 /app/data,所以上面三个文件最终会落在:

  • /volume1/docker/certd/tmp/fullchain.pem
  • /volume1/docker/certd/tmp/private.key
  • /volume1/docker/certd/tmp/intermediate.pem

你可以按自己的映射关系调整路径,但脚本里要用同一套路径。

用脚本导入证书到 DSM


(可选)为启用 2FA 的账户获取 device-id

如果你的 DSM 账户启用了两步验证(2FA),需要先获取一次 device-id,后续就可以用它来免 OTP 登录。

在你的本地电脑(或任何能访问 NAS 内网的设备)上执行以下命令,只需要执行一次(记得替换其中的参数):

curl -sS -k -G "https://192.168.1.10:5001/webapi/entry.cgi" \
  --data-urlencode "api=SYNO.API.Auth" \
  --data-urlencode "version=6" \
  --data-urlencode "method=login" \
  --data-urlencode "format=sid" \
  --data-urlencode "account=admin" \
  --data-urlencode "passwd=your-password" \
  --data-urlencode "otp_code=123456" \
  --data-urlencode "device_name=certd" \
  --data-urlencode "enable_device_token=yes" \
  --data-urlencode "enable_syno_token=yes" \
  | grep -oP '"device_id"\s*:\s*"\K[^"]+'

参数说明:

  • https://192.168.1.10:5001:你的 NAS 内网地址和端口
  • account:DSM 管理员账号
  • passwd:DSM 管理员密码
  • otp_code:从你的两步验证应用(如 Google Authenticator)获取的 6 位数字
  • device_name:自定义设备名称,用于在 DSM 中标识该设备,建议填 certd

命令会输出类似 aBcD1234eFgH5678 这样的字符串,这就是你的 device-id,记录下来供后续脚本使用。

如果命令执行失败或没有输出,可以检查:

  • OTP 码是否正确且未过期
  • 网络是否能访问到 NAS
  • 账号密码是否正确

准备导入脚本

首次使用前不需要手动准备证书记录。执行脚本时传入 --cert-desc,并加上 --create,脚本会在 DSM 里找不到同名描述时自动创建证书记录并导入证书。
如果你需要手动排查或验证,也可以在 DSM 里走 “新增 → 新增新证书 → 导入证书”,导入一次后再交给脚本更新。

DSM 自带的 Let's Encrypt 需要公网 80 端口可达,且通常不支持 DNS-01 与泛域名,不适合本方案。

把下面脚本保存到 NAS 上,例如 /volume1/docker/certd/scripts/synology-import-cert.sh,无需修改脚本内容,所有配置都通过参数传入。

该脚本同步保存在 Github 上:synology-import-cert.sh

脚本保存到本地后赋予其可执行权限:

chmod +x /volume1/docker/certd/scripts/synology-import-cert.sh

让流水线自动执行脚本

要做到全自动,需要让 certd 在证书更新后自动执行脚本。做法是给流水线增加一个 “主机 - 执行远程主机脚本命令” 任务。

  1. certd 的 “授权管理” 里新增 SSH 授权,主机填 NAS 地址,端口填你的 SSH 端口(可在 DSM 的 “控制面板 → 终端机和 SNMP → 终端机” 中查看,默认为 22),账号使用管理员账户。
    如果你之前没开 SSH,可以在 DSM 的 “控制面板 → 终端机和 SNMP → 终端机” 里开启。
  2. 回到证书流水线,在 “主机 - 复制到本机” 之后新增 “主机 - 执行远程主机脚本命令” 任务。
  3. 选择刚才的 SSH 授权,脚本命令填写下面的内容:

如果启用了 2FA,则在参数上带上之前获取到的 device-id

bash /volume1/docker/certd/scripts/synology-import-cert.sh \
  --base-url "https://192.168.1.10:5001" \
  --username "admin" \
  --password "your-password" \
  --cert-desc "nas-wildcard" \
  --fullchain "/volume1/docker/certd/tmp/fullchain.pem" \
  --key "/volume1/docker/certd/tmp/private.key" \
  --device-id "your-device-id" \
  --set-default \
  --assign-services "all" \
  --insecure \
  --create

参数说明:

  • 如果 DSM 证书尚未可信或域名不匹配,请保留 --insecure 参数。携带该参数后将跳过 HTTPS 证书校验,避免首次接入时因自签证书或域名不匹配而登录失败。
  • 如果你希望一次性覆盖全部服务,保留 --assign-services all;也可以指定具体服务名称,例如 "DSM Desktop Service,Reverse Proxy"

配置完成后,可以手动运行一次流水线测试。如果一切正常,证书会自动导入到 DSM。这样流水线每次拿到新证书后都会自动更新 DSM。

首次运行成功后,建议优化脚本配置:将 --base-url 改为你的域名,并去掉 --insecure 参数,这样可以正常校验 HTTPS 证书,更加安全:

bash /volume1/docker/certd/scripts/synology-import-cert.sh \
  --base-url "https://nas.example.com:5001" \
  --username "admin" \
  --password "your-password" \
  --cert-desc "nas-wildcard" \
  --fullchain "/volume1/docker/certd/tmp/fullchain.pem" \
  --key "/volume1/docker/certd/tmp/private.key" \
  --device-id "your-device-id" \
  --set-default \
  --assign-services "all" \
  --create

配置 Nginx 使用 SSL 证书

现在证书已经能通过 certd 自动管理了,接下来需要让 Nginx 使用这些证书来支持 HTTPS 访问。本文将基于 DSM 自带的 Nginx(参考第三篇文章)。

修改 Nginx 配置

DSM 自带的 Nginx 配置文件在 /etc/nginx/sites-enabled 目录下。

假设你在第三篇文章中为 calibre 配置了 calibre.example.com.conf,现在需要修改它以支持 HTTPS。

使用 SSH 连接到 NAS,编辑对应的配置文件:

sudo vim /etc/nginx/sites-enabled/calibre.example.com.conf

将原有的配置改为:

# HTTP 自动跳转到 HTTPS
server {
    listen 80;
    server_name calibre.example.com;
    return 301 https://$server_name$request_uri;
}

# HTTPS 配置
server {
    listen 443 ssl;
    server_name calibre.example.com;

    # SSL 证书配置
    ssl_certificate /volume1/docker/certd/tmp/fullchain.pem;
    ssl_certificate_key /volume1/docker/certd/tmp/private.key;

    # SSL 协议与加密套件
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
        proxy_pass http://172.17.0.1:8083;

        client_max_body_size 2000m;
    }
}

关键修改点:

  • 新增 80 端口的 server 块,自动跳转到 HTTPS
  • 原有的 server 块改为监听 443 端口,并启用 SSL
  • 使用 certd 导出的证书路径:/volume1/docker/certd/tmp/fullchain.pem/volume1/docker/certd/tmp/private.key
  • 新增 X-Forwarded-Proto header,让后端应用知道使用的是 HTTPS

重启 Nginx

保存配置文件后,测试配置并重启 Nginx 使配置生效:

sudo /usr/bin/nginx -c /etc/nginx/nginx.conf.run -t
sudo /usr/bin/nginx -c /etc/nginx/nginx.conf.run -s reload

验证 HTTPS 是否生效

用浏览器访问 https://calibre.example.com,检查:

  • 浏览器地址栏显示小锁图标。
  • 证书信息显示正确的域名和颁发机构。
  • 页面能正常加载,没有证书警告。

如果有问题,可以查看 Nginx 日志:

sudo tail -f /var/log/nginx/error.log

常见问题:

  • 证书路径不对:确认 /volume1/docker/certd/tmp/ 下有 fullchain.pemprivate.key 文件。
  • 权限问题:确保 Nginx 能读取证书文件,可以执行 sudo chmod 644 /volume1/docker/certd/tmp/*.pem /volume1/docker/certd/tmp/*.key
  • 证书未更新:certd 更新证书后,Nginx 需要重启才能重新加载证书。你可以在流水线的 “主机 - 执行远程主机脚本命令” 任务中,在导入脚本后面加一个重启命令:
bash /volume1/docker/certd/scripts/synology-import-cert.sh \
  --base-url "https://nas.example.com:5001" \
  --username "admin" \
  --password "your-password" \
  --cert-desc "nas-wildcard" \
  --fullchain "/volume1/docker/certd/tmp/fullchain.pem" \
  --key "/volume1/docker/certd/tmp/private.key" \
  --device-id "your-device-id" \
  --set-default \
  --assign-services "all" \
  --create

# 重启 Nginx 以重新加载证书
sudo /usr/bin/nginx -c /etc/nginx/nginx.conf.run -s reload

续期与维护

在创建 certd 流水线时,默认会有包含一个定时触发器,Let's Encrypt 证书有效期是 90 天。要实现全自动,只需要在流水线里配置定时触发即可。

certd 会在证书到期前按照 “更新天数” 设定的天数自动申请新证书并继续执行后续部署任务,没到期前不会重复申请。