win-acme 实战:Windows 免费 HTTPS 证书自动签发与续期
简介:win-acme.v2.1.18-x64 是一款面向 Windows 站点管理员、个人站长与小型项目运维者的免费 SSL 证书自动化工具,重点解决 Let's Encrypt 证书申请、安装与续订流程繁琐的问题。压缩包共 27 个文件、约 29.58MB,包含 15 个 ps1 脚本、4 个 dll 运行库、2 个 json 配置文件、2 个 bat 批处理文件,以及 exe 主程序、xml 配置模板、dat 公共后缀列表和 txt 版本说明等,兼顾图形化操作与命令行脚本自动化。工具可自动完成域名所有权验证、证书签发、IIS 等 Web 服务器部署及到期续订,并支持多域名与通配符证书管理,配合日志与报告便于排查验证失败、续订异常等问题;对 HTTP-01 与 DNS-01 验证、证书计划任务和状态检查也有相应支持。已有 334 人学习下载,适合希望以较低成本为站点启用 HTTPS、减少手工维护证书负担,并借助脚本与配置样例搭建自动化续期流程的读者参考使用。
1. 免费域名搜进来,先说清楚 win-acme 到底给什么
搜「免费域名」的人多半会被 win-acme 这个名字带偏:它不发域名,发的是 90 天一轮的免费 TLS 证书,把 HTTPS 那笔每年几千块的开销直接抹掉。v2.1.18 的 x64 自包含包解开就是一个wacs.exe,不需要预装 .NET 运行时,Windows Server 2012 R2 以上丢上去双击就能跑。它管的是证书从签发、验证、部署到续期的整条链路:对接 ACME 协议、跑 HTTP-01 或 DNS-01 验证、把结果写进 IIS 或 PEM/PFX 文件、再自己挂一个计划任务做无人值守续期。适合自己维护一两台 Windows 机器的运维、给内网测试站配证书的后端,以及不想天天敲 OpenSSL 的人。它不管域名本身,也不替你做解析,这两件事必须先自己搞定。
2. 装到签发第一张证书:x64 自包含包的启动姿势与向导映射
2.1 解压后的目录结构:为什么优先选自包含包
这个包最大的价值是「零依赖启动」。解压出来你会看到wacs.exe加一批 dll、一个web子目录和若干插件目录,全部自包含,机器上没装 .NET 运行时也能直接跑。同一版本系列里还有依赖框架的变体,体积小一些,但换来的代价是每台目标机器都得先补运行时,一旦版本对不上,双击就是一句「缺少运行时」的弹窗,连菜单都进不去。生产环境里我一般无条件选自包含版,硬盘多占几十兆换的是排障时少一个变量。解压路径也有讲究,别放桌面或者带中文的目录,放到C:\tools\win-acme这类纯英文短路径下,因为后面计划任务的工作目录会引用这个位置,路径里带空格或者中文很容易在任务计划里被截断。第一次运行必须以管理员身份开命令行,不是双击 exe——双击虽然也能进交互菜单,但后续写证书存储、改 IIS 绑定都需要提权,中途失败会让你以为证书签发失败,其实是权限不够。
先做一件事:确认这版支持哪些参数,别照抄三年前的教程。
# 管理员身份打开 cmd,进到解压目录 cd /d C:\tools\win-acme # 打印完整参数列表,重点看 target / validation / store / installation 四段 wacs.exe --help--help的输出很长,但只需要盯四组参数:--target决定主机名从哪来(手动输入还是扫 IIS),--validation决定用哪种验证方式,--store决定证书存成什么格式,--installation决定要不要顺手装进 IIS。这四个决定了后面所有命令的形状。不同小版本之间参数名会有微调,比如早期版本某些开关叫法不一样,抄旧命令最常见的报错就是「未知参数」,而这条命令能在三十秒内告诉你正确答案。养成习惯:换一台机器、换一个版本,先跑一次--help,比出问题再回头查强得多。
2.2 交互向导走一遍:从主菜单到证书落地
第一次用建议走交互模式,把流程在脑子里建立一遍映像,之后再用命令行固化。运行wacs.exe(管理员 cmd 里),主菜单是几个字母键:新建证书、续期、管理续期、列出已有续期。选新建之后,向导会依次问你五件事,这五件事和后面命令行的参数是一一对应的,理解了这层映射,参数就不用背了。
第一步问主机名,输入www.example.com,多个域名用逗号分隔,注意这里填的必须是能通过公网解析到本机的域名,填错后面验证必然失败。第二步问验证方式,列表里会同时出现文件系统验证、自托管验证、DNS 手动验证、DNS 脚本验证等选项,每一项前面标着[http-01]、[dns-01]或[tls-alpn-01],这就是协议类型。第三步问证书存到哪,可选 PEM 文件、PFX 归档、Windows 证书存储,选完还会追问具体路径或存储位置。第四步问要不要安装到 IIS,如果这台机器上有 IIS 站点,选安装会自动更新绑定。第五步是接受协议条款和填邮箱,邮箱必须真实可用,证书快到期时 CA 会往这个地址发提醒,很多「证书突然过期」的事故就是当初随手填了个不存在的邮箱。
向导跑完,屏幕上会打印一段汇总,包含证书路径、指纹、到期时间和下次续期时间。这一段务必截图或者复制到记事本,因为它是后面写脚本、核对绑定关系的依据。整个过程中最容易出错的是第二步和第三步的路径:验证路径填的必须是网站真实根目录,证书输出路径必须是空目录或者你有写权限的目录,两者填反了会得到两种完全不同的报错,前者是验证 403,后者是写文件失败。
2.3 用命令行把向导固化:一条能重复执行的签发命令
交互模式适合第一次,第二次开始就该上命令行了。命令行最大的好处是可复现:换机器、换域名,改几个变量就能重跑,而且能直接塞进自动化脚本里。下面这条是我常用的最小可用组合,做的是「手动指定域名 + 文件系统验证 + 输出 PEM」:
wacs.exe ^ --target manual ^ --host www.example.com ^ --validation filesystem ^ --webroot C:\inetpub\wwwroot ^ --store pemfiles ^ --pemfilespath C:\certs\www.example.com ^ --accepttos ^ --emailaddress ops@example.com ^ --verbose逐段说明这几个参数在干什么,以及改哪里:
| 参数 | 作用 | 改动时的注意点 |
|---|---|---|
--target manual | 域名从参数里读,不扫 IIS | 想让它自动发现 IIS 站点,改成--target iis |
--host | 要签的域名,逗号分隔多个 | 通配符写*.example.com,且必须配 DNS 验证 |
--validation filesystem | 把验证文件写进网站根目录 | 需要--webroot配套,路径必须是站点真实根 |
--webroot | 验证文件的落地目录 | 写错就是 403,用浏览器能访问到该目录再继续 |
--store pemfiles | 输出 PEM 格式证书 | 需要--pemfilespath,目录不存在会自动建 |
--pemfilespath | PEM 输出目录 | 建议一域名一目录,便于脚本按目录 reload |
--accepttos | 自动接受 CA 条款 | 不加会卡在交互确认,自动化场景必加 |
--emailaddress | 到期提醒邮箱 | 别填 noreply 之类的假地址 |
--verbose | 打印详细过程 | 首次签发必开,出问题就看它 |
这条命令跑完,最快十几秒就能出结果。如果第一次就失败,把--verbose的输出从头看到尾,它会明确告诉你卡在哪一步:是 DNS 解析不到、是 80 端口连不上、还是文件写不进去。这比在浏览器里反复刷新猜原因高效得多。还有一点:调试阶段不要拿生产环境反复试,ACME 服务端对同一组域名有每周签发次数限制,反复失败重试很容易把自己锁住,正确做法是把 baseuri 指向 CA 提供的测试目录,先跑通再切回来。测试证书浏览器不认,这是正常的,它只用来验证流程通不通。
3. 验证方式怎么选:HTTP-01、DNS-01 与产物落地位置
3.1 文件系统验证和自托管验证,差在一个 80 端口
两种方式都属于 HTTP-01,本质都是让 CA 通过http://域名/.well-known/acme-challenge/随机串访问到一个指定内容。差别在于谁来提供这个 HTTP 服务。文件系统验证是「借」你已有的 web 服务器:win-acme 把验证文件写进网站根目录的.well-known/acme-challenge/下,然后由你现有的 IIS 或 nginx 把它吐出去。这条路适合已经有站点在跑、80 端口本来就被占用的机器,缺点是对 web 服务器的路由配置有依赖,最常见的失败原因是站点做了全站 301 跳 HTTPS,或者 nginx 里写了try_files把所有请求都转给后端应用,导致.well-known这个路径根本落不到真实文件上。
自托管验证是 win-acme 自己临时起一个极简 web 服务器抢占 80 端口,验完就关。它适合这台机器上根本没有 web 服务、或者 web 服务不在这台机器上的场景。代价是运行期间 80 端口必须是空闲的,如果 IIS 正在跑,就会直接报端口占用。判断方法很简单:netstat -ano | findstr :80看有没有进程在监听,有就说明自托管模式走不通,乖乖用文件系统模式,或者先停服务再签。
下面这段是 nginx 环境下最容易踩的配置坑,放行规则要写在跳转规则之前:
server { listen 80; server_name www.example.com; # 验证路径必须优先匹配,且不能跳转、不能交给后端 location ^~ /.well-known/acme-challenge/ { root /var/www/html; default_type text/plain; try_files $uri =404; } # 其余请求才走 HTTPS 跳转 location / { return 301 https://$host$request_uri; } }^~前缀匹配的优先级高于正则,能保证验证请求不被后面的 rewrite 规则截走;try_files $uri =404是为了在文件真的不存在时返回 404 而不是被兜底到首页,这样出问题时你能立刻从状态码判断是「文件没写进来」还是「路由被拦了」。改完 nginx 一定要nginx -t再 reload,配置语法错误会导致整个站点挂掉,那时候你面对的就是双重故障。IIS 那边同理,检查 URL 重写模块里有没有全站规则,有的话给.well-known加一条排除。
3.2 DNS-01 与通配符:手动模式和脚本钩子的边界
通配符证书*.example.com只能走 DNS-01,这是硬约束,因为 HTTP-01 无法证明你控制所有子域。DNS-01 的原理是 CA 让你在_acme-challenge.example.com加一条指定内容的 TXT 记录,它去权威 DNS 查询这条记录,查到就算通过。手动模式下,win-acme 会把该加的记录名和记录值打印在屏幕上,你复制到 DNS 控制台,等生效后回车继续,签完再手动删掉。这条路适合一年只签一次、域名数量个位数的场景,虽然笨但绝不会因为 API 权限出问题而失败。
脚本模式是把「加记录 / 删记录」两个动作交给脚本,win-acme 在验证前后各调一次。真正的坑不在脚本本身,而在 DNS 传播延迟:记录提交成功不代表权威 DNS 立刻对外可见,客户端如果查得太早会拿到 NXDOMAIN 直接判失败。稳妥做法是在脚本里加主动探测,用公共 DNS 反复查这条 TXT 直到查到为止,再返回给 win-acme,而不是靠固定 sleep 硬等。另一个坑是 TXT 记录同名多条:同一主域下同时签*.example.com和example.com时,两条验证会往同一个记录名写不同值,如果脚本是「覆盖写」而不是「追加写」,第二条必然覆盖第一条导致验证失败。脚本里必须实现「读现有值 → 追加 → 验证完删掉自己那条」的逻辑。
提示:脚本模式的参数名在不同小版本间有过调整,写脚本之前先跑
wacs.exe --help找到 validation 段的实际写法,别直接套网上的旧示例。
3.3 产物清单:PEM、PFX、证书存储分别喂给谁
同一个证书,不同消费者要不同格式,这一步选错,后面会得到「证书明明签下来了,服务就是不认」的玄学现象。下面这张表是我这几年反复对照过的对应关系:
| 存储类型 | 落地产物 | 典型消费者 | 备注 |
|---|---|---|---|
| PEM 文件 | fullchain.pem、privkey.pem、chain.pem | nginx、Apache、Go 服务 | 私钥权限要收紧,Linux 侧 600 |
| PFX 归档 | .pfx单文件 | IIS 导入、Tomcat、Java keystore | 需要设密码,密码要能传给部署脚本 |
| Windows 证书存储 | 本机「个人」或「Web 托管」存储 | IIS 绑定、SMTP、远程桌面 | 私钥 ACL 丢了会报密钥集不存在 |
| IIS 安装 | 中央证书存储 + 站点绑定 | 直接对外的 IIS 站点 | 会自动改绑定,签之前先备份配置 |
PEM 那一行要特别说一句:fullchain.pem是「站点证书 + 中间证书」拼起来的完整链,chain.pem只有中间证书,privkey.pem是私钥。nginx 的ssl_certificate要指向 fullchain 而不是单独的站点证书,只配站点证书会出现在部分客户端上「证书链不完整」的警告,桌面浏览器因为会自动补链所以看不出来,手机和部分 API 客户端就直接报错了。这个问题藏得很深,属于典型的「本机测试一切正常、上线被投诉」类型。
证书存储那条路的坑在私钥权限。证书导进「Web 托管」存储后,IIS 应用池账户需要能读到私钥,如果这台机器是从别的机器迁移过来的、或者你手工用 certutil 导入过,私钥 ACL 很容易丢,症状是绑定站点时报0x80070520。遇到这个别急着删证书重来,先用certutil -store WebHosting看证书是否在、私钥是否标记为可导出,再检查私钥文件的 ACL。
4. 自动续期闭环:计划任务、钩子脚本与日志三件套
4.1 续期任务是怎么被写进任务计划的
证书只有 90 天,能不能自动续上决定了这套方案是省事还是添乱。win-acme 在创建续期条目时,默认会往 Windows 任务计划里注册一个名字带win-acme renew的任务,触发器设成每天跑一次,实际执行时它自己判断「离到期还有多久」,没到续期窗口就直接退出,所以天天跑并不会真的天天签发。这个设计比「算好日期定一个 60 天后的单次任务」稳得多,因为机器可能关机、可能休眠、可能临时没网,单次任务错过就是错过,每日轮询则有很多次补救机会。
注册任务时会问你用哪个账户运行,这一步别随手选当前登录用户。选当前用户意味着任务依赖这个账户的密码,密码一改、账户一禁用,任务就静默失败,而且失败得很安静——任务计划里只显示一个错误码,没有任何弹窗。我一般选 SYSTEM,它不受登录状态和密码策略影响,缺点是脚本里如果用到了用户级的环境变量或者映射网络驱动器会读不到。如果确实需要访问网络路径,就用一个有固定密码的服务账户,并且把「不管用户是否登录都运行」勾上。
:: 列出当前所有续期条目,看下一次到期时间和上次执行结果 wacs.exe --list :: 强制立刻走一次续期流程,用于验证钩子脚本是否生效 wacs.exe --renew --force :: 测试阶段不想让它自动注册计划任务,加上这个开关 wacs.exe --renew --notaskscheduler--list这条命令的价值被严重低估。它输出的不只是域名,还有每条续期的 ID、绑定的验证方式、证书路径和下次续期时间,出问题时先跑它,能立刻区分是「续期条目压根没建」还是「条目在但执行失败」。--renew --force是调试利器,但它会真的去 CA 申请新证书,所以在生产域名上别连着跑——一周内重复签发同一组域名是有次数上限的,超了就得等窗口重置。想反复测流程,就把 baseuri 指到测试目录,随便折腾。
注意:
--force绕过的是「是否在续期窗口内」的判断,不是绕过 CA 的限制。调试和生产的边界要在这里划清楚。
4.2 续期后的钩子:导出 PEM 并让服务热加载
证书续下来只是第一步,让正在跑的服务用上新证书才是闭环。win-acme 支持在续期成功后执行一个脚本,把续期 ID、证书指纹之类的信息作为参数传进去,脚本里根据这些信息做后续动作。典型场景是:win-acme 只负责把新 PEM 写到固定目录,脚本负责把文件同步到 nginx 目录并 reload。
# reload-nginx.ps1 # 由 win-acme 在续期成功后调用,参数由 --scriptparameters 传入 param( [string]$RenewalId, [string]$Thumbprint ) $log = "C:\tools\win-acme\hooks\reload.log" "$(Get-Date -Format s) renewal=$RenewalId thumbprint=$Thumbprint" | Out-File $log -Append # 先做语法检查,检查不过就不 reload,避免把站点搞挂 & "C:\nginx\nginx.exe" -t if ($LASTEXITCODE -ne 0) { "nginx config test failed, skip reload" | Out-File $log -Append exit 1 } # 平滑重载,不中断现有连接 & "C:\nginx\nginx.exe" -s reload "reload done, exit=$LASTEXITCODE" | Out-File $log -Append这段脚本的关键设计有三点。第一,它自己写日志,因为 win-acme 的日志只记录到「脚本已执行」,脚本内部的成败它看不到,没有独立日志你就只能靠猜。第二,reload 之前先nginx -t,配置有问题时宁可继续用旧证书也不要让服务起不来,这个顺序反了就是血泪经验。第三,用-s reload而不是重启服务,reload 是平滑的,重启会掐断正在处理的请求。参数$RenewalId和$Thumbprint来自 win-acme 的占位符替换,具体可用哪些占位符以--help里 script 段的说明为准,不同版本支持的不完全一样,写之前对一遍,别想当然。
4.3 用日志和状态文件判断这次续期到底成没成
续期失败最怕的是「静默」:计划任务照常跑,日志里没有明显红色报错,等到某天浏览器弹证书过期才发现已经断了两个月。所以要有固定的巡检动作。日志落在程序数据目录下,路径形如C:\ProgramData\win-acme\acme-v02.api.letsencrypt.org\Log,文件名按日期组织,一次续期一个文件。判断成败不要只看最后一行,要看有没有出现明确的成功字样和证书指纹,中间出现的重试记录不算失败,只有最终没有拿到新证书才算。
除了日志,续期条目的配置文件也值得看一眼。每个续期条目在Renewals子目录下有一份自己的配置文件,记录了域名、验证方式、存储路径、上次执行时间。这份文件是「当前生效配置」的唯一真相,改了它等于改了续期行为,所以我一般先备份再动。有些场景下你会需要手工改它,比如换了 webroot 路径、换了输出目录,与其删掉重建,不如直接改配置里的路径字段,保留原来的续期 ID 和计划任务关联。
巡检脚本可以很简单:每天读一次最新日志,找关键字,没有成功标记就发告警。真正麻烦的不是写脚本,而是坚持看。我见过太多「配好就不管」的案例,包括我自己早期,配完三个月后证书过期,排查半天才发现是某次系统更新后计划任务的运行账户被改成了需要交互登录。从那以后我给自己定了规矩:任何自动续期上线后,第二周必须人为跑一次--list确认状态,之后再按月抽查。
5. 五个翻车现场:从 403 到 0x80070520 的排查顺序
5.1 验证返回 403 或 Invalid response
现象:命令跑完报验证失败,提示从http://域名/.well-known/acme-challenge/xxx取文件失败,返回 403 或 404。原因基本只有三类:webroot 路径填的不是站点真实根目录;站点有全站跳转或重写规则把.well-known拦截了;文件写进去了但 web 服务器进程没有读权限。解决顺序是先用浏览器或curl -I直接访问那个路径,看返回什么状态码,403 就是权限或规则问题,404 就是路径问题。确认路径无误后,检查 nginx 的location顺序和 IIS 的 URL 重写规则,把.well-known的放行规则提到跳转规则之前。
5.2 自托管模式报端口占用
现象:选自托管验证后立刻失败,日志里出现地址已被占用的字样。原因很直接:80 端口上已经有别的进程在监听,最常见的是 IIS 的默认站点,其次是某些系统服务。排查用netstat -ano | findstr :80拿到 PID,再tasklist | findstr <PID>对上是哪个进程。解决方式不是硬停服务,而是换验证方式——既然 80 端口已经被 web 服务器占着,直接改用文件系统验证,把验证文件写进那个站点的根目录,反而更省事。硬停 IIS 再签,签完还得启回来,中间站点是断的。
5.3 IIS 绑定更新了但浏览器还是旧证书
现象:证书续期成功,路径里的文件时间也是新的,但浏览器访问还是显示旧证书或者证书链不完整。原因通常是 IIS 站点绑定还指向旧证书的指纹,或者用了中央证书存储但站点没有正确引用。先用netsh http show sslcert看 443 端口当前绑定的指纹,和--list输出的新指纹比对,不一致就是绑定没更新。再检查 IIS 站点绑定的 SNI 设置,多站点共用 443 时靠 SNI 区分,SNI 没开会导致所有域名都拿到同一个证书。改完绑定记得iisreset或者回收应用池,某些情况下 IIS 会缓存证书句柄。
5.4 计划任务天天跑但证书始终不续
现象:任务计划里能看到任务,上次运行结果是一个非零的错误码,证书到期时间纹丝不动。原因有三个高频项:任务运行账户的密码过期或被改;任务被设成「仅在用户登录时运行」而服务器没人登录;机器在续期窗口期长期断网。排查先看任务的「上次运行结果」和「历史记录」,再手动用同一账户执行一次--renew看报错。解决方式是把运行账户改成 SYSTEM,勾选「不管用户是否登录都运行」,并在脚本里加网络可达性检查,不通时提前退出并写日志,而不是等到验证阶段才失败。
5.5 报 too many certificates 被限流
现象:调试阶段反复重试,突然所有签发都失败,提示同一组域名的证书签发次数超限。原因是公共 CA 对「同一组域名集合」的单位时间签发数量有上限,--force反复跑正好踩中。这个坑没有后悔药,只能等窗口重置,通常是一周。正确的应对是提前把调试流量导到测试目录:签发命令里把 baseuri 指向 CA 提供的测试入口,测试证书不被浏览器信任但流程完全一致,等流程确认无误再切回生产目录跑一次。这个习惯我从第一次被限流之后就固定下来了,此后再没在生产目录上做过任何调试。
6. 多站点批量:把续期配置当代码管起来
机器上站点一多,一条条敲命令就不现实了。批量模式的核心思路是:先用一条命令把多个域名塞进同一个证书,再用--list和续期配置文件统一管理,而不是为每个域名建一个计划任务。
6.1 多域名合并与配置复用
同一个站点的主域、www、几个子域完全可以签进同一张证书,--host支持逗号分隔多个域名,验证只需做一次,续期窗口也只有一个,管理成本直接降一个量级。但要注意证书里的域名数量不是越多越好,一个域名验证失败会导致整张证书签发失败,所以「按业务分组、每组一张证书」比「全站一张大证书」更稳。
:: 三个域名合并一张证书,输出到按主域命名的目录 wacs.exe ^ --target manual ^ --host example.com,www.example.com,api.example.com ^ --validation filesystem ^ --webroot C:\inetpub\wwwroot ^ --store pemfiles ^ --pemfilespath C:\certs\example.com ^ --accepttos ^ --emailaddress ops@example.com这条命令生成的续期条目会作为一个整体存在,续期时三个域名一起验、一起签、一起更新文件。改配置时改的也是这一个条目,不用满机器找散落的计划任务。如果确实需要通配符,把--host换成*.example.com,example.com,同时把验证方式换成 DNS-01,因为通配符不支持 HTTP 验证。
6.2 用一条命令验收证书链
签完之后别只信日志,直接验证证书本身。Windows 上可以用 OpenSSL,也可以用 PowerShell 的Get-PfxCertificate,但我更习惯用 OpenSSL 看完整的链和有效期,信息最全:
# 连上目标站点,打印证书链和有效期 openssl s_client -connect www.example.com:443 -servername www.example.com </dev/null 2>/dev/null \ | openssl x509 -noout -subject -issuer -dates -fingerprint -sha256输出里重点看三项:subject里的域名是否和预期一致、dates的到期时间是否是刚续期后的新日期、issuer是否是中间证书而不是根证书。如果s_client输出里出现verify error: unable to get local issuer certificate,说明服务端没把中间证书一起发出来,回到 3.3 节检查是不是只配了站点证书没配 fullchain。这条命令我一般放在续期脚本的最后一步,自动跑一遍,输出写进日志,比事后靠用户投诉发现要早得多。
证书这东西,配好那天是最可靠的,之后可靠性只会一路下降。我现在的习惯是:每次动过 web 服务器配置、改过防火墙、换过 DNS 服务商,都强制跑一遍--list加一次 openssl 验证,确认链完整、日期正确、续期条目还在。多花三分钟,省掉的是某天早上被电话叫醒。这份 x64 自包含包解压即用,配置、日志、续期条目全部落在 ProgramData 下,按照第 2 章的向导走一遍就能出第一张证书,先把单域名的闭环跑通,再往上叠批量和钩子,希望帮到你。
本文还有配套的精品资源,点击获取