lucky证书更新群晖自动同步

lucky证书更新群晖自动同步

夜影
2026-10-25 / 0 评论 / 2 阅读 / 正在检测是否收录...

引言

lucky证书更新群晖自动同步1.png

家里这套东西跑起来之后,有一个细节很容易被忽略:反代用的证书和群晖 DSM 用的证书,其实是同一张。Lucky 作为反代入口,负责申请和续签 Let's Encrypt 证书;DSM 的 Web 界面、群晖套件的 HTTPS 访问,走的也是这张证书。Lucky 续证很勤快,DSM 这边却不会自己跟着换——续证之后如果不把新证书同步过去,DSM 用的还是旧证书,拖到旧证书过期,浏览器就会开始报不安全。

所以需要一个同步机制:Lucky 证书一变,DSM 里的证书就跟着变。这篇文章记录的就是我最后定下来的方案:不按固定日期跑,改成每天检测证书指纹,变了才同步。

一、原来每三个月跑一次,为什么不好

最开始我用的是 DSM 任务计划里一个每三个月执行一次的任务,到日子就跑更新脚本。这个办法能用,但有两个别扭的地方。

第一,续证时间不固定。Let's Encrypt 证书有效期 90 天,Lucky 一般提前 30 天左右续签,也就是说证书实际变化的时间点,取决于 Lucky 的续签策略,而不是日历上的固定日期。按固定日期跑,运气好能赶上,运气不好就错过一轮。

第二,时间卡得太死。我当时把任务定在证书到期日当天晚上,等于一点缓冲都没有。万一那次执行出点问题(比如脚本报错、服务没重启),旧证书过期了都还没人发现。

后来我想明白了:与其算日子,不如直接盯着证书文件本身。文件变了,就说明续证发生了;文件没变,就什么都不用做。

二、更新脚本到底做了什么

同步的核心是一个更新脚本,它做四件事:

  1. 先把 DSM 里正在用的旧证书备份一份,带时间戳,存在 Lucky 的配置目录下;
  2. 把 Lucky 的新证书、新私钥复制到 DSM 的证书存档目录;
  3. 把证书和中间证书合并成 fullchain;
  4. 重启 DSM 的 nginx,让新证书生效。

这里有个坑,值得单独说一句:这个脚本是宿主机脚本,不能在 Lucky 容器终端里跑。容器里看到的 /app/conf,其实就是宿主机上的 /volume1/docker/lucky 映射进去的;而 DSM 的证书目录 /usr/syno/etc/certificate/_archive/... 根本没有挂载进容器,synosystemctl 也是宿主机才有的命令。在容器里执行,只会得到一堆"文件不存在"。所以更新脚本必须跑在宿主机上,DSM 任务计划的用户账号选 root 就是这个原因。

更新脚本全文如下(域名和存档目录换成自己的):

#!/bin/bash

# 证书源目录
CERT_SRC_DIR="/volume1/docker/lucky/ssl"
# 证书文件名,这里需要你按照你自己的情况修改,把你自己的域名填写在里面,比如上面你的域名填写的是*.qq.com,那么这你就填qq.com
CERT_FILE="$CERT_SRC_DIR/yeyhome.pem"
KEY_FILE="$CERT_SRC_DIR/yeyhome.key"
CHAIN_FILE="$CERT_SRC_DIR/yeyhome_issuerCertificate.crt"

# 群晖证书目标目录,这里需要你按照你自己的情况修改,一会在第三点里面会讲这个具体的位置是什么,每个人都不一样
CERT_DEST_DIR="/usr/syno/etc/certificate/_archive/9i9viz"

# 备份旧证书
BACKUP_DIR="/volume1/docker/lucky/ssl/backup/cert_backup"
mkdir -p "$BACKUP_DIR" # 创建备份目录
cp "$CERT_DEST_DIR/cert.pem" "$BACKUP_DIR/cert.pem.$(date +%Y%m%d%H%M%S)" # 备份证书
cp "$CERT_DEST_DIR/privkey.pem" "$BACKUP_DIR/privkey.pem.$(date +%Y%m%d%H%M%S)" # 备份私钥
cp "$CERT_DEST_DIR/fullchain.pem" "$BACKUP_DIR/fullchain.pem.$(date +%Y%m%d%H%M%S)" # 备份完整证书链

# 更新证书
cp "$CERT_FILE" "$CERT_DEST_DIR/cert.pem" # 复制新证书
cp "$KEY_FILE" "$CERT_DEST_DIR/privkey.pem" # 复制新私钥
cat "$CERT_FILE" "$CHAIN_FILE" > "$CERT_DEST_DIR/fullchain.pem" # 合并证书和中间证书

# 重启群晖 Web 服务
synosystemctl restart nginx # 重启 Nginx 使新证书生效

echo "证书已更新并重启服务。"

