news 2026/7/22 5:00:17

用群晖给 ESXi 自动续期 Let‘s Encrypt 证书:三个官方文档没写的坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用群晖给 ESXi 自动续期 Let‘s Encrypt 证书:三个官方文档没写的坑

目录

  • 环境
  • 为什么是「群晖跑 acme.sh + SSH 推送」
  • 遇到的坑
    • 坑一:ESXi 不认 ed25519 密钥
    • 坑二:`MULTI_CALL` 不够,必须开 `USE_SCP`
      • 这个坑真正危险的地方
    • 坑三:`auto-backup.sh` 会把私钥打进日志
  • 另外几个小提醒
  • 最终脚本
    • 群晖任务计划配置
  • 验证
  • 小结
  • 参考

家里那台 ESXi 6.7 的 HTTPS 证书过期一年多了,每次打开管理页面都得点"继续访问不安全的网站"。这次把它换成 Let’s Encrypt,并且让群晖每天自动检查续期。

整个过程踩了三个坑,都不在 acme.sh 的官方文档里,其中一个还差点让主机在重启后彻底连不上 Web 界面。这篇把过程和坑一起记下来。

环境

  • ESXi:6.7.0,内网192.168.1.111
  • 群晖:DSM 7.2.1,DS918+,负责跑 acme.sh 和定时任务
  • 域名esxi.example.com,NS 托管在 Cloudflare

为什么是「群晖跑 acme.sh + SSH 推送」

ESXi 本身跑不了 acme.sh(busybox 环境,且不该往 hypervisor 上塞东西),所以必须有一台机器负责签发再推过去。群晖 7×24 开机、有任务计划、能出网,是现成的选择。

推送方式上,一开始我打算自己写脚本:签发、传文件、重启服务。后来查文档才发现acme.sh 官方就有 ESXi 的部署配方,用通用的sshdeploy hook,文档里明确标注"tested with 6.7u3":

exportDEPLOY_SSH_USER="root"exportDEPLOY_SSH_SERVER="esxi.example.com"exportDEPLOY_SSH_KEYFILE="/etc/vmware/ssl/rui.key"exportDEPLOY_SSH_FULLCHAIN="/etc/vmware/ssl/rui.crt"exportDEPLOY_SSH_REMOTE_CMD="/etc/init.d/hostd restart"exportDEPLOY_SSH_MULTI_CALL="yes"acme.sh--deploy-desxi.example.com --deploy-hookssh

用上游钩子的好处很实在:分块传输、旧证书备份轮转、续期后自动重新部署,这些都是上游维护的,我只需要写一层很薄的入口脚本。

遇到的坑

下面是这个配方直接用会遇到的三个问题。

坑一:ESXi 不认 ed25519 密钥

部署钩子要求先交换 SSH 密钥。我图公钥字符串短,生成了 ed25519,装到/etc/ssh/keys-root/authorized_keys之后死活连不上:

root@192.168.1.111: Permission denied (publickey,keyboard-interactive)

文件内容对、权限对(-rw------Troot:root)、sshd_configPermitRootLogin yesAuthorizedKeysFile路径也对。查了半天配置文件毫无线索。

答案在 ESXi 的/var/log/auth.log里:

userauth_pubkey: key type ssh-ed25519 not in PubkeyAcceptedKeyTypes [preauth]

ESXi 的 sshd 是用 FIPS 版 OpenSSL 编译的OpenSSL 1.0.2s-fips),FIPS 验证的算法集里不含 Ed25519,所以编译期就把它从PubkeyAcceptedKeyTypes里排除了。这条在sshd_config里看不到,只能从日志发现。

结论:ESXi 只能用 RSA 密钥。

ssh-keygen-trsa-b2048-N""-C"dsm-acme-esxi"-f~/.ssh/esxi_deploy

顺带一提,证书本身也建议用 RSA 2048(acme.sh --issue--keylength 2048)。ESXi 6.7 的 hostd 对 ECC 证书支持有问题。

排查提示:遇到公钥登录失败,别在客户端反复试,直接看服务端的auth.log,一行日志顶半小时猜测。


坑二:MULTI_CALL不够,必须开USE_SCP

密钥通了,部署还是失败:

[...] will copy fullchain to remote file /etc/vmware/ssl/rui.crt /bin/sh: File too large [...] Error code 1 returned from ssh [...] Error deploying for domain: esxi.example.com

官方配方里的DEPLOY_SSH_MULTI_CALL="yes"就是为 busybox 准备的——文档原话是"command line buffer is not long enough"。但它只是把多个文件拆成多次 SSH 调用,单个文件仍然是一整条echo命令塞进去的。1.7KB 的私钥能过,5.6KB 的 fullchain 就超了。

