win-acme pluggable:IIS证书自动签发续期

发布时间:2026/11/4 3:31:22
win-acme pluggable:IIS证书自动签发续期

简介:这是一份面向Windows平台网站管理员与系统集成商的免费SSL证书自动化工具包,主要解决Let's Encrypt等证书的申请、部署与续订问题,适合使用IIS、nginx等Web服务器且希望降低证书管理成本的项目。压缩包共28个文件、约35.58MB,以21个PowerShell脚本和2个批处理文件为主,辅以可执行程序、JSON配置、XML模板及版本说明等,脚本覆盖IIS、Exchange、RDS、Azure应用代理等多种证书导入与部署场景,便于按实际环境组合使用。已有404人学习,说明其在证书自动化领域具有一定参考价值。解压后可通过核心可执行程序完成证书申请与更新,配合默认配置和脚本实现较自动化的安装部署;对官方渠道下载较慢的用户,也提供了更便捷的获取方式。整体适合需要快速上手免费SSL、并希望了解自动续订与多场景部署思路的读者。

1. 一个 zip 包背后:win-acme 的 pluggable 构建在 Windows 上到底替谁干活

拿到 win-acme.v2.2.9.1701.x64.pluggable.zip 这个包,多数人的第一反应是解压、双击 wacs.exe、跟着向导一路点下一步。这个包里装的是 Windows 上的 ACME 客户端 win-acme 的 x64 插件版构建,版本号 2.2.9.1701 决定了命令行参数和插件接口的具体形态,pluggable 后缀意味着它把 IIS、Exchange、若干 DNS 与存储插件一起打了进来,而不是那个精简到只能做基础验证的 trimmed 包。它干的活很具体:向公共 ACME 目录申请证书、把证书装进 IIS 或 Windows 证书存储、再挂一个计划任务做自动续期。适合手里管着几台 Windows 服务器、IIS 站点证书快到期、又不想每年手工重签的人。下面按“包怎么放、命令怎么发、插件怎么接、坑在哪”四步走,全部能在测试机上复现。

2. 解压之前先看清楚:pluggable 包的目录、依赖与初始化

2.1 x64 pluggable 包和 trimmed 包差在哪,什么时候必须选前者

同一个版本号下通常有两个构建:pluggable 和 trimmed。两者核心的 wacs.exe 是同一套,差别在随包发布的插件程序集和依赖库。判断标准很简单:命令里只要出现--source iis、--source exchange,或者要用某个 DNS 验证插件、某个集中存储插件,就必须用 pluggable;如果只是给单台机器签一张 pem 或 pfx 文件,trimmed 更轻,出问题的面也小。

对比项pluggable 包trimmed 包
随包插件IIS、Exchange、多种 DNS 与存储插件仅核心,插件需另行补齐
目录体积较大较小
--source iis/--source exchange直接可用缺程序集时加载失败
适用场景多站点、自动绑定、DNS 验证手工验证、只输出证书文件

x64 与 x86 的差别在进程位数,不是“新一点就好”。IIS 管理库对位数敏感,32 位进程去加载 64 位的Microsoft.Web.Administration会直接抛BadImageFormatException,日志里看起来像插件坏了,其实是位数不匹配。服务器上统一用 x64 构建,能少掉一整类玄学问题。另外,pluggable 包解压后不要和旧版本目录混放,插件程序集版本对不上时,win-acme 启动阶段就会报找不到方法,而不是等到签发才报错。

2.2 用 PowerShell 解压并固定到不带空格的目录

解压这一步看着没技术含量,实际是两个坑的高发区:路径带空格或中文,以及解压工具把目录层级多套了一层。win-acme 写计划任务时会把可执行文件路径拼进命令行,路径里有空格而没加引号,任务执行时就是“系统找不到指定的文件”。我一般固定放到C:\tools\win-acme,短、无空格、机器级可见。

$zip = "D:\downloads\win-acme.v2.2.9.1701.x64.pluggable.zip" $dest = "C:\tools\win-acme" New-Item -ItemType Directory -Path $dest -Force | Out-Null Expand-Archive -LiteralPath $zip -DestinationPath $dest -Force # 确认 wacs.exe 在根层,而不是多套了一层目录 Get-ChildItem $dest | Select-Object Name, Length # 与获取渠道公布的 SHA256 比对,别跳过 (Get-FileHash -LiteralPath $zip -Algorithm SHA256).Hash

