简介:面向个人站长、小型项目与 Windows 运维人员的 Let's Encrypt 证书管理工具包,针对 64 位 Windows 环境提供 win-acme v2.1.18 可执行程序及配套脚本,用于简化免费 SSL/TLS 证书的申请、安装与续期,缓解 HTTPS 部署和证书过期维护压力。压缩包共 27 个文件、约 29.58MB,包含 ps1 脚本、dll 运行库、json 配置、bat 批处理以及 exe 主程序,并附带 txt、xml、dat 等说明与配置数据,便于按目录直接使用或按需调整。已有 334 人学习下载,适合希望低成本完成站点加密的初中级用户参考。工具支持 ACME 自动化验证、IIS 等 Web 服务器证书安装、多域名与通配符证书申请、定期续订、日志报告及命令行脚本调用,可帮助读者建立可复用的证书申请与更新流程,并借助日志排查验证和部署中的常见问题。
1. 搜「免费域名」搜到它的人,多半会先愣一下
很多人在找免费域名的时候,会被推荐下载 win-acme.v2.1.18-x64。解压、双击 wacs.exe、看到满屏英文交互菜单,第一反应是「域名呢?」——这里得先把话说清楚:win-acme 不发域名,它签发的是域名对应的免费 TLS 证书。域名你还得自己有一个(哪怕只花几块钱买的最便宜后缀),但证书这一块它确实能帮你从「每年续费几千块的商业证书」变成「零成本、全自动续期」。这个落差是我见过最多的认知翻车点,所以我把它放在最前面。
它适合谁:手上有一台 Windows Server 或者干脆是台常年开机的 Windows 机器,跑着 IIS、跑着几个 ASP.NET 站点,或者反过来,Windows 只是你签证书的「工作台」,签完把 PEM 文件 scp 到 Linux 上去用。不适合谁:纯 Linux 环境(有更贴合的工具)、需要通配符且 DNS 服务商没有 API 的(验证方式会卡死)。v2.1.18 这个版本属于 2.x 稳定线,单文件 exe + 配置目录,解压即用,没有安装程序,也没有注册表写入——这一点对老旧生产机很友好。
2. ACME 的工作模型:从账户注册到证书落盘的四个阶段
要先搞明白它凭什么能免费发证书,否则后面报错你连该看哪一行日志都不知道。整套东西跑在 ACME 协议上,win-acme 只是这个协议在 Windows 上的一个客户端实现,把「生成密钥 → 提交申请 → 通过域名验证 → 下载证书 → 装进系统」这几步包成了一条流水线。
2.1 四个动作:账户、订单、挑战、签发
把它拆开看,一次完整的签发过程是这样的:
第一步,注册账户。首次运行时它会生成一对账户密钥,用这对密钥向 CA 注册一个账户,并把账户信息连同密钥存到本地。之后每次续期都用这个账户身份,不需要重新注册。密钥文件丢了不影响已签发的证书,但会丢失账户关联,续期时相当于重新开一个户。
第二步,下订单。客户端告诉 CA「我要给 a.example.com 和 b.example.com 签证书」,CA 返回一个订单,订单里包含若干个「授权」(authorization),一个域名一条授权,每条授权里带一个待完成的「挑战」(challenge)。
第三步,完成挑战。这是唯一需要你配合的一步——CA 必须确认你确实控制着这个域名。客户端拿到挑战内容后,按你选的方式把一段特定内容放到指定位置,然后通知 CA「放好了,来查」,CA 从公网去访问验证。验证通过,授权状态变成 valid。
第四步,签发与落盘。所有授权都 valid 之后,客户端生成证书私钥和 CSR,提交给 CA,CA 返回签好的证书链。客户端再按你指定的存储方式写盘:Windows 证书存储、PFX 文件、PEM 文件,或者干脆只放在内存里交给安装步骤。
理解这个链条的价值在于:后面所有报错,都能定位到这四个阶段中的某一个。账户问题的特征是「403 / unauthorized」,订单问题的特征是「域名被拒绝」,挑战问题的特征是「验证失败 / timeout / 404」,落盘问题的特征是「文件没生成 / 服务没换证」。看日志的时候先对号入座,比逐行读要快十倍。
私钥始终在本地生成,不会上传给 CA,这一点从协议层面就保证了。所以别被「免费」两个字吓到,安全模型和商业证书没有区别。
2.2 三种验证方式的取舍,直接决定你会不会卡住
验证方式是整个配置里最需要想清楚的一项,选错了不是慢一点的问题,是根本走不通。常见的有三种:
| 方式 | 原理 | 适用条件 | 典型卡点 |
|---|---|---|---|
| http-01 | CA 访问http://域名/.well-known/acme-challenge/随机串 | 80 端口公网可达,域名已解析到本机 | 80 被占用、被防火墙拦、被 CDN 接管 |
| dns-01 | 在域名下加一条_acme-challengeTXT 记录 | 无法开放 80 端口,或需要通配符证书 | DNS 服务商无 API,只能手动加记录 |
| tls-alpn-01 | CA 在 443 端口用特殊 ALPN 握手 | 80 不通但 443 通 | 前置代理不支持透传 |
我的一般判断顺序是:能开 80 就走 http-01,开不了 80 但有 DNS API 就走 dns-01,两者都不行再考虑 tls-alpn-01。http-01 是最省事的,因为 win-acme 自带一个小型自托管服务器,可以在验证的几十秒内临时占用 80 端口应答,验证完立刻释放,不需要你手动放文件。
dns-01 的坑在于「手动模式」:如果你选手动,它会停下来等你按回车,这在无人值守场景下是致命的——计划任务跑到这一步就永远挂着。所以 dns-01 要么配服务商 API 做自动,要么接受每 60 天人工介入一次。通配符证书(*.example.com)强制要求 dns-01,没有别的路。
提示:如果前面挂着反向代理或 CDN,「http-01 通不通」取决于代理有没有把
/.well-known/路径回源到你的机器。很多验证失败的根因不在 win-acme,而在代理规则只转发了业务路径。
2.3 解压后先认路:哪些文件不能删,哪些随便动
解压出来的目录结构不复杂,但有几个位置必须先认清楚,不然后面排查会抓瞎:
win-acme\ ├── wacs.exe # 唯一可执行文件,所有操作入口 ├── settings.json # 全局配置,首次运行后生成 ├── web\ # 自托管验证用的静态目录,别清空 ├── Certificates\ # 证书输出目录(默认) ├── Scripts\ # 钩子脚本放这里,路径相对引用方便 └── Logs\ # 每次运行的日志,排查全靠它有两点容易被忽略。一是工作目录的迁移规则:如果 exe 放在C:\Program Files\这类需要管理员权限才能写的目录下,win-acme 会自动把工作目录切到%ProgramData%\win-acme,证书和配置就跑到那儿去了,你在 exe 旁边怎么找都找不到。我习惯直接把整个文件夹丢到C:\win-acme\这种普通可写路径,省掉这一层不确定性。
二是settings.json里存着账户密钥的引用和默认参数,迁移机器的时候这个文件要跟着走,光拷 exe 是没用的。日志文件按天切分,出问题先翻最新那个,里面有完整的 HTTP 请求和响应,比界面提示详细得多。
3. 拿到包之后的第一跑:交互向导与静默参数两条路
第一次跑强烈建议走交互式向导,它会把手把手把该填的都问一遍,你也能顺便看清它到底做了哪些事。等跑通一次之后,再改用命令行参数做成脚本,用于批量和重复部署。
3.1 交互式向导:七八个提问,每个都要想清楚
双击wacs.exe(或者在管理员 PowerShell 里执行),会出现一个字符菜单。选「创建新证书」,然后依次面对这些提问:
主机名。输入你要签的域名,多个用逗号分隔。注意这里填的是完全限定域名,不能带http://,也不能带路径。想同时签example.com和www.example.com,就写成example.com,www.example.com,它会签进同一张证书的多 SAN 里,省一张证书的管理成本。
验证方式。菜单里会列出 filesystem、selfhosting、dns、tls-alpn 等选项。本机跑着 IIS 且 80 端口可达,选 selfhosting 最省事;如果 80 被 IIS 占了不想停,选 filesystem 并把 webroot 指到 IIS 站点的物理路径,它会往.well-known\acme-challenge\写文件。
存储方式。三个主要选项:Windows 证书存储(适合纯 IIS 场景,装完直接能在 IIS 绑定里看到)、PFX 文件(适合要分发给其他 Windows 机器)、PEM 文件(适合要给 Nginx / Apache 用)。可以多选,我通常 PEM + 证书存储都勾上,一份给 Windows 用,一份留着备用。
安装方式。如果选了 IIS,它会在签发成功后自动找到对应站点、创建或更新 443 绑定,全程不用你手动导入导出。这是它相对手工方案最大的价值点。
邮箱与条款。邮箱用于到期提醒和账户找回,务必填真实可收信的。条款必须同意,否则流程终止。
最后一问是「是否创建计划任务」,默认是,选是。这一步就是自动续期的全部秘密——它会在 Windows 任务计划程序里建一个每日运行的任务,任务本身不做签发,只在证书临近到期时才真正发起续期。
3.2 无人值守:一条命令把整个流程跑完
跑通一次之后,把交互过程中的选择翻译成参数,就能做出可复制、可纳入部署脚本的命令。下面这条是 http-01 + 文件验证 + 输出 PEM 的组合:
# 以管理员身份运行;--test 使用测试环境,不消耗正式配额 .\wacs.exe ` --target manual ` --host "example.com,www.example.com" ` --validation filesystem ` --webroot "C:\inetpub\wwwroot" ` --store pemfiles ` --pemfilespath "C:\certs\example" ` --accepttos ` --emailaddress "ops@example.com" ` --verbose逐段说明:--target manual表示不自动探测 IIS 站点,全部手动指定,适合非 IIS 托管或者想完全掌控行为的场景;--validation filesystem配合--webroot,把挑战文件写到指定目录的.well-known\acme-challenge\下,目录必须能被 IIS 或其它 Web 服务器以静态文件形式访问到;--store pemfiles加--pemfilespath决定证书落在哪,这个路径必须是运行账户可写的;--accepttos是免交互同意条款,第一次跑必须带;--verbose让日志输出更详细,调试阶段务必加上。
调试阶段还有一条铁律:先加--test跑一遍。--test会切到测试环境,签出来的证书浏览器不信任,但整个流程(账户、订单、验证、落盘)完全一致。这样可以放心地把配置错误试个遍,而不会撞上正式环境的频率限制——那个限制是按域名按周算的,撞上了只能干等,没有任何后悔药。
参数名在不同小版本间偶有调整,拿不准的时候执行.\wacs.exe --help看当前版本的完整列表,比对着旧教程抄要可靠。
3.3 三种落盘形态:证书到底被写成了什么
签发成功后,落到磁盘上的东西取决于你选的存储方式,理解每种产物的用途,才不会出现「证书签成功了但服务没换证」这种乌龙。
Windows 证书存储。证书连同私钥被写进本机的个人存储区,指纹可以在证书管理器里查到。IIS 的绑定直接引用这个指纹,所以换证之后 IIS 会自动用上新证书,不需要重启站点(有时需要回收应用程序池)。缺点是导出麻烦,跨机器分发要额外步骤。
PFX 文件。一个文件打包了证书、中间链和私钥,通常带密码保护。适合分发给其它 Windows 机器,或者导入到不支持分离格式的设备上。win-acme 支持设置 PFX 密码,密码会明文存在配置文件里,所以目录权限要收严。
PEM 文件。输出一组文本文件:证书链、私钥、可能还有单独的证书文件。这套东西是 Nginx、Apache、HAProxy 的标准输入。要注意私钥文件的权限,Windows 上虽然不像 Linux 那样有严格的 600 概念,但也别放在 Web 可访问目录下——这是新手最容易犯的致命错误,私钥被 HTTP 直接下载走等于证书作废。
一个实用的组合是:IIS 站点用证书存储,同时在--store pemfiles里输出一份 PEM 到固定目录,再用钩子脚本把这份 PEM 推到反向代理机器上。一份签发,多处消费。
4. 续期与自动化:任务、钩子、证据链
证书免费不等于免费续期,很多人是在第 90 天收到「证书已过期」告警时才发现自动续期从来没跑成过。这一章讲怎么把它变成真正无人值守的流程,以及怎么验证它真的在跑。
4.1 计划任务是谁建的、什么时候会触发
向导最后一步选「是」,win-acme 会在任务计划程序里注册一个任务,名字形如win-acme renew (...),括号里是它对接的 CA 端点。任务的触发方式是每天一次,但它不是每天都签——每次运行它先读本地证书的到期时间,如果距离到期还有足够余量(默认是剩余 1/3 有效期以内才动手),就直接退出。所以日常运行的代价极小,只是一次本地文件检查。
这里有几个必须知道的细节:
运行账户。任务默认以 SYSTEM 或当前用户运行。如果以 SYSTEM 跑,而工作目录在某个用户目录下,会读不到配置——这是「手动跑能成功、计划任务跑就失败」的头号原因。稳妥做法是让任务以你配置时用的那个账户运行,并且勾选「不管用户是否登录都要运行」。
触发时间。默认时间点往往在凌晨,如果你的服务器在那个时间点是关机状态(比如公司内网的测试机),任务会顺延到下次开机后执行,但有可能错过续期窗口。生产环境建议把机器设成常开,或者把触发时间挪到明确在线的时段。
失败重试。任务本身不带重试逻辑,跑挂了就是挂了,要等第二天。这也是为什么必须有外部监控——不能指望它自己报错给你看。
4.2 用钩子脚本把证书推到别的机器上
单机场景到这儿就结束了。但只要你有第二台机器要用同一张证书,就必须用钩子脚本,因为 win-acme 只管本机落盘,不管分发。
钩子脚本的逻辑很简单:在证书更新的前/后执行一个可执行文件或脚本,把参数传进去。配置方式有两种,交互式向导里可以填,命令行用--script和--scriptparameters。
# C:\win-acme\Scripts\deploy.ps1 # 由 win-acme 在证书更新后调用,接收证书路径与密码 param( [Parameter(Mandatory=$true)][string]$PfxPath, [Parameter(Mandatory=$true)][string]$PfxPassword, [string]$TargetHost = "10.0.0.21" ) # 1. 复制到目标机器的固定目录(需提前配置免密或凭据) Copy-Item -Path $PfxPath -Destination "\\$TargetHost\certs\incoming\" -Force # 2. 远程触发目标机上的导入脚本(目标机需开启远程管理) Invoke-Command -ComputerName $TargetHost -ArgumentList $PfxPassword -ScriptBlock { param($pwd) $pfx = Get-ChildItem "C:\certs\incoming\*.pfx" | Sort-Object LastWriteTime -Descending | Select-Object -First 1 Import-PfxCertificate -FilePath $pfx.FullName -CertStoreLocation Cert:\LocalMachine\My ` -Password (ConvertTo-SecureString -String $pwd -AsPlainText -Force) # 后续可用 netsh 或 IIS 命令更新绑定 } # 3. 退出码非 0 会被 win-acme 记录为钩子失败 exit 0参数说明:$PfxPath和$PfxPassword由 win-acme 注入,对应你配置的 PFX 存储方式和密码;$TargetHost是我自己加的,用默认值方便测试,实际部署时通过--scriptparameters传进去。
这段脚本里最关键的不是复制,是退出码。win-acme 通过退出码判断钩子是否成功,非 0 会在日志里记一条失败,但不会回滚已经签发的证书。也就是说,证书续了,分发挂了,本机是新的,远端还是旧的——这个状态必须靠监控发现,工具帮不了你。
注意:钩子脚本里的密码是明文传递的。如果脚本本身放在 Web 目录或者共享目录里,等于把私钥密码公开了。生产环境应该把脚本目录的 NTFS 权限收到只有运行账户可读。
4.3 续期到底成没成:三处证据链
不要相信「任务显示上次运行成功」这一句话。任务成功只代表脚本退出码是 0,不代表证书真的换了。我一般会看三个地方:
第一处,日志。Logs\目录下按日期命名的文件,搜关键字Renewal和Certificate,能看到它这次判断的剩余天数、是否触发、以及签发的证书指纹。如果日志里写的是「跳过,剩余 XX 天」,那就是正常的——说明还不到时候。
第二处,文件时间戳。直接看 PEM 或 PFX 文件的修改时间。如果这个时间停留在三个月前,而日志说续期成功,那说明落盘路径配错了,证书写到别的地方去了。
第三处,线上实际生效的证书。这是最终裁决。用openssl或者浏览器直接读服务端返回的证书链,看Not After是哪一天。
# 读取远端 443 端口实际返回的证书有效期 openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \ | openssl x509 -noout -subject -dates三处对不上,问题点就基本定位了:日志说成功但文件没变,是存储配置问题;文件变了但线上没变,是安装/绑定/钩子问题;线上变了但浏览器还报错,是中间链缺文件。这个三段式排查法我用了很多年,几乎没失手过。
5. 避坑与排查:五条踩过的血泪记录
下面这五条都是我自己或者身边同事真实撞过的,按「现象 → 原因 → 解决」整理,遇到问题可以对号入座。
5.1 验证一直失败,日志里是 404 或者超时
现象:签发时反复重试,日志显示 CA 访问挑战 URL 返回 404,或者干脆连接超时。
原因:404 说明 Web 服务器能访问但找不到文件,通常是--webroot路径填错了,或者 IIS 的.well-known目录被 URL 重写规则拦掉、被静态文件处理器排除。超时则是 80 端口从公网根本不通——防火墙、安全组、NAT 端口映射三者之一没放行。
解决:先在服务器本地用浏览器访问http://localhost/.well-known/acme-challenge/看能不能命中;再从公网另一台机器用curl -v试同样路径。本地通、公网不通就是网络层;本地都不通就是路径或服务器配置。URL 重写是高频元凶,临时加一条排除规则再试。
5.2 签发成功,但浏览器仍报证书过期或不受信任
现象:日志明确写了证书已签发,指纹也是新的,但访问站点浏览器还在报警。
原因:三种可能——证书没绑到站点(IIS 绑定还指向旧指纹)、绑定了但应用程序池没回收导致进程缓存了旧证书、或者给 Nginx 用的 PEM 文件里缺中间证书,只放了叶证书。
解决:按顺序查。IIS 里看站点绑定的指纹和证书存储里的新指纹是否一致;执行iisreset或回收应用程序池排除缓存;对于 PEM,确认fullchain类型的文件里除了自己的证书还包含中间证书,顺序是叶证书在前、中间证书在后。
5.3 计划任务显示成功,但证书就是没换
现象:任务历史里每次都是「操作成功完成」,但证书文件时间戳几个月没动过。
原因:任务只是执行了 exe,而 exe 判断「还没到续期时间」就正常退出了,退出码 0。这在证书有效期还长的时候是正常的,但如果你已经临近过期还是这样,那就是工作目录不对——任务以 SYSTEM 运行,读的是%ProgramData%\win-acme下的配置,和你手动跑时用的用户目录配置不是同一份。
解决:打开任务属性,看「起始于」这一栏填的目录,和你实际配置所在目录比对。修正为同一个路径,或者干脆重装一次任务。另外把任务改成用你的账户运行、勾选「不管用户是否登录都要运行」,能规避大部分权限问题。
5.4 80 端口被占,自托管验证起不来
现象:选了 selfhosting 方式,日志里报端口绑定失败,验证根本无法开始。
原因:IIS 默认占着 80 端口,Windows 上还有HTTP.sys在内核层做了 URL 预留,普通的 socket 绑定抢不过它。
解决:两条路。一是改用--validation filesystem,不动端口,只往 IIS 的物理目录写文件,让 IIS 自己去应答——这是最省事的。二是确实要用自托管,得先停掉占用 80 的服务,或者用netsh http show urlacl查预留并调整,但生产环境上动这个风险不小,不推荐。
5.5 反复调试把自己锁在频率限制里
现象:调试阶段反复失败重试,突然所有请求都返回「too many certificates」,要等好几天。
原因:正式环境对同一组域名有每周签发次数上限,还有「重复证书」的去重限制。用正式环境做调试,几下就撞墙。
解决:调试阶段一律加--test,用测试环境跑通全流程再切正式。已经撞上了只能等,没有加速手段。养成「任何参数改动都先 test 一遍」的习惯,这一条比什么都管用。
6. 进阶:把证书到期时间接进监控,别等告警变成事故
自动化跑通之后,剩下的风险就只有一个:它在某一天悄悄坏了,而你不知道。自动续期最危险的地方在于,它成功了九十次,你永远不会去看它;它失败一次,你在第九十一天才发现,那时候业务已经挂了。
所以我给每台用 win-acme 的机器都加了一个独立检查,用一个简单的 PowerShell 脚本读线上实际返回的证书剩余天数,低于阈值就报警。这个检查刻意不依赖 win-acme 的任何状态——它检查的是最终结果,不是过程。
# 检查远端 443 实际证书剩余天数,低于阈值返回非 0 退出码 param( [Parameter(Mandatory=$true)][string]$HostName, [int]$Port = 443, [int]$WarnDays = 20 ) try { $tcp = New-Object System.Net.Sockets.TcpClient($HostName, $Port) $ssl = New-Object System.Net.Security.SslStream($tcp.GetStream(), $false, {$true}) # SNI 必须显式传,否则多站点服务器会返回默认站点的证书 $ssl.AuthenticateAsClient($HostName) $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($ssl.RemoteCertificate) $days = [math]::Floor(($cert.NotAfter - (Get-Date)).TotalDays) $ssl.Close(); $tcp.Close() Write-Output "$HostName 剩余 $days 天,到期 $($cert.NotAfter)" if ($days -lt $WarnDays) { exit 1 } else { exit 0 } } catch { Write-Output "连接或握手失败:$($_.Exception.Message)" exit 2 }这段脚本里有三个参数值得说。-HostName必须是证书里的 SAN 之一,否则握手会因名称不匹配失败;-Port在反向代理场景下可能不是 443,看实际暴露端口;-WarnDays默认给 20 天,是留给人工介入的缓冲——从发现到排查到处理,20 天足够,但如果你的团队响应慢,调到 30 更稳。
AuthenticateAsClient($HostName)这一行里的 SNI 参数是重点。省略它,脚本在单站点服务器上照样能跑,但一旦目标机器上挂了多个站点,拿回来的就是默认站点的证书,剩余天数完全对不上,你会得到一堆假警报或者假安心。这个坑我踩过一次,排查了半天才发现证书根本不是同一个。
把这脚本挂到任意监控系统里,用退出码驱动告警:0 正常,1 即将过期,2 连接失败。三态区分开,才不会把「网络抖动」和「证书真的快过期了」混为一谈。
再往上一层,如果机器数量超过十台,手工维护这个列表就不现实了,可以在每台机器上部署这个脚本,把结果推到一个中心化的采集端,做成一张「所有域名剩余天数」的看板。数据来源统一,判断标准统一,比零散的邮件告警靠谱得多。
最后说个我自己的习惯:每次改完配置,不管改的是 webroot 路径还是钩子参数,都强制先跑一遍--test,再手动触发一次续期(--force之类),确认三处证据链全部对上,才算改完。这个动作看起来多余,但它拦住过至少三次「配置写错了但当天没暴露、等到真实续期时才炸」的事故。证书这件事的容错窗口太窄,宁可多花十分钟,也别赌那一次。
希望帮到你。
本文还有配套的精品资源,点击获取