解法是文档里另一个参数,改用 scp 传文件而不是塞命令行:

exportDEPLOY_SSH_USE_SCP="yes"exportDEPLOY_SSH_SCP_CMD="scp -q -i /path/to/key -o BatchMode=yes -o StrictHostKeyChecking=no"

ESXi 自带 scp,直接可用。

这个坑真正危险的地方

失败信息看着只是"部署没成功",但看一眼时间戳会发现问题严重得多:

-rw-r--r-- 1 root root 5649 Jul 19 09:33 /etc/vmware/ssl/rui.crt ← 旧证书 -rw-r--r-- 1 root root 1675 Jul 19 12:01 /etc/vmware/ssl/rui.key ← 新私钥

钩子先写成功了私钥,然后卡在证书上——主机被留在了 cert/key 不匹配的状态。

此时 HTTPS 表面完全正常,因为运行中的 hostd 用的是内存里已加载的旧证书对。但这是个定时炸弹:ESXi 的 crontab 每小时会跑一次auto-backup.sh/etc固化进 bootbank,之后任何一次 hostd 重启或主机重启,HTTPS 就直接起不来了。而这种时候你往往正需要 Web 界面去救。

所以动完 ESXi 证书,一定要验证配对,别只看服务还活着:

c=$(openssl x509-noout-modulus-in/etc/vmware/ssl/rui.crt|md5sum)k=$(openssl rsa-noout-modulus-in/etc/vmware/ssl/rui.key|md5sum)["$c"="$k"]&&echoMATCH||echoMISMATCH

好在 acme.sh 覆盖前会自动备份(DEPLOY_SSH_BACKUP默认开),把备份里的rui.key还原回去就恢复配对了。建议把DEPLOY_SSH_BACKUP_PATH指到 datastore 上,默认位置在 ESXi 的内存盘里,一重启备份自己就没了:

exportDEPLOY_SSH_BACKUP_PATH="/vmfs/volumes/datastore1/.acme_bak"

坑三:auto-backup.sh会把私钥打进日志

ESXi 的/etc是内存文件系统,改动要靠/sbin/auto-backup.sh固化到 bootbank。很自然地会想把它加进部署后的远程命令:

exportDEPLOY_SSH_REMOTE_CMD="/etc/init.d/hostd restart && /sbin/auto-backup.sh"

auto-backup.sh会把/etc的变更 diff 打到 stdout,其中包含rui.key的完整内容。

+MIIEowIBAAKCAQEA... +... -----END RSA PRIVATE KEY----- Saving current state in /bootbank

意味着每次自动续期,主机私钥都会被完整写进任务日志。日志文件权限往往比密钥文件宽松得多,等于白白降低了私钥的保护等级。

修复很简单,但必须记得

exportDEPLOY_SSH_REMOTE_CMD="/etc/init.d/rhttpproxy restart >/dev/null 2>&1 && /etc/init.d/hostd restart >/dev/null 2>&1 && /sbin/auto-backup.sh >/dev/null 2>&1"

补充两点:

  • 官方配方只重启hostd,但监听 443 的实际是rhttpproxy,我这边实测两个都重启才稳定生效,所以加上了。
  • auto-backup.sh其实不是必须的。ESXi 的 crontab 里本来就有1 * * * * /sbin/auto-backup.sh,每小时自动跑。显式调用只是让持久化立即完成,把窗口从"最多一小时"缩短到"立即"。装 SSH 公钥后同理。

另外几个小提醒

装 acme.sh 别只下单个脚本。curl那个acme.sh文件的话,dnsapi/deploy/两个目录不会跟过来,表现为Cannot find DNS API hook for: dns_cf。要下整个仓库:

curl-sL-oacme.tar.gz https://github.com/acmesh-official/acme.sh/archive/refs/heads/master.tar.gzmkdir-pacmesrc&&tarxzf acme.tar.gz-Cacmesrc --strip-components=1cdacmesrc&&./acme.sh--install--home~/.acme.sh--accountemailyou@example.com--nocron

群晖没有crontab命令,所以用--nocron,调度交给 DSM 的任务计划。另外 DSM 的/tmpnoexec,脚本别放那儿执行。

DEPLOY_SSH_SERVER填内网 IP,不要填域名。我这个域名对外解析到一台公网反向代理,填域名会把证书推到错误的机器上。用192.168.1.111直连。

CF 凭据用 API Token,不要用 Global API Key。Token 可以限定成只有Zone / DNS / Edit且只对单个域名生效;Global API Key 等同于账号全权限,能改所有 zone、账单甚至删域名。两者在 acme.sh 里都能用,没有任何理由选后者。

