一次从 0xFFFFFFFF 开始的完整排查,以及三个月后自己动手续订的标准流程。记录于 2026/11/11–12。
一、三个月后怎么自己续订(最重要)
RenewalDays = 55,也就是签发后第 35 天就进入窗口;计划任务每天 09:00(随机延迟 0–4 小时)自动检查一次。本次成功日志给出 Next renewal due after 2027/1/5,从 1/5 到新证到期还有约一个月,中间有几十次自动机会。你要做的不是「到点去点一下」,而是「到点看一眼」。
第 1 步:看证书什么时候到期
管理员 CMD,一行:
C:\xampp\apache\bin\openssl.exe x509 -in C:\xampp\apache\conf\ssl\www.511yj.com-crt.pem -noout -dates
读 notBefore 和 notAfter。只要 notAfter 已经往后推了大约三个月,说明计划任务自己跑完了,什么都不用做。
顺手看一眼这张证覆盖哪些域名:
C:\xampp\apache\bin\openssl.exe x509 -in C:\xampp\apache\conf\ssl\www.511yj.com-crt.pem -noout -text | findstr /i "DNS:"
本次实测输出:DNS:511yj.com, DNS:bbs.511yj.com, DNS:www.511yj.com。三个域名在同一张 SAN 证书里,没有 home.511yj.com,bbs 不用单独申请、单独续。
第 2 步:看四个 PEM 有没有真的被覆盖
dir "C:\xampp\apache\conf\ssl\*.pem"
本次成功后四个文件的修改时间全部变成 2026/11/12 0:36。-crt.pem 和 -key.pem 由 wacs 成对写入,不用手工复制、不用手工导出 pfx。
第 3 步:看计划任务自己跑得怎么样
schtasks /query /tn "win-acme renew (acme-v02.api.letsencrypt.org)" /fo list /v | findstr /i /c:"上次运行时间" /c:"上次结果" /c:"下次运行时间"
上次结果 = 0x0 就是成功。若中文匹配不到,去掉 | findstr ... 后半段看完整输出;任务名以「任务计划程序」里显示的原名为准。
第 4 步:只有确认没续上,才手动跑一次
"C:\Program Files\Win-acme\wacs.exe" --renew
不带 --force。让 wacs 自己判断窗口,这是最不容易添乱的写法。看到 Renewal for [Manual] www.511yj.com succeeded 或 Certificate generated 就停。
.htaccess、加防火墙规则都治不了。隔一段时间重跑一次即可;窗口有几十次机会,不需要今晚一次定输赢。
第 5 步:确认 Apache 加载的是新证
这条 renewal 记录里配了安装脚本,成功时日志会打印 Script c:\xampp\apache\bin\httpd.exe starting with parameters -k restart,Apache 已自动重启。浏览器 Ctrl+F5 看「颁发日期」变今天即可。若没自动重启,XAMPP 控制台 Stop → Start 一次。
二、命令速查
| 目的 | 命令 | 怎么读结果 |
|---|---|---|
| 查证书到期 | openssl.exe x509 -in ...\www.511yj.com-crt.pem -noout -dates |
notAfter 是否已推后约 90 天 |
| 查证书域名 | openssl.exe x509 -in ...\www.511yj.com-crt.pem -noout -text | findstr /i "DNS:" |
应列出三个域名 |
| 查 PEM 是否更新 | dir "C:\xampp\apache\conf\ssl\*.pem" |
四个文件时间是否变成今天 |
| 手动续订(推荐) | "C:\Program Files\Win-acme\wacs.exe" --renew |
succeeded / 退出码 0x0 |
| 强制走一遍 | "C:\Program Files\Win-acme\wacs.exe" --renew --force |
只跳过「是否到期」判断,不保证成功 |
| 看详细报错 | "C:\Program Files\Win-acme\wacs.exe" --renew --verbose |
唯一可靠的定位依据 |
| 查计划任务 | schtasks /query /tn "win-acme renew (acme-v02.api.letsencrypt.org)" /fo list /v |
上次结果 / 下次运行时间 |
| 手动触发任务 | schtasks /run /tn "win-acme renew (acme-v02.api.letsencrypt.org)" |
「成功: 尝试运行」只代表调度器接受,不代表续期成功 |
| 终止卡住的任务 | schtasks /end /tn "win-acme renew (acme-v02.api.letsencrypt.org)" |
状态卡「正在运行」时用,之后 taskkill /im wacs.exe /f |
| 读日志(PowerShell) | Get-Content "C:\ProgramData\win-acme\acme-v02.api.letsencrypt.org\Log\log-20261111.txt" -Tail 45 |
看 [EROR] 和最后一行退出码 |
| 确认客户端完好 | dir "C:\Program Files\Win-acme\wacs.exe" |
约 39,725,880 字节;0 KB 就是被隔离了 |
| 确认 TCP 参数没被改回去 | netsh interface tcp show global | findstr /i "ECN" |
应为 disabled |
log-YYYYMMDD.txt,不是 .log——用 Get-ChildItem *.log 会拿到空值、报「无法将参数绑定到参数 Path,因为该参数是空值」。另外 schtasks /run 之后立刻读日志会读到半截,等十几秒再读。
三、退出码与报错对照表
0xFFFFFFFF 判断不了断在哪一步。一次续期有十几步,任何一步失败都是 Exiting with status code -1。唯一依据是日志。
| 看到 | 含义 | 动作 |
|---|---|---|
0x0 + No renewals are due |
正常,还没到窗口 | 什么都不做 |
0x0 + succeeded |
成功 | 查四个 PEM 日期 |
0xFFFFFFFF / -1 |
未处理异常 | 读日志,别猜 |
0xE0434352 |
.NET CLR 未处理异常 | 读日志 |
| 拒绝访问 | 进程根本没起来(常见于 exe 变 0 KB 或窗口不是管理员) | dir 看文件大小 |
12002 / WinHttpException / 操作超时 |
出站请求超时 | 重跑,不改配置 |
'<' is an invalid start of a value |
/directory 超时后回退取根路径,把 1448 字节 Boulder HTML 当 JSON 解析 |
同一种超时的另一张脸 |
The operation has timed out + Mutex.ReleaseMutex |
初始连接超时后清理时崩了一下,进程已退出 | 不影响下次,重跑 |
409 Conflict / alreadyReplaced |
ARI replaces 孤儿订单 |
把 Certificates 改名为 Certificates_bak,让 previous: [none] |
Cached order has status invalid, discarding |
自愈动作,丢弃旧订单 | 正常,继续看后面 |
Unable to refresh cached order |
旧订单缓存刷新不了 | 可忽略;只要后面出现 Downloading certificate 就有戏 |
Source change in order Main detected |
缓存被移走后判定源变化 | 正常,也是触发条件之一 |
challenge / Invalid / 404 |
才轮到验证文件问题 | 查 .well-known 和目录联接 |
Renewal ... succeeded |
成了 | 查 PEM 日期、看浏览器颁发日期 |
四、环境与关键路径
| 项 | 值 |
|---|---|
| 系统 | Windows Server 2019,10.0.17763.9487,阿里云 ECS,4 GB 内存 |
| Web | XAMPP / Apache + mod_php,WordPress 主站 + bbs 子站 |
| 客户端 | win-acme 2.2.9.1701(release, pluggable, standalone, 32 位) |
| 程序目录 | C:\Program Files\Win-acme\ |
| 配置目录 | C:\ProgramData\win-acme\acme-v02.api.letsencrypt.org\ |
| renewal 记录 | 配置目录根下的 xHCGjFDQdU2ir5xCyUa9tQ.renewal.json,不在 Renewals\ 子目录 |
| 日志 | ...\Log\log-YYYYMMDD.txt |
| 证书缓存 | ...\Certificates\(.pfx) |
| 证书输出 | Store 插件 PemFiles → c:\xampp\apache\conf\ssl\,四个 .pem |
| 安装脚本 | httpd.exe -k restart(续期成功自动重启 Apache) |
| 证书域名 | www.511yj.com / 511yj.com / bbs.511yj.com(同一张 SAN,Manual 模式) |
| 旧证 | 2026/8/26 签发,2026/11/24 09:37 到期 |
| 本次新证 | 2026/11/12 0:36 落地,下次窗口 2027/1/5 |
| 计划任务 | win-acme renew (acme-v02.api.letsencrypt.org),每天 09:00,随机延迟 4 小时,执行时限 2 小时 |
| 关键 settings | RenewalDays: 55、RetryCount: 15、RetryInterval: 5、Proxy.Url: [System]、DefaultBaseUri: https://acme-v02.api.letsencrypt.org/ |
latest due date 2026/10/20、Order Main should run (due for renewal)。真正该续的日子是 10/20,拖到 11/11 已经晚了 22 天。而且「没到窗口」会打印 No renewals are due 并以 0x0 正常退出,绝不会是 0xFFFFFFFF。
五、昨晚排查复盘:一条链,不是十几种病
起点是任务计划程序里天天 0xFFFFFFFF。前台跑一次 --renew --verbose,看到的是同一个进程里两种结果:
GET /directory → 10 秒超时
GET / → 10 秒超时
Initial connection failed, retrying with TLS 1.2 forced
GET /directory → OK
Connection OK!
...
Constructing ACME protocol client...
Getting service directory...
WinHttpException 12002 操作超时
Renewal for [Manual] www.511yj.com failed
Exiting with status code -1
初始连通性检查有「强制 TLS 1.2」兜底能过,正式 ACME 客户端没有兜底,撞上 10 秒硬超时就退出。后面所有花式报错,都是这一处超时落在不同代码分支的样子。
逐层清障(按实际顺序)
- 端点:任务命令行里的
baseuri改成https://acme-v02.api.letsencrypt.org/。settings.json里DefaultBaseUri也是 https。 - TLS:写了两条
DefaultSecureProtocols = 0x800(64 位与Wow6432Node)。wacs 是 32 位,Wow6432Node那条才是关键。之后初始 GET 不再动不动需要 forced 兜底。 - 代理:
netsh winhttp show proxy→ 「直接访问(没有代理服务器)」,set | findstr /i proxy无输出,排除。 - 防护软件:云锁把
wacs.exe加进「例外进程」后,出站立刻通了,日志第一次跑到POST /acme/new-order。同一套防护还两次把 exe 清成 0 KB——表现为 CMD 直接「拒绝访问」。替换回 39,725,880 字节后恢复。 - 409 ARI:
Conflict / cannot indicate an order replaces certificate ... which already has a replacement order。之前某次POST new-order已在 LE 侧建单,本地却判失败。把Certificates改名为Certificates_bak,让previous: [none],409 消失。 - 验证文件:
bbs.511yj.com是独立DocumentRoot C:/xampp/htdocs/bbs,而 wacs 把 token 写在C:/xampp/htdocs/.well-known/acme-challenge/。建目录联接让两边读到同一份文件:mklink /J "C:\xampp\htdocs\bbs\.well-known" "C:\xampp\htdocs\.well-known"自测
curl.exe -sL ... probe.txt返回bbs=200 size=4。注意-L必须加,否则只看到 301 跳转页误判为失败。 - 命令粘贴:多次把整段回显粘回命令行,出现过
--verbose后面黏四遍路径、Microsoft Windows [版本 ...]被当命令执行、Press <Enter> to close。一条一条贴,一次回车。 - 网络栈:
nslookup显示端点解析到prod.api.letsencrypt.org → ca80a1...pacloudflare.com → 172.65.32.248(Cloudflare Anycast);IPv6 无全局路由、八个网络服务全RUNNING、Winsock 目录全是mswsock.dll、WinDefend/WdNisSvcSTOPPED、内存 4 GB 剩约 447 MB 但Pages/sec不高。最后在netsh interface tcp show global里发现ECN 功能 : enabled——2019 出厂是 disabled,是装完系统跑的那个优化 bat 改的。关掉:netsh interface tcp set global ecncapability=disabled之后 curl 采样里
connect=0.000000(SYN 一次都没建起来)消失了,但仍偶发total=20s / code=000。 - 远程通道:一度把安全组 3389 入站删了,连接还活着只是因为已有 TCP 不会被掐断。补回后远程明显变顺。这跟出站续期无关,但让「本地传文件到 ECS」不再卡。
成功那次的输出
Connection OK!
Unable to refresh cached order: An error occurred while sending the request.
Downloading certificate [Manual] www.511yj.com
Store with PemFiles...
Exporting .pem files to c:\xampp\apache\conf\ssl
Installing with Script...
Script c:\xampp\apache\bin\httpd.exe starting with parameters -k restart
Script finished
Next renewal due after 2027/1/5
Renewal for [Manual] www.511yj.com succeeded
四个 PEM 的修改时间随后全部变成 2026/11/12 0:36。
六、为什么最后成功了(诚实版:没有定论)
能确定的三件事
- 成功那次没有
new-order、没有challenge、没有HEAD new-nonce,只有Downloading certificate。它不是当场跑完验证链,而是把一张 LE 侧已经存在的证书下载回来。 - 之前有一轮跑到
[bbs.511yj.com] Preliminary validation looks good之后才Error submitting challenge answer (12002),接着Deactivating pending authorization。最合理的推断是:那次请求其实到了 LE、验证通过、证也签了,只是回包在回程丢了,wacs 判失败退出,证书留在了服务端。这是推断,不是已证实的事实。 - 前面那些修复确实都是必要前置:https 端点、TLS 1.2、云锁白名单、exe 恢复、409 绕开、junction、ECN 回默认。没有它们,连「下载」这一步都走不到。
为什么会有「运行一万次也这样」的感觉
一次完整续期是 directory ×2 → HEAD new-nonce → renewal-info → new-order → challenge ×3 → finalize → 下载证书,8–10 个串联请求,每个都独立碰那 10 秒。RetryCount: 15 只管状态轮询次数,管不了单次超时上限,而且超时异常直接退出、不走重试。单次成功率哪怕有八成,连乘十次整程也只剩一成左右。所以每次都死在不同一步,看着像冒不完的新病。
而「下载已签好的证书」只要两三步。最后一次恰好只需要这一步,于是成了。8/26 手动申请能成也是同理——交互式菜单会等、会重试,没有一次性退出的 10 秒硬上限。
不是「阿里云故意减速逼升级」
- 没有受益方:阿里云不卖 Windows Server 升级、不卖 win-acme,这张证书还是 Let's Encrypt 免费的。
- 策略性限速不会只卡一半:
Connection OK!出现过很多次、HEAD 200 / 3.5s、Boulder 页面能打开、还收到过 409 业务响应,最后下载证书一次就通。 - 全程没有升级过任何东西:wacs 一直是
2.2.9.1701,系统一直是 17763,证书照样续上了。
更朴素的解释:LE 的 API 前端挂在 Cloudflare Anycast,国内 ECS 跨境访问它本来就时通时断;叠上优化 bat 打开的 ECN(SYN 更容易被中间设备静默丢),再叠上 wacs 写死的 10 秒超时,就成了这个症状。
七、一次性加固清单(做完就别再动)
| 项 | 做法 | 为什么 |
|---|---|---|
| 云锁文件防护白名单 | 目录级加 C:\Program Files\Win-acme\、C:\ProgramData\win-acme\、C:\xampp\apache\conf\ssl\,别只加单条「例外进程」 |
exe 两次被清成 0 KB 就是这层干的 |
| ECN 保持 disabled | netsh interface tcp show global | findstr /i "ECN" |
恢复 2019 出厂值,别再跑那个优化 bat |
| bbs 目录联接保留 | mklink /J "C:\xampp\htdocs\bbs\.well-known" "C:\xampp\htdocs\.well-known" |
下次 HTTP-01 三个域名还要共用 token |
Certificates_bak 留着别还原 |
不删、不改回 Certificates |
还原会把 409 的 replaces 带回来 |
| 3389 入站别删 | 安全组里保留,来源限制成本地公网 IP 最稳 | 删了当前会话还能用,一断就回不去 |
| 两条 TLS 注册表保留 | DefaultSecureProtocols = 0x800,含 Wow6432Node |
wacs 是 32 位,读的是这条 |
八、排障原则(比任何一条命令都值钱)
- 只看日志,不看退出码。
0xFFFFFFFF可能是任何一步挂了。 - 一行一条命令,一次回车。把整段回显粘回命令行是这次最大的自伤来源。
- 别
cd进C:\Program Files\Win-acme敲命令。那个目录里出现过cd/netsh/reg三个 0 KB 垃圾文件。用绝对路径调用。 - 别连点「运行」。任务有「请勿启动新实例」,重复点击会被静默吞掉;状态卡「正在运行」先
schtasks /end再taskkill /im wacs.exe /f。 - 业务层没坏就别改业务层。
.htaccess、vhosts、压缩、缓存、CSS、PHP、WebP、Apache 配置全程没动过;日志没走到challenge就不加 ACME 排除规则。 - 不要用「重新申请新证书」当解药。它走同一串请求、卡在同一处,还多一条 renewal 记录、可能撞限流。
- 最坏只是回到现状。旧证有效到到期那一刻,手动续期失败不会让站塌,窗口里还有几十次机会。
一句话收尾:不是找到一个开关修好了,而是把障碍逐层清掉之后,最后一次恰好只需要「下载」这一步。下次不用复刻昨晚,按第一节看一眼 notAfter、需要时跑一次 --renew 就够了。