Expand-Archive对超长路径不友好,目标目录别超过两三级;-Force会覆盖同名文件,升级前先把旧目录改名备份,出问题能整目录回滚。Get-ChildItem的输出里必须能看到wacs.exe在根层,如果看到的是又一层win-acme目录,说明压缩包自带外层文件夹,后面所有命令的相对路径都要跟着改。哈希比对不是形式主义,这类工具会接触私钥和证书存储,包在传输中被替换的代价太高。

2.3 第一次运行:settings.json 里必须确认的三个路径

win-acme 首次运行会在程序目录生成settings.json,里面控制配置、日志、缓存放哪。默认值在单机试用时够用,一旦挂进计划任务、以 SYSTEM 账户运行,默认路径落在用户配置文件下就会出问题。常见做法是先把三个路径改成机器级目录,再开始签发。

$settingsPath = "C:\tools\win-acme\settings.json" # 首次运行任意命令会生成默认 settings.json;--version 不会触发交互 if (-not (Test-Path $settingsPath)) { & "C:\tools\win-acme\wacs.exe" --version | Out-Null } $settings = Get-Content $settingsPath -Raw | ConvertFrom-Json $settings.ClientName = "ops-win-acme" $settings.ConfigurationPath = "C:\tools\win-acme\config" $settings.LogPath = "C:\tools\win-acme\logs" $settings.CachePath = "C:\tools\win-acme\cache" $settings | ConvertTo-Json -Depth 10 | Set-Content $settingsPath -Encoding UTF8

ClientName是日志和续期任务里的标识,多套环境共用一个模板时靠它区分;ConfigurationPath存每张证书的续期配置,这是最该备份的目录,丢了就等于每张证书都要重新走一遍申请;LogPath是排错的唯一入口,ACME 交互的每一步都写在这里;CachePath存账户信息和验证缓存,删掉后要重新注册 ACME 账户,换邮箱时会用到。四个键之外的配置保留默认即可,改多了反而记不住自己动过什么。

3. 第一张证书:win-acme 命令行参数逐项拆开

3.1 --source、--validation、--store 三者的组合逻辑

win-acme 的命令行可以拆成三段:从哪找目标(--source)、怎么证明域名归你(--validation)、签完存到哪(--store)。三段各自独立,组合错了不会报语法错误,而是走到一半卡在验证或安装阶段。先把验证方式选对,后面才谈得上自动化。

验证方式关键参数依赖端口前置条件适用场景
HTTP 文件--validation filesystem --webroot80webroot 可写,IIS 能返回.well-known下的无扩展名文件IIS 站点已对外
自托管--validation selfhosting8080 端口能被 win-acme 临时监听单机、IIS 可短停
TLS-ALPN--validation tls-alpn-01443443 能被临时接管80 不可用但 443 可支配
DNS--validation dns无DNS 接口或脚本能写 TXT 记录泛域名、内网、80 被封

IIS 站点对外、80 可达,filesystem最省心,它只往 webroot 写文件,不抢端口;selfhosting会临时占用 80,IIS 正在跑就容易翻车;tls-alpn-01要接管 443,和 IIS 的绑定冲突更直接;泛域名只有dns-01能签,绕不开脚本或插件。--source则决定目标是手工指定还是自动发现:manual靠--host显式给域名,iis会枚举本机站点让你选,exchange走 Exchange 相关插件。

3.2 一条可以照抄的签发命令:给 IIS 站点签单域名证书

下面这条命令是测试机上跑通的最小组合:手工指定域名、写文件验证、输出 pfx。复制后把域名、邮箱、路径换成自己的即可。

