
简介win-acme.v2.2.9.1701.x64.pluggable.zip 面向 Windows 平台网站管理员与系统集成商用于简化 Lets Encrypt 等免费 SSL 证书的申请、部署与续订可配合 IIS、nginx 等 Web 服务器使用缓解官方下载缓慢的问题。压缩包共 28 个文件大小约 35.58MB以 21 个 PowerShell 脚本和 2 个批处理文件为主另含可执行程序、配置文件、域名列表及 Web 配置示例便于按不同服务场景调用自动化脚本。现有 392 人学习下载说明其在证书自动化管理方向具有一定参考价值。读者可从中获得可运行的工具核心、默认配置模板与多类导入脚本用于快速搭建证书签发流程、调整参数并接入现有服务器环境减少手工申请和续期带来的重复操作。1. 证书到期那天才发现没人管win-acme 在 Windows nginx 上到底解决什么一台 Windows 机器上跑着 nginx前台证书是当初手工贴上去的到期日只记在某个人脑子里——这种情况我碰到过不止一次。win-acme v2.2.9.1701 x64 pluggable 要做的事很具体在 Windows 上向 Lets Encrypt 申请免费 SSL 证书把签下来的文件按 nginx 能读的格式落地再挂一个计划任务定期续期。听着不复杂真装起来坑不少pluggable 包不自带运行时.NET 版本对不上直接闪退80 端口被 nginx 自己占着验证方式选错必然失败签下来的是 pfxnginx 根本不认续期成功了但没人 reload站点照样报过期。这篇按我自己的装机顺序走一遍从版本选择、运行时补齐、首次签发、接进 nginx一直到强制续期演练每一步都给可抄的命令和参数。2. 装之前把环境列清pluggable 包、.NET 运行时与目录权限这一章不做任何签发动作只把「装在哪、依赖什么、谁来读文件」这三件事定死。我见过太多人卡在第一步双击 wacs.exe 弹个黑框就没了然后开始怀疑包是坏的。九成情况是运行时没装或者装错架构。2.1 pluggable 和 self-contained 的区别先认清手里这个 zipwin-acme 的发行包一直有两种形态文件名里就写着。带pluggable的是「可插拔」版程序本体不含 .NET 运行时体积小启动时去系统里找已安装的运行时另一种通常标self-contained把运行时一起打包进去体积大几十兆但拷过去就能跑。你这儿拿到的是win-acme.v2.2.9.1701.x64.pluggable.zipx64是架构pluggable是形态两个信息都不能忽略。对比项pluggableself-contained依赖需预装 .NET Desktop Runtime x64无外部运行时依赖包体积小大含运行时升级方式换 exe 即可运行时不动整包替换适用场景服务器上已有对应 .NET 运行时干净机器、离线交付、不想动系统运行时我一般优先选 pluggable服务器上往往已经因为别的服务装过 .NET不重复占空间而且以后换 win-acme 版本只要替换目录运行时那层不用碰。代价是版本必须对齐——这也是下一节要处理的。如果目标机器是完全干净的、或者运维规范不允许往系统里装运行时直接换 self-contained 包别在这上面耗时间。判断依据很简单先看服务器上有没有对应主版本的 .NET Desktop Runtime有就走 pluggable没有且不方便装就换包。2.2 解压路径、架构对齐与目录权限路径这件事看着琐碎实际是两个坑的源头。第一别放在C:\Users\某某\Downloads下面。win-acme 的计划任务是以 SYSTEM 账户跑的用户目录下的路径在 SYSTEM 上下文里可能根本访问不到续期任务会静默失败。第二路径里不要有空格和中文钩子脚本、nginx 配置、计划任务参数三层转义下来一个空格就能让整条链路断掉。我固定的做法是解到C:\win-acme解压后目录里应该能看到wacs.exe、若干*.dll、*.json配置文件。确认一下 exe 的架构# 查看 wacs.exe 的目标架构确认和系统对齐 $p C:\win-acme\wacs.exe $fs [IO.File]::OpenRead($p) $br New-Object IO.BinaryReader($fs) $fs.Seek(0x3C, Begin) | Out-Null $peOffset $br.ReadInt32() $fs.Seek($peOffset 4, Begin) | Out-Null $machine $br.ReadUInt16() $br.Close(); $fs.Close() switch ($machine) { 0x8664 { x64 版本可装在 64 位系统 } 0x014c { x86 版本只能装在 32 位系统 } default { 未知架构: 0x{0:X} -f $machine } }这段读的是 PE 头里的 Machine 字段0x8664对应 x640x014c对应 x86。作用只有一个确认下载的包和系统架构一致。64 位系统装 x86 版不是完全跑不起来但一旦涉及证书存储、IIS 插件这些要读系统证书库的操作就会出现「明明装了却读不到」的玄学现象。权限方面分两块。C:\win-acme本身给 Administrators 和 SYSTEM 完全控制就行普通用户不需要写权限。真正要小心的是证书输出目录——后面 PEM 文件会落在那里nginx 进程要能读私钥。如果 nginx 是用 nssm 之类的工具注册成服务、以 LocalSystem 身份跑的默认权限够用如果为了降低风险改用某个低权限账户跑 nginx就必须显式给那个账户授读取权限否则 nginx 启动时报cannot load certificate key日志里只给你一行Permission denied。注意证书输出目录绝对不要放在 nginx 的站点根目录比如C:\nginx\html下面。私钥文件一旦落在 web 根目录理论上可以被当成静态文件下载走这是实打实的安全事故不是理论风险。2.3 把 .NET Desktop Runtime 补齐版本、架构、验证三步v2.2.x 这一代的 pluggable 包编译在 .NET 6 上需要的是.NET Desktop Runtime注意不是 ASP.NET Core Runtime也不是最基础的 .NET Runtime。win-acme 里用到 WinForms 做证书选择、对话框这类交互缺了 Desktop 那一层启动时就会报框架缺失。最省事的判断方式其实不是查文档而是直接双击wacs.exe。缺运行时的时候控制台窗口会打印类似You must install .NET to run this application或者Framework Microsoft.WindowsDesktop.App, version 6.0.0 was not found的信息里面会明确写出需要的主版本号和架构。按那行提示去装对应版本就行比猜准。装之前先看机器上已经有什么# 列出已安装的 .NET 运行时重点看 Microsoft.WindowsDesktop.App 的主版本 dotnet --list-runtimes # 如果 dotnet 命令不存在说明连运行时都没装直接去装 Desktop Runtime # 装完后再次执行期望看到类似 # Microsoft.WindowsDesktop.App 6.0.x [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]dotnet --list-runtimes输出的每一行是「框架名 版本 [安装路径]」。要找的是Microsoft.WindowsDesktop.App并且主版本号6.x / 8.x要和 wacs.exe 要求的对上。次版本号6.0.25 还是 6.0.36不影响运行时向前兼容同一主版本内的补丁。架构上必须装 x64和 exe 保持一致混装 x86 运行时时dotnet --list-runtimes会多列出一组[Program Files (x86)]路径下的条目容易看花眼。验证是否装对别只看安装程序说成功直接跑一次# 只打印版本和帮助不触发任何证书操作 C:\win-acme\wacs.exe --version能正常打印版本号说明运行时这层通了。这一步过不去后面所有章节都不用看。3. 第一次签发用命令行把 HTTP-01 验证跑通环境通了之后进入签发。我的习惯是先用命令行而不是向导——向导点错一步要重来一遍命令行改个参数重跑就行而且命令能直接存档成文档。3.1 先拿 --test 跑一遍把参数拼写和域名解析验证掉win-acme 支持--test作用是走 Lets Encrypt 的测试环境签出来的证书浏览器不信任但整个验证、签发、落地的流程和正式环境完全一致。第一次装必须走这个原因很实际Lets Encrypt 对正式环境有频率限制同一域名一周内重复签发次数超了会被临时封参数拼错重试几次就可能撞上。测试环境宽松得多随便折腾。完整命令长这样C:\win-acme\wacs.exe ^ --target manual ^ --host example.com,www.example.com ^ --validation filesystem ^ --webroot C:\nginx\html ^ --store pemfiles ^ --pemfilespath C:\certs ^ --accepttos ^ --emailaddress opsexample.com ^ --test逐段说清楚--target manual表示手动指定域名不让程序自己去扫 IIS 站点。Windows 上如果装了 IISwin-acme 默认会去枚举 IIS 绑定扫出来的域名跟你真正想签的未必一致显式写 manual 更可控。--host后面跟域名列表逗号分隔不要空格。第一个域名会成为证书的主名Subject其余进 SAN 扩展。www和裸域写在一起签一张证书省一次验证。--validation filesystem指定验证方式为「写文件」win-acme 会把验证文件写进--webroot指定的目录下的.well-known\acme-challenge\然后由 Lets Encrypt 从公网访问http://你的域名/.well-known/acme-challenge/随机串来校验。这是和 nginx 共存时最稳的方式原因下一节展开。--store pemfiles是最关键的一个参数指定证书落地格式为 PEM 文件而不是 pfx。--pemfilespath是输出目录。--accepttos表示接受服务条款非交互运行时必须带否则卡在确认提示上。--emailaddress填到期提醒邮箱别填 noreply 之类的假地址证书快过期时的提醒邮件是真的有用。参数名以wacs.exe --help的输出为准v2.2.x 里这套拼写是能用的。第一次跑建议加--test等整条链路包括钩子脚本都验证通过把--test去掉再跑一次拿正式证书。3.2 filesystem 验证与 nginx 的 /.well-known 放行验证方式一共有几种Windows nginx 这个组合下要避开selfhosting。它的原理是 win-acme 自己起一个临时 HTTP 监听去响应验证请求问题是 80 端口已经被 nginx 占着——绑不上直接失败。报错大概是Failed to bind to port 80或者验证超时第一次见容易误判成防火墙问题。filesystem反过来它不抢端口只往一个目录里扔文件剩下的交给已经在跑的 nginx。所以 nginx 那边必须保证两件事/.well-known/acme-challenge/这个路径能访问到C:\nginx\html下的真实文件这个路径不能被重定向到 https。server { listen 80; server_name example.com www.example.com; # ACME 验证目录必须直出静态文件优先级要高于下面的 301 location ^~ /.well-known/acme-challenge/ { root C:/nginx/html; default_type text/plain; try_files $uri 404; } # 其余请求照常跳 https location / { return 301 https://$host$request_uri; } }location ^~的前缀匹配优先级高于正则和普通前缀这样即使别处写了location /做跳转验证路径也不会被带走。root C:/nginx/html里的路径用正斜杠nginx 在 Windows 上认这个写法反斜杠反而容易出问题。default_type text/plain是保险某些精简过的 mime.types 不认识无扩展名文件返回的 Content-Type 不对时验证端可能拒收。try_files $uri 404让文件不存在时老实返回 404而不是绕到别的 location 去。改完配置先nginx.exe -t检查语法再nginx.exe -s reload生效。然后手工造一个测试文件验证链路# 在验证目录下放一个探针文件 probe-ok | Out-File -Encoding ascii C:\nginx\html\.well-known\acme-challenge\probe.txt # 从本机访问确认返回 probe-ok 而不是 301 或 404 Invoke-WebRequest -Uri http://example.com/.well-known/acme-challenge/probe.txt -MaximumRedirection 0第二个命令用-MaximumRedirection 0一旦发生跳转会直接抛异常比自动跟随重定向更容易发现问题。返回内容里有probe-ok就说明链路通了可以把探针文件删掉。这一步能挡掉后面大概一半的失败。3.3 落地成 PEM输出文件长什么样私钥为什么不加密--store pemfiles会在--pemfilespath目录下生成一组文件文件名前缀默认是主域名。我这边实际会看到这些东西文件名内容nginx 用不用example.com-chain.pem站点证书 中间证书按顺序拼好用配给ssl_certificateexample.com-key.pem私钥用配给ssl_certificate_keyexample.com-crt.pem只有站点证书不含中间证书一般不用example.com-chain-only.pem只有中间证书排查链问题时用重点是别把-crt.pem配给 nginx。少了中间证书桌面浏览器往往能靠缓存或者 AIA 自动补齐看起来一切正常但移动端、微信内置浏览器、部分 Java 客户端会直接报证书链不完整。这是那种「我这儿好好的啊」的经典翻车场景排查起来能耗掉半天。配-chain.pem就不会有这个问题。私钥加密这块win-acme 在向导里会问要不要给私钥加密码。走命令行pemfiles存储时不涉及交互默认落的是明文私钥。nginx 这边确实也支持加密私钥但每次 reload 都要交互输入密码作为服务跑的时候直接卡死。所以我的做法是私钥不加密改用文件系统 ACL 控制访问——把C:\certs的权限收紧到 SYSTEM、Administrators 和 nginx 运行账户三个主体其他一律不给。4. 把证书接进 nginx钩子脚本、reload 与续期计划任务证书签下来只是半程。这一章解决「签完之后自动有人管」的问题否则三个月后照样过期。4.1 钩子脚本让 win-acme 签完就调用你win-acme 提供--script和--scriptparameters两个参数前者指定一个可执行文件或脚本后者传参。证书签发或续期成功后它会把参数替换成实际值再调用这个脚本。最常用的占位符有几个{CacheFile}是 pfx 格式的缓存文件路径{StorePath}是 PEM 输出目录{RenewalId}是续期任务编号{SubjectCommonName}是证书主域名。完整列表在--help里能看到版本之间有细微差异以本机输出为准。我一般写一个 .bat 而不是 .ps1。原因是 PowerShell 脚本受执行策略限制计划任务以 SYSTEM 跑的时候执行策略、模块路径都可能和你在交互式终端里不一样多了两层不确定性。bat 虽然土但没有这些问题。echo off setlocal enabledelayedexpansion set NGINX_DIRC:\nginx set LOG_FILEC:\certs\deploy.log echo [%date% %time%] deploy start, args%* %LOG_FILE% rem nginx 用相对路径找 conf 和 pid必须先切到安装目录 cd /d %NGINX_DIR% || ( echo [%date% %time%] cd failed %LOG_FILE% exit /b 1 ) rem 先做配置检查配置有问题就不要 reload避免把在跑的进程搞挂 nginx.exe -t %LOG_FILE% 21 if errorlevel 1 ( echo [%date% %time%] nginx -t failed, skip reload %LOG_FILE% exit /b 1 ) nginx.exe -s reload %LOG_FILE% 21 if errorlevel 1 ( echo [%date% %time%] reload failed %LOG_FILE% exit /b 1 ) echo [%date% %time%] deploy done %LOG_FILE% exit /b 0几个细节值得说。cd /d %NGINX_DIR%这行不能省nginx 在 Windows 上是用编译时前缀加上相对路径去找conf/nginx.conf和logs/nginx.pid的如果计划任务在C:\Windows\System32下调用它找不到 pid 文件-s reload就变成一个静默的空操作退出码还是 0。这是最阴的一种失败——日志里一切正常证书就是不生效。nginx.exe -t放在 reload 前面是先验证再动作。配置文件写错了直接 reloadnginx 会拒载新配置继续用旧的表面上「没崩」实际上新证书没生效你以为搞定了其实没有。先-t拿到明确失败信号比事后猜强。日志一定要写。计划任务里的脚本出错是没有任何界面的不落日志等于黑匣子。%*把 win-acme 传进来的占位符参数原样记下来排查时能确认参数替换是否符合预期。调用的时候这样拼C:\win-acme\wacs.exe ^ --renew ^ --force ^ --script C:\certs\deploy-nginx.cmd ^ --scriptparameters {RenewalId} {SubjectCommonName}--renew表示走续期流程--force强制立即执行而不是等到临期。这两个参数只用来演练和验证日常续期不需要--force。--scriptparameters里的占位符会被替换成真实值再传给脚本脚本里用%1%2接。我这里其实没在脚本里用这两个参数纯粹是记进日志方便追溯是哪一次续期触发的部署。4.2 nginx.conf 里两个 ssl_certificate 的落点证书文件就位之后改 443 的 server 块server { listen 443 ssl; http2 on; server_name example.com www.example.com; # 必须用 chain 文件站点证书 中间证书不要用 crt ssl_certificate C:/certs/example.com-chain.pem; ssl_certificate_key C:/certs/example.com-key.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; location / { root C:/nginx/html; index index.html; } }路径统一用正斜杠nginx 在 Windows 上能正确处理。ssl_certificate指向-chain.pem原因前面说过——它包含站点证书和中间证书客户端能拿全整条链。ssl_certificate_key指向-key.pem两个文件必须配对混用旧版本的私钥和新版本的证书会导致 nginx 启动失败报key values mismatch这个报错信息算比较友好的一眼能看懂。http2 on;是 nginx 1.25.1 之后的新写法老版本写listen 443 ssl http2;。版本差异这里容易翻车nginx -v先确认一下自己跑的是哪个版本再抄配置否则-t会直接报unknown directive http2。改完照例先检查cd /d C:\nginx nginx.exe -t nginx.exe -s reload-t的输出会明确告诉你配置语法有没有问题、证书文件能不能读到。这一步通过443 就基本能起来了。4.3 续期计划任务别让两套任务互相打架win-acme 默认会在 Windows 任务计划程序里创建一个续期任务名字大概是win-acme renew (acme-v02.api.letsencrypt.org)触发时间每天固定跑一次每次运行时检查有没有证书进入续期窗口一般是到期前 30 天左右有就续没有就退出。这个机制本身够用不用你再写一个。先确认它在不在# 查 win-acme 创建的计划任务确认存在且状态为 Ready Get-ScheduledTask | Where-Object { $_.TaskName -like *win-acme* } | Select-Object TaskName, State, {nRunAs;e{$_.Principal.UserId}} # 看最近一次执行结果和下次运行时间 Get-ScheduledTaskInfo -TaskName win-acme renew (acme-v02.api.letsencrypt.org)State是Ready表示任务正常待命。RunAs一般是SYSTEM这意味着脚本里所有相对路径、用户环境变量、映射的网络驱动器都不可用——这就是 4.1 里那句cd /d的必要性来源。Get-ScheduledTaskInfo的LastTaskResult是 0 表示上次跑成功了非 0 就得去看 win-acme 自己的日志。如果你有统一的任务编排系统想把续期收归到自己的调度里就在签发和续期命令里都加上--notaskschedulerwin-acme 就不会再自己建任务。关键是别两边都留着。两套任务同时跑撞在一起的时候一边在写证书文件另一边在 reload nginx运气不好就 reload 到一个写了一半的文件nginx 直接起不来。这种事故我见过一次之后就把这条当成硬规矩要么用自带的要么用自己的绝不允许并存。win-acme 自己的执行日志默认落在wacs.exe同级的Log目录下按日期分文件。续期失败的第一现场永远在那里看LastTaskResult不如直接翻日志来得快。5. 排查与避坑五条踩过的记录这一章都是真踩过的按「现象 → 原因 → 解决」写遇到对得上的直接抄解决方案。5.1 80 端口被占用验证直接超时现象命令跑起来之后停在验证阶段等一两分钟报验证失败提示大概是连接超时或者 refused。防火墙、安全组都查过了公网访问http://域名/是通的。原因--validation选成了selfhosting。这种方式要 win-acme 自己监听 80 端口但 80 已经被 nginx 占着绑不上绑不上就没法响应验证请求验证端只能超时。解决改成--validation filesystem配合--webroot指向 nginx 站点根目录让 nginx 去服务验证文件。如果因为某些原因必须用自托管验证那就得错峰——先停 nginx跑签发再起 nginx。生产上这么干显然不合适所以还是 filesystem。5.2 验证请求被 301 跳到 https一直失败现象探针文件在本机用curl http://127.0.0.1/.well-known/acme-challenge/probe.txt能拿到但用域名访问就被跳到 https验证始终过不去。原因站点为了全站 https写了一条return 301 https://$host$request_uri;的兜底规则把/.well-known/也一起带走了。Lets Encrypt 的验证器不跟随跨协议跳转看到 301 直接判定失败。解决在 80 的 server 块里把/.well-known/acme-challenge/的 location 放到跳转规则前面并用^~修饰符保证前缀匹配优先。这个改动只影响验证路径其他请求照常跳转对 SEO 和用户无感。5.3 钩子脚本在交互终端里能跑计划任务里静默失败现象手工执行一次wacs.exe --renew --force一切正常证书更新、nginx reload 都成功。等计划任务触发的时候证书文件确实更新了但 nginx 用的还是旧的站点显示的到期时间没变。原因计划任务以 SYSTEM 身份在C:\Windows\System32下启动脚本脚本里写的是相对路径调用nginx.exe -s reload。nginx 找不到 pid 文件reload 变成空操作退出码还是 0所以从任务计划的结果看是「成功」。解决脚本开头强制cd /d C:\nginx用绝对路径调用nginx.exe。另外把每次执行的输出和退出码都追加到日志文件出问题时有据可查。从那以后我写这类钩子脚本第一行一定是切目录第二行一定是开日志。5.4 续期成功但 nginx 还在用旧证书现象日志里 win-acme 明确写了续期成功C:\certs下的文件修改时间也是新的但浏览器里看证书还是旧的重启 nginx 之后就正常了。原因--script参数压根没配或者早期配了后来换版本时漏掉了。win-acme 只管把文件写下来它不知道谁在用这些文件更不会主动去通知 nginx。解决补上--script和--scriptparameters。已经存在的续期任务可以用--renew --force重新跑一次把钩子挂上去。配完之后务必做一次强制续期演练确认从「文件更新」到「nginx reload」整条链是通的别等到真过期那天才发现钩子没挂。5.5 私钥权限过宽或加密nginx 起不来现象nginx -t报cannot load certificate key C:/certs/example.com-key.pem后面跟Permission denied或者bad decrypt。原因两种可能。一是 nginx 以低权限账户运行C:\certs目录没给这个账户读权限二是私钥被加了密码nginx 启动时没有交互界面输入密码。解决权限问题用icacls明确授权只给必要的主体# 关掉继承只保留 SYSTEM、Administrators 和 nginx 运行账户 icacls C:\certs /inheritance:r icacls C:\certs /grant SYSTEM:(OI)(CI)R icacls C:\certs /grant Administrators:(OI)(CI)F icacls C:\certs /grant NGINX_SVC:(OI)(CI)R/inheritance:r移除从上级目录继承来的 ACE避免某个宽松的父目录权限把私钥暴露出去。(OI)(CI)表示对象继承和容器继承让目录下新建的证书文件自动带上同样权限这样每次续期生成新文件不用重新授权。私钥加密的问题只能重新签发一次落地时不加密。6. 装完怎么验证跑一次强制续期演练才算完证书装上、nginx 起来了、浏览器小锁出现了这些都不算装完。真正要验证的是「三个月后它会不会自己续上」。这件事唯一的验证方式就是手动触发一次完整的续期流程然后检查每一环的产物。第一步强制续期C:\win-acme\wacs.exe --renew --force--force会跳过「是否临近过期」的判断立刻走一遍完整的签发、落地、调用钩子。命令结束后先看C:\certs\deploy.log里面应该有本次的时间戳、nginx -t的输出和 reload 的结果。日志里出现deploy done才算钩子那一段通了。第二步确认证书文件确实是新的Get-ChildItem C:\certs\*.pem | Select-Object Name, Length, LastWriteTimeLastWriteTime应该是刚才那一分钟。如果时间没变说明 win-acme 那一段就没成功回头去看C:\win-acme\Log下当天的日志。第三步从外部视角看站点实际在用的证书。本地文件对不代表线上对# 抓取 443 上真实返回的证书看颁发者和到期时间 echo | openssl s_client -connect example.com:443 -servername example.com 2/dev/null \ | openssl x509 -noout -subject -issuer -dates输出里notAfter应该是刚续的新日期。如果还是旧日期说明 nginx reload 没生效或者 reload 到了旧配置回到 5.3 和 5.4 排查。没有 openssl 环境的话用 PowerShell 也行# 不跟随跳转直接读 TLS 握手拿到的证书 $req [Net.HttpWebRequest]::Create(https://example.com) $req.AllowAutoRedirect $false $req.Timeout 8000 $req.GetResponse() | Out-Null $cert $req.ServicePoint.Certificate [PSCustomObject]{ Subject $cert.Subject Issuer $cert.Issuer Expires $cert.GetExpirationDateString() }第四步把这张表从头到尾对一遍任何一项没打勾都别急着收工检查项期望结果不通过时看哪里wacs.exe --version正常打印版本号.NET Desktop Runtime 是否装对架构和主版本探针文件 HTTP 访问返回文件内容无跳转nginx 的/.well-knownlocation 优先级C:\certs下 PEM 文件-chain.pem和-key.pem齐全且时间新存储参数是否写成了 pemfilesnginx -tsyntax is ok, test is successful证书路径、http2指令版本差异deploy.log有本次时间戳和deploy done钩子脚本路径、执行账户权限外部抓取证书notAfter为最新日期reload 是否真的执行了计划任务状态ReadyLastTaskResult为 0任务是否重复创建、账户上下文最后还有一件容易忽略的事把--test留下的痕迹清掉。测试环境签的证书浏览器不信任如果之前用--test跑过正式域名得确认C:\certs下现在放的是正式证书而不是测试证书最直接的判断是看颁发者——测试环境的颁发者名字里带 staging 字样。我一般会在正式签发完成后直接从外部抓一次证书确认颁发者避免把测试证书留在生产上。从那以后我每次装完这套东西都会强制走一遍--renew --force加外部抓证书不管当天是不是刚签过。因为真正会出问题的从来不是「第一次能不能签下来」而是三个月后那个没人盯着的凌晨续期任务跑了、文件写了、nginx 没 reload。演练一次花五分钟能省掉一次半夜被告警叫起来。希望帮到你。本文还有配套的精品资源点击获取