acme.sh 现在支持 ARI(ACME Renewal Information),续期时机由 CA 动态给出,不再是固定的"到期前 30 天":

ARI suggestedWindow: 2026-09-16T13:55:25Z to 2026-09-18T09:06:14Z Next renewal time picked from ARI window: 2026-09-17T07:40:54Z

最终脚本

放在群晖上,由任务计划每天调用一次。

配置文件acme-esxi.env(权限 600,含凭据):

exportDEPLOY_SSH_USER=rootexportDEPLOY_SSH_SERVER=192.168.1.111exportDEPLOY_SSH_KEYFILE=/etc/vmware/ssl/rui.keyexportDEPLOY_SSH_FULLCHAIN=/etc/vmware/ssl/rui.crtexportDEPLOY_SSH_MULTI_CALL=yesexportDEPLOY_SSH_USE_SCP=yesexportDEPLOY_SSH_BACKUP_PATH=/vmfs/volumes/datastore1/.acme_bakexportDEPLOY_SSH_CMD="ssh -i /var/services/homes/YOURUSER/.ssh/esxi_deploy -o BatchMode=yes -o StrictHostKeyChecking=no"exportDEPLOY_SSH_SCP_CMD="scp -q -i /var/services/homes/YOURUSER/.ssh/esxi_deploy -o BatchMode=yes -o StrictHostKeyChecking=no"exportDEPLOY_SSH_REMOTE_CMD="/etc/init.d/rhttpproxy restart >/dev/null 2>&1 && /etc/init.d/hostd restart >/dev/null 2>&1 && /sbin/auto-backup.sh >/dev/null 2>&1"exportCF_Token="你的_Cloudflare_API_Token"exportCF_Zone_ID="你的_Zone_ID"exportCF_Account_ID="你的_Account_ID"

入口脚本acme-esxi-renew.sh(权限 700):

#!/bin/bash# ESXi HTTPS 证书自动续期,由 DSM 任务计划调用# 退出码非 0 时任务计划会发邮件告警set-u# D = 脚本自身所在目录,配置和日志跟着脚本走,整个目录可随意搬动D="$(cd "$(dirname"$0")"&&pwd)" # H = acme.sh 的家目录 H="/var/services/homes/YOURUSER" ENV_FILE="$D/acme-esxi.env" ACME="$H/.acme.sh/acme.sh" LOG="$D/acme-esxi.log" DOMAIN="esxi.example.com" ESXI="192.168.1.111" MIN_DAYS=20 log() { echo "[$(date'+%F %T')]$*" >>"$LOG"; } die() { log "FAIL:$*"; exit 1; } # 日志超过 1MB 时轮转 [ -f "$LOG" ] && [ "$(wc-c<"$LOG")" -gt 1048576 ] && mv "$LOG" "$LOG.old" log "===开始===" [ -f "$ENV_FILE" ] || die "配置文件缺失:$ENV_FILE" . "$ENV_FILE" # --cron 只在 ARI 建议的窗口内才真正续期,平时空跑 out=$("$ACME" --cron --home "$H/.acme.sh"2>&1)rc=$?# 过滤证书/私钥正文,防止敏感内容落进日志 echo "$out" | grep -viE 'BEGIN |END |^\+|^[A-Za-z0-9+/=]{40,}$' >>"$LOG" [$rc-eq 0 ] || die "acme.sh 退出码$rc" # 独立验证:直连 443 抓线上真实证书,而不是相信 acme.sh 的返回值。 # 能抓到「续期成功但没部署上去」和「部署了但服务没重启」两类静默故障。 live=$(echo|openssl s_client-connect"$ESXI:443"-servername"$DOMAIN"2>/dev/null|openssl x509-noout-serial2>/dev/null)mine=$(openssl x509-in"$H/.acme.sh/$DOMAIN/fullchain.cer"-noout-serial2>/dev/null)[ -n "$live" ] || die "无法从$ESXI:443 取得证书,ESXi 可能不可达" [ -n "$mine" ] || die "本地证书文件读取失败" [ "$live" = "$mine" ] || die "线上证书与本地不一致(线上$live/ 本地$mine),部署未生效" # 剩余天数兜底:即使上面都过,天数不足也说明续期逻辑没在按预期工作 end=$(echo|openssl s_client-connect"$ESXI:443"2>/dev/null|openssl x509-noout-enddate|cut-d=-f2)days=$(((($(date-d "$end"+%s)-$(date+%s))/ 86400))) [ "$days" -ge "$MIN_DAYS" ] || die "线上证书仅剩$days天(阈值$MIN_DAYS),续期未按预期工作" log "===正常:$live,剩余$days==="exit0

这个脚本的重点不是调 acme.sh,而是后面那两段独立校验。

acme.sh --cron返回 0 只说明它自己没报错,说明不了证书真的在对外服务。上面坑二里那种"私钥写了、证书没写、服务还活着"的状态,acme.sh 报的是失败,但如果失败发生在重启服务之后呢?所以脚本跑完会直连 443 抓真实握手的证书序列号,和本地文件比对,再查一遍剩余天数。任何一步不过就退出码 1,让任务计划发邮件。

群晖任务计划配置

控制面板 → 任务计划 → 新增 → 计划的任务 → 用户定义的脚本:

  • 用户:普通用户即可,不需要 root(脚本只需要出网和一把 SSH 密钥)
  • 计划:每天一次
  • 命令:脚本的绝对路径
  • 勾选「异常时通过电子邮件发送运行详情」——这是唯一的告警出口

验证

别只看"没报错"就认为成了。至少验证这几项:

# 1. 线上实际提供的证书(直连 443 抓真实握手,不是看文件内容)echo|openssl s_client-connect192.168.1.111:443-servernameesxi.example.com2>/dev/null\|openssl x509-noout-subject-issuer-dates-serial# 2. 证书链是否完整可信echo|openssl s_client-connect192.168.1.111:4432>&1|grep"Verify return code"# 期望:Verify return code: 0 (ok)# 3. 序列号与本地签发的是否一致openssl x509-in~/.acme.sh/esxi.example.com/fullchain.cer-noout-serial

比对序列号而不是只看有效期——有效期相同的两张证书完全可能是不同的两张。

另外建议测一次失败路径。复制一份脚本、把ESXI改成一个不可达的 IP 再跑,确认它确实退出码 1 并记录了错误。

小结

三个坑按踩到的顺序:

  1. ESXi 只认 RSA 公钥,ed25519 会被 FIPS 编译的 sshd 拒绝,证据在服务端auth.log
  2. 必须开DEPLOY_SSH_USE_SCP,只靠MULTI_CALL会在 fullchain 上失败,并留下 cert/key 不匹配的主机
  3. auto-backup.sh的输出要重定向掉,否则每次续期都把主机私钥写进日志

真正花时间的不是配置本身,而是第一个坑——公钥文件内容、权限、路径全都正确却连不上,光看配置文件永远找不到原因。远程服务的日志比本地的猜测有用得多。

参考

  • acme.sh deployhooks wiki
  • acme.sh 项目主页
  • Configuring CA signed certificates for ESXi hosts(Broadcom KB)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/20 18:41:09

ReAct 和 Plan and Solve 理解

1、区别的理解例子&#xff1a;预订一次从北京到上海的商务旅行&#xff0c;包括机票、酒店、租车它们之间存在依赖&#xff1a;航班到达机场↓ 影响酒店位置↓ 影响是否需要租车↓ 影响取车地点和时间还要同时考虑&#xff1a;出发和返程时间&#xff1b;会议时间&#xff1b;…

作者头像 李华
网站建设 2026/7/20 18:39:55

鸿蒙Flutter Center与Align:组件对齐方式

作者&#xff1a;马振显&#xff08;YM52e&#xff09; 仓库地址&#xff1a;https://gitcode.com/feng8403000/FlutterfromBeginnertoAdvancedForHarmonyOS.git 联系邮箱&#xff1a;372699828qq.com 引言 在Flutter开发中&#xff0c;Center和Align是两个用于控制子组件对齐…

作者头像 李华
网站建设 2026/7/20 18:39:07

如何用Path of Building 2精准规划PoE2角色构建?3大核心功能深度解析

如何用Path of Building 2精准规划PoE2角色构建&#xff1f;3大核心功能深度解析 【免费下载链接】PathOfBuilding-PoE2 项目地址: https://gitcode.com/GitHub_Trending/pa/PathOfBuilding-PoE2 你是否曾花费数小时计算角色的伤害输出&#xff0c;却发现实际游戏中的表…

作者头像 李华
网站建设 2026/7/20 18:35:59

逆变器芯片失效分析与防护设计实践

1. 故障现象深度解析&#xff1a;小芯片失效引发的逆变器级联损坏当逆变器中的控制芯片发生击穿短路时&#xff0c;往往会导致灾难性的级联故障。具体到本次案例&#xff0c;故障发展路径非常典型&#xff1a;控制芯片首先因过压或过流发生物理性烧毁&#xff0c;其失效产生的异…

作者头像 李华