cd C:\tools\win-acme .\wacs.exe ` --source manual ` --host shop.example.com ` --friendlyname "shop.example.com" ` --validation filesystem ` --webroot "C:\inetpub\wwwroot" ` --store pfxfile ` --pfxfilepath "C:\certs\shop" ` --pfxpassword "ChangeMe-2024" ` --accepttos ` --emailaddress "ops@example.com" ` --verbose
参数含义什么时候改
--source manual手工定义目标站点多、要自动枚举时换iis
--host要签的域名多域名重复写--host,以--help为准
--friendlyname证书显示名多张证书时用它区分,别用默认名
--validation filesystem写文件到 webroot换 DNS 时改dns
--webrootIIS 站点物理路径必须和站点根一致,不是虚拟目录
--store pfxfile输出 pfx 文件要自动绑 IIS 时改用证书存储加安装插件
--pfxfilepathpfx 输出目录目录必须已存在,win-acme 不建父目录
--pfxpasswordpfx 密码明文会进 PowerShell 历史,生产环境慎用
--accepttos接受服务条款首次必须,之后可省
--emailaddress到期通知邮箱用值班邮箱,别用个人邮箱

--verbose不是可选装饰,它会把 ACME 交互的请求和响应都写进日志,第一次跑务必带上,成功之后再从计划任务里去掉。--pfxfilepath指向的目录要提前建好,命令不会帮你创建,报错信息又只写“路径无效”,很容易看漏。密码直接写在命令行会留在 PowerShell 历史里,我一般把整条命令封进一个 ACL 收紧的脚本,密码从环境变量读,再让计划任务调用脚本。

3.3 先用 --test 跑一遍再正式签,避免触发速率限制

公共 ACME 目录对同一域名的失败次数和重复签发都有速率限制,反复试错会把配额耗光,之后几个小时都签不出来。win-acme 提供--test,走测试环境,签出来的证书不被浏览器信任,但验证链路、文件写入权限、DNS 传播这些环节都能验证到。

# 测试环境跑一遍,确认验证链路通 .\wacs.exe ` --source manual ` --host shop.example.com ` --validation filesystem ` --webroot "C:\inetpub\wwwroot" ` --store pfxfile ` --pfxfilepath "C:\certs\shop-test" ` --accepttos ` --emailaddress "ops@example.com" ` --test ` --verbose # 正式签发后,再验证一次续期路径能否走通 .\wacs.exe --renew --force --verbose

--test和正式命令的唯一差别就是多这一个开关,其他参数完全一致,这样才能保证测试覆盖的是同一条路径。正式签完之后立刻用--renew --force强制走一次续期,比等到 60 天后再发现问题强得多;--force会忽略到期时间,直接执行续期流程,日志里能看到配置读取、验证、存储三个阶段的完整输出。这一步跑通,后面的计划任务基本不会出大问题。

4. 插件模式落地:IIS 绑定、DNS 脚本与证书落盘权限

4.1 让 --installation iis 自动改绑定,而不是手工导入

pfx 输出到目录只是把证书拿到手,IIS 站点还是旧证书在跑,到期照样报警。pluggable 包的价值就在这里:--installation iis插件会在签发和续期后自动把证书绑到对应站点,省掉“打开 IIS 管理器、导入 pfx、改绑定”这套手工流程。

.\wacs.exe ` --source iis ` --host shop.example.com ` --validation filesystem ` --webroot "C:\inetpub\wwwroot" ` --store certificateStore ` --certificatestore My ` --installation iis ` --accepttos ` --emailaddress "ops@example.com" ` --verbose

--source iis会枚举本机站点,交互模式下列出站点让你选;非交互场景要补站点标识参数,具体名称随小版本略有差异,用wacs.exe --help查当前版本支持的写法。--store certificateStore --certificatestore My把证书装进“本地计算机”的个人存储,--installation iis再从存储里取出来绑定。站点有多个绑定(80/443、多域名)时,--host必须和绑定里的域名完全一致,否则插件可能绑到同名的另一个站点上。装完去 IIS 里看站点的 443 绑定,证书显示名应该是--friendlyname指定的值,不是一长串哈希。

4.2 DNS-01 用插件还是脚本:--dnscreatescript 的接线方式

没有现成插件、或 DNS 服务只暴露了 REST 接口时,就走外部脚本。协议很简单:win-acme 在验证前调用创建脚本写 TXT 记录,验证后调用删除脚本清理。脚本要幂等,重复创建不报错,删除找不到记录也要正常返回。

# dns-hook.ps1:创建/删除 _acme-challenge TXT 记录 param( [Parameter(Mandatory=$true)][string]$Identifier, [Parameter(Mandatory=$true)][string]$RecordValue, [Parameter(Mandatory=$true)][string]$Action ) $apiBase = "https://dns.api.example.internal/v1" # 换成实际 DNS 服务地址 $token = $env:DNS_API_TOKEN if ($Action -eq "create") { $body = @{ name = "_acme-challenge.$Identifier" type = "TXT" value = $RecordValue ttl = 60 } | ConvertTo-Json Invoke-RestMethod -Method Post -Uri "$apiBase/records" ` -Headers @{ Authorization = "Bearer $token" } ` -Body $body -ContentType "application/json" } elseif ($Action -eq "delete") { Invoke-RestMethod -Method Delete ` -Uri "$apiBase/records?name=_acme-challenge.$Identifier&type=TXT" ` -Headers @{ Authorization = "Bearer $token" } }

调用时用--validation dns --dnscreatescript和--dnsdeletescript分别指向脚本,部分小版本合并成单个--dnsscript加动作参数,先用--help确认当前版本的实际参数名。TTL 设 60 是底线,缓存没过去就重试只会一直失败;等待时间有的版本用--dnssleep控制,没有的话就在脚本里轮询权威 DNS,直到能查到记录再返回。脚本里的令牌从环境变量读,不要硬编码进文件,脚本本身的 ACL 也要收紧。

4.3 证书私钥的 ACL:别让 IIS 应用池读不到

pfx 和私钥文件默认继承父目录权限,父目录如果是管理员个人目录,IIS 应用池账户读不到,绑定的时候就会报“证书不存在或无法访问”。这不是证书问题,是 ACL 问题。我一般把证书目录单独建在机器级路径下,先断开继承,再按最小权限授。

$certDir = "C:\certs\shop" icacls $certDir /inheritance:r icacls $certDir /grant:r "BUILTIN\Administrators:(OI)(CI)F" icacls $certDir /grant:r "NT AUTHORITY\SYSTEM:(OI)(CI)F" icacls $certDir /grant:r "IIS_IUSRS:(OI)(CI)R"

/inheritance:r去掉从上级继承来的权限,避免父目录给 Everyone 读时把私钥一起放开;IIS_IUSRS只给读权限,应用池用自定义账户时换成那个账户;SYSTEM必须有权限,因为计划任务默认以 SYSTEM 运行,续期时要往这个目录写新文件。给完权限用icacls $certDir复查一遍,确认没有多余的 Everyone 或 Users 条目。pfx 密码存在脚本里是常见做法,但脚本本身也要限制读取权限,否则等于把私钥裸奔。

5. 避坑与排查:win-acme 在 Windows 上最容易翻车的五个点

5.1 验证阶段的三个坑:404、端口占用、DNS 不生效

现象一:--test时日志里 challenge 文件返回 404,但文件明明已经写到 webroot 下了。原因多半是 IIS 不给无扩展名文件返回内容,.well-known/acme-challenge/下的文件名没有后缀,静态内容模块的 MIME 映射里找不到对应类型,直接 404。解决办法是在站点web.config里补一条映射:<staticContent><mimeMap fileExtension="." mimeType="text/plain" /></staticContent>,再确认.well-known是真实目录而不是重写规则拦掉的路径。改完用浏览器直接访问 challenge 地址,能看到那串字符才算通。

现象二:--validation selfhosting报拒绝访问或端口已被占用。原因是 IIS 的站点正占着 80,win-acme 想临时监听同一个端口,Windows 的端口共享和 URL ACL 都不允许。解决要么停掉站点再跑,要么干脆换filesystem写 webroot,让 IIS 自己去响应验证请求。生产机上不要反复停启站点来迁就验证方式,这个操作本身就是风险;tls-alpn-01在 443 上也是同样的道理,冲突更直接。

现象三:DNS 验证一直停在 pending,日志里反复查 TXT 记录查不到。常见原因是_acme-challenge做了 CNAME 委托,但脚本把记录写在了原域名下,而不是委托目标下;或者 TTL 设得太大,权威 DNS 已经更新,递归解析器还在返回旧缓存。解决是先Resolve-DnsName -Type TXT手工确认记录位置,把 TTL 降到 60,等权威 DNS 生效后再重试。连续点重试没有意义,只会把失败次数堆上去触发速率限制。

5.2 续期与权限的两个坑:任务成功但证书没换、SYSTEM 读不到路径

现象四:计划任务历史显示执行成功,IIS 绑定的还是旧证书。这里最容易误判,任务返回 0 只代表进程正常退出,不代表证书换了。原因通常是续期命令少了--installation iis,或者ConfigurationPath被改过,续期时找不到首次签发的配置,走了另一条默认路径。排查方法是先看config目录下有没有对应域名的续期配置文件,再手工跑一次--renew --force --verbose,日志里会明确写出“读取配置”“验证”“安装”三个阶段,缺哪段一眼就能看出来。

现象五:手工跑没问题,计划任务一跑就报路径不存在或写文件失败。根因是路径落在用户配置文件下(C:\Users\...),计划任务以 SYSTEM 运行时访问不到;或者输出目录的 ACL 只给了当前登录用户。解决是把pfxfilepath、webroot、ConfigurationPath全部挪到C:\ProgramData或C:\tools这类机器级路径,ACL 里补上SYSTEM和应用池账户。改完用Get-ScheduledTask找到续期任务,看它的“操作”里实际命令行,确认路径和手工跑的一致。

5.3 一段能一次性收集现场信息的排查脚本

出问题时最怕只看一句报错就猜。下面这段把最近三次日志的关键行、续期任务状态、证书目录权限一次性拉出来,比逐条命令翻要快得多。

$root = "C:\tools\win-acme" Get-ChildItem "$root\logs" -Filter *.log | Sort-Object LastWriteTime -Descending | Select-Object -First 3 | ForEach-Object { Write-Host "=== $($_.FullName) ===" -ForegroundColor Cyan Select-String -Path $_.FullName ` -Pattern "error|fail|exception|challenge|renew" ` -CaseSensitive:$false | Select-Object -Last 20 } Get-ScheduledTask | Where-Object { $_.TaskName -like "*win-acme*" } | Get-ScheduledTaskInfo | Select-Object TaskName, LastRunTime, LastTaskResult, NextRunTime icacls "C:\certs\shop"

日志按时间倒序取最近三次,关键字过滤能把 ACME 交互里的关键行拎出来,不用整篇读;LastTaskResult非 0 才是任务失败,0 只说明进程退出正常,证书换没换还得看 IIS 绑定;icacls放最后,确认私钥目录没有被意外放开。这套组合我用了很久,比一上来就重装工具有效。

6. 无人值守的最后一步:用 --renew 加日志关键字验证续期真的生效

签发成功只是开始,真正决定这套方案值不值得投入的是续期环节。我的习惯是升级 win-acme 之后、或者改过任何参数之后,立刻手工触发一次强制续期,并在日志里做关键字断言,而不是等 60 天后靠到期告警来发现问题。

$wacs = "C:\tools\win-acme\wacs.exe" $out = "$env:TEMP\renew-check.log" # 强制续期,忽略到期时间,输出完整日志 & $wacs --renew --force --verbose 2>&1 | Tee-Object -FilePath $out # 关键字断言,措辞随版本不同,先跑一次看实际输出再固定 $log = Get-Content $out -Raw if ($log -match "0 renewals failed" -or $log -match "Renewal completed") { "renew ok" } else { "check log: $out" }

--renew只处理已存在的续期配置,不会重新交互;--force让它忽略剩余天数直接执行;--verbose保证验证和安装阶段的细节都落盘。断言字符串不要照抄,不同小版本的措辞有差异,先用--verbose跑一次,看日志里成功那行到底怎么写的,再把关键字固定下来。这一步做完,再去 IIS 里核对 443 绑定的证书指纹,和证书存储里的新证书对得上,才算闭环。

日志关键字含义正常表现
Renewal续期流程入口能看到读取配置的记录
challenge验证交互验证文件写入或 TXT 记录创建成功
installed/installation安装阶段证书写入存储或绑定更新
renewals failed失败计数0 才算全部成功

我自己的血泪经验是:每次升级前先把settings.json和整个config目录打包备份,升级后立刻跑一遍强制续期。最省事的后悔药就是这份可回滚的配置——工具升级出问题,把目录换回去就能恢复,不用重新申请一遍证书。希望帮到你。

本文还有配套的精品资源,点击获取