注意事项:

  • 存档目录因人而异:9i9viz 是 DSM 给这张证书分配的存档编号,每台机器、每张证书都不一样,不能照抄。用 SSH 看 /usr/syno/etc/certificate/_archive/ 下面哪个目录里的证书和你的域名对得上(可以用 openssl x509 -in 目录/cert.pem -noout -subject 看域名),就用哪个。
  • 文件名按自己域名改:yeyhome.pem、yeyhome.key、yeyhome_issuerCertificate.crt 换成你自己的证书文件名。
  • 必须用 root 跑:DSM 的证书存档目录只有 root 能写,DSM 任务计划的用户账号要选 root,普通账号连查看都会被拒绝。
  • 只能在宿主机上跑:不要在 Lucky 容器终端里执行,容器里没有 DSM 证书目录,也没有 synosystemctl 命令。
  • 别只看成功提示:这个脚本没有 set -e,中途某一步失败也不会停下,最后还是会打印“证书已更新并重启服务”。所以每次同步后都要核对 DSM 里证书的修改时间和指纹,确认真的换了。
  • 旧证书有备份:每次执行都会把旧证书、旧私钥、旧 fullchain 带时间戳备份到 ssl/backup/cert_backup,万一新证书有问题可以回滚。

三、新方案:每天检测指纹,变了才同步

lucky证书更新群晖自动同步2.png

新方案的思路很简单:每天凌晨 3 点跑一次检测脚本,比对 Lucky 证书文件的 md5 指纹。指纹和上次记录的一样,说明证书没变,直接退出,nginx 也不会被重启;指纹不一样,说明 Lucky 续证了,马上执行更新脚本,同步成功后才把新指纹写下来。如果更新失败,指纹不会被写死,第二天还会再试一次。

检测脚本全文如下:

#!/bin/sh
CERT=/volume1/docker/lucky/ssl/yeyhome.pem
STATE=/volume1/docker/lucky/.cert_last_md5
LOG=/volume1/docker/lucky/check_ssl.log
[ -f "$CERT" ] || exit 0
NEW=$(md5sum "$CERT" | awk '{print $1}')
OLD=$(cat "$STATE" 2>/dev/null)
if [ "$NEW" != "$OLD" ]; then
  if bash /volume1/docker/lucky/update_syno_cert.sh; then
    echo "$NEW" > "$STATE"
    echo "$(date '+%Y-%m-%d %H:%M') 证书变化,已同步" >> "$LOG"
  else
    echo "$(date '+%Y-%m-%d %H:%M') 证书变化,但更新脚本失败" >> "$LOG"
  fi
else
  echo "$(date '+%Y-%m-%d %H:%M') 证书未变,跳过" >> "$LOG"
fi

DSM 任务计划里的设置也很简单:任务名 check ssl,用户账号 root,计划每天 3 点,脚本直接粘贴到任务的脚本框里,不用单独存文件。第一次跑的时候还没有旧记录,会先同步一次并重启一次 nginx,这是正常的;之后证书没变就每天只记一行日志。

这里面有三个文件,分工很清楚:

  • yeyhome.pem:Lucky 维护的证书本体,是检测对象;
  • .cert_last_md5:上次同步时的证书指纹,是判断"变没变"的基准;
  • check_ssl.log:每次运行的记录,能看到哪天跳过、哪天同步。

四、怎么确认它真的在工作

lucky证书更新群晖自动同步3.png

搭好之后不用天天盯着,但第一次最好验证一下:

  1. 看日志:check_ssl.log 里应该有一行"证书未变,跳过",说明检测脚本已经跑通过;
  2. 看 DSM 证书:DSM 证书存档目录里 cert.pem 的修改时间,应该和 Lucky 证书同步的时间对得上;也可以直接比对两个文件的 md5,完全一致才算同步到位;
  3. 看任务状态:DSM 任务计划列表里,check ssl 的"上次运行时间"和"状态"每次跑完都会更新,状态是成功就说明没问题。

资源占用也不用担心。每天一次的检测,就是读一个 5KB 多的文件算个 md5、再读一个几十字节的状态文件,全程几十毫秒,没有常驻进程。真正"重"的重启 nginx,一年也就发生几次——只在证书真的续签了那天。

五、另外两条路为什么没选

其实还考虑过另外两个方案,最后都没用,顺便记一下,免得以后绕回来。

一个是 Lucky 的触发器 + Bark 推送:Lucky 续证后触发一条 Bark 消息,再由脚本检测到消息后执行更新。这个办法的问题是 Bark 只有推送接口,没有查询历史消息的接口,脚本没法直接"看到"消息;要实现就得去翻 Bark 容器的日志,可靠性差了一截。

另一个是 inotify 实时监听:守着证书文件,一变立刻执行。这是最实时的,但群晖默认没有 inotifywait,得另外装工具、搞常驻进程和开机自启,为 90 天才发生一次的事折腾这个,不值。

指纹检测算是折中得刚刚好:每天一次,延迟最多 24 小时,失败自动重试,没有常驻进程,也不依赖任何外部服务。

结尾

证书这种东西,平时感觉不到它的存在,出问题的时候却都是大事。把同步做成"变了才动、动了必记"的样子,之后就不用再算日子、不用再惦记续证时间,交给每天凌晨 3 点的那一次检测就行。

相关旧文:Lucky / FRP / ECH 三种内网穿透实测对比:延迟、成本、稳定性怎么选

0

评论 (0)

取消