Skip to content

CertiMan CLI 自动化实践:IIS 证书部署与自动续期 ​

CertiMan CLI 生成 PFX 证书文件,部署脚本(Hook)将它导入服务器并替换目标网站的 HTTPS 证书。网站原绑定继续使用,无需重启整个 IIS。

本文接管已有订单和正常运行的 HTTPS 网站,按“接入已有订单 → 确认绑定 → 准备 PFX → 配置 Hook → 测试换证 → 开启计划任务”完成配置。尚未启用 HTTPS 时,请先完成IIS 首次部署指南。

方案是怎样的? ​

在 IIS 服务器上安装 CertiMan CLI,由 Windows 任务计划定期检查证书是否需要更新。需要更新时,CertiMan 从 CertCloud 获取新证书,再调用部署脚本,将证书导入 Windows 并更新指定网站的 HTTPS 绑定。

Windows 计划任务由 CLI 注册。需要写 DNS 验证记录时,CLI 调用 DNS Hook;持久验证记录按订单要求保留。证书交付与网站访客的 HTTPS 请求是两条不同的路径。

首次手动部署验收后再注册或启用 Windows 任务计划。任务定期扫描已开启自动续期的证书,满足续期条件才更新,不是每次扫描都重新签发。

网络放行 ​

IIS 服务器需能通过 HTTPS(443)访问 certiman.trustasia.com、certcloud.cn 和 api.certcloud.cn。使用 DNS 验证脚本时,还需访问 DNS 服务商 API。CLI 安装成功后若连接异常,在同一运行账号下执行 certiman doctor,按诊断说明检查;自定义服务地址以实际配置为准。网站继续使用原 HTTPS 端口。

开始前准备什么? ​

  • 一个已经能正常访问 HTTPS 的 IIS 网站,和 Windows 服务器管理员账号。
  • 网站当前证书对应的 CertCloud 订单,支持“自动交付”。
  • 该证书的 PEM 私钥;如果只有 PFX,按第一步链接的提取方法准备。

若使用 CDN、WAF 或负载均衡,请确认公网证书实际部署在哪个入口。

第一步:接入已有订单并确认材料 ​

使用原订单和匹配私钥接入 CLI,不重新创建网站或订单。

1.1 确认订单并安装 CLI ​

在 CertCloud 打开已有订单,确认支持“自动交付”,无需重复购买。若页面没有对应入口,请联系支持确认。

从订单的“自动交付”进入接入引导,选择“命令行工具 CLI → Windows”。在 IIS 服务器上右键打开 Windows PowerShell,选择“以管理员身份运行”,执行安装命令:

订单接入引导中的 CLI 安装与申请步骤
powershell
irm https://certiman.trustasia.com/install.ps1 | iex

安装完成后重新打开管理员 PowerShell,检查版本:

powershell
certiman version

显示版本号后继续。后面的登录、部署和计划任务都使用这个 Windows 账号;详细安装方法见官方安装说明。

1.2 登录并接入订单 ​

在运行 CLI 的环境中,以前述固定运行账号执行:

powershell
certiman login status

缓存有效且账户正确时无需重新登录;未登录时执行 certiman login。在浏览器中核对账户及应用名称为 CertiMan 后完成授权。终端没有浏览器时,在自己的电脑上打开 CLI 输出的授权地址。

CertiMan 登录授权确认

下面使用 C:\CertiMan 保存私钥和部署脚本。先在当前管理员 PowerShell 中创建目录:

powershell
New-Item -ItemType Directory -Path 'C:\CertiMan' -Force | Out-Null

可通过目录“属性 → 安全”核对访问权限,仅允许运行账号、必要管理员及系统所需账号访问;目录创建命令本身不设置这些权限。后续示例使用此目录,已有其他工作目录时可统一替换。

将网站证书对应的 PEM 私钥放到上述目录。只有 PFX 时,先按文末从 PFX 提取私钥操作,再回到这里。

将 YOUR_ORDER_ID 换成 CertCloud 接入引导中的订单号,将 C:\CertiMan\original-key.pem 换成准备好的私钥路径,执行:

powershell
certiman issue --reuse-order YOUR_ORDER_ID --private-key 'C:\CertiMan\original-key.pem'

已由本机 CLI 管理且材料齐全时不重复导入。确实无法取得匹配私钥时,参见文末找不到原始私钥时重新签发。

1.3 确认订单状态和本地材料 ​

将下面的 trustasia.com 换成订单主域名,查看状态和本地文件。只有 CLI 提示待验证或审核时,才按订单提示完成;需要继续处理时执行 certiman issue -d 订单主域名。

powershell
certiman status -d trustasia.com
Get-ChildItem "$HOME/.certiman/cc/certs/trustasia.com/"

将示例域名替换为订单实际主域名。确认已签发,且目录中已有 fullchain.pem、privkey.pem 和 cert.yaml。目录名由订单主域名决定,请以 CLI 输出为准。

1.4 确认后续验证可自动完成 ​

自动续期时,CertCloud 仍需要验证域名归属。根据订单使用的验证方式,按下面对应的一种处理:

持久验证: 使用 dns-persist 时,按订单提示添加 TXT 记录;使用 dns-proxy 时,添加指向平台的 CNAME 记录。授权有效期间通常可以继续使用这些记录,无需每次续期修改。已经配置好的记录继续保留,不要在首次签发成功后删除,也不要配置会删除它们的清理操作。

普通 DNS 验证: 如果每次签发都需要新的验证记录,就需要让 CertiMan 自动更新 DNS。先按 DNS 服务商配置 DNS 验证 Hook(自动写入验证记录的脚本)和 API 凭据;CertiMan 会将本次记录名、类型和值交给脚本,由脚本调用服务商 API 写入 DNS。具体配置见官方 DNS Hook 示例。确认自动写入可用后,再开启自动续期;仅安装 CertiMan 或配置 IIS 部署脚本,不会自动完成 DNS 验证配置。

第二步:确认目标网站和 HTTPS 绑定 ​

先在浏览器确认网站 HTTPS 正常,再在 IIS 管理器中打开“网站 → 目标网站 → 绑定”,选中要更新的 https 条目并点击“编辑”。只查看和记录,暂不修改:

从哪里取得第四步脚本填写
IIS 左侧列表中的网站名称SiteName
HTTPS 条目的“IP 地址”BindingIP;“全部未分配”填 *
HTTPS 条目的“端口”BindingPort
HTTPS 条目的“主机名”BindingHost,填写域名,不带 https://

本例更新一个已填写主机名、已勾选“需要服务器名称指示(SNI)”的 HTTPS 条目。脚本会检查是否找到唯一目标;配置不符时先按实际绑定确认适用方式,不为了套用脚本改动其他网站绑定。

第三步:配置 PFX 输出与续期选项 ​

本步让 CLI 为已有证书生成 PFX,第四步脚本会把它导入 IIS。

3.1 配置证书输出格式与续期选项 ​

打开证书目录中的 cert.yaml,路径示例:

text
C:\Users\<Windows用户名>\.certiman\cc\certs\trustasia.com\cert.yaml

修改前将本证书的 renew.auto_cert 设为 false,确认没有更新任务正在运行,不影响其他证书的任务。

修改已有字段,保留订单和域名等其他信息,不要重复添加同名节点:

yaml
output:
  formats: [pfx]
  password: '替换为 PFX 密码'
  pfx_file: 'TrustAsia.pfx'
deploy: null
renew:
  auto_cert: false   # 验收通过后再开启
  auto_order: false  # 不自动创建付费续订订单
  • password:填写本次生成 PFX 使用的密码,第四步 PfxPassword 填相同值。
  • pfx_file:生成的文件名,示例为 TrustAsia.pfx,第四步 PfxFileName 填相同值。
  • deploy: null 和 auto_cert: false:准备期间先不执行部署和自动续期。

密码含单引号时,在 YAML 的单引号内写成两个单引号;

保存配置后,在 PowerShell 中执行:

powershell
certiman download -d trustasia.com -f

将 trustasia.com 替换为实际订单主域名。下载完成后,生成的 TrustAsia.pfx 保存在该证书目录下的 converted 文件夹中,路径示例:

text
C:\Users\<Windows用户名>\.certiman\cc\certs\trustasia.com\converted\TrustAsia.pfx

第四步:保存并配置部署脚本 ​

4.1 准备 PowerShell 部署脚本 ​

在同一运行账号的管理员 PowerShell 中,将以下脚本保存为 C:\CertiMan\deploy-iis.ps1,使用第一步已准备的目录和访问权限。修改顶部“用户修改这里”的参数,与第二步记录的网站及绑定一致。Windows PowerShell 5.1 下请以 UTF-8 带 BOM 保存。

脚本从 CLI 生成的目录读取 PFX,导入原绑定使用的“个人”或“Web Hosting”证书存储,再替换第二步选中的网站证书。

powershell
# ================= 用户修改这里 =================

$SiteName     = 'Default Web Site'              # IIS 中的网站名称,不一定是域名
$BindingHost  = 'trustasia.com'                 # HTTPS 绑定中的实际主机名,不能为空
$BindingPort  = 443                             # HTTPS 绑定端口
$BindingIP    = '*'                             # “全部未分配”填 *;指定 IP 则填写实际 IP

$PfxFileName  = 'TrustAsia.pfx'                 # 与 cert.yaml 的 output.pfx_file 一致
$PfxPassword  = '替换为 PFX 密码'              # 与 output.password 一致

# ================================================

$ErrorActionPreference = 'Stop'

try {
    Import-Module WebAdministration -ErrorAction Stop

    # 1. 精确查找已有 HTTPS 绑定
    $bindingInfo = '{0}:{1}:{2}' -f $BindingIP, $BindingPort, $BindingHost

    $bindings = @(
        Get-WebBinding -Name $SiteName -Protocol 'https' |
            Where-Object { $_.bindingInformation -eq $bindingInfo }
    )

    if ($bindings.Count -ne 1) {
        throw "未找到唯一的 HTTPS 绑定:站点 [$SiteName],绑定 [$bindingInfo]。请核对顶部参数。"
    }

    $binding = $bindings[0]

    # 仅支持主机名非空的普通 SNI 绑定
    if ([string]::IsNullOrWhiteSpace($BindingHost) -or [int]$binding.sslFlags -ne 1) {
        throw '本例仅支持主机名非空且 sslFlags=1 的 SNI 绑定。'
    }

    # 同一主机名和端口的其他绑定可能共享 TLS 关联,本例直接拒绝
    $shared = @(Get-WebBinding -Protocol 'https' | Where-Object {
        $_.bindingInformation -like ('*:{0}:{1}' -f $BindingPort, $BindingHost)
    })
    if ($shared.Count -ne 1) {
        throw '该主机名和端口存在其他 HTTPS 绑定,需另行适配。'
    }

    # 沿用原绑定的证书存储,并输出旧状态供人工恢复
    $storeName = [string]$binding.certificateStoreName
    $oldHash = $binding.certificateHash
    $oldThumbprint = if ($oldHash -is [byte[]]) {
        [BitConverter]::ToString($oldHash).Replace('-', '')
    } else { ([string]$oldHash).Replace(' ', '') }
    if ($storeName -notin @('My', 'WebHosting') -or [string]::IsNullOrWhiteSpace($oldThumbprint)) {
        throw '无法确认原证书和存储,未执行部署。'
    }
    Write-Output "原绑定:$SiteName / $bindingInfo;存储:$storeName;指纹:$oldThumbprint"


    # 2. 从 CertiMan 传入的目录获取本次 PFX
    if ([string]::IsNullOrWhiteSpace($env:CERTIMAN_CONVERTED_DIR)) {
        throw '缺少 CertiMan 环境变量,请通过 certiman deploy 调用此脚本。'
    }

    $pfxPath = Join-Path $env:CERTIMAN_CONVERTED_DIR $PfxFileName

    if (-not (Test-Path -LiteralPath $pfxPath -PathType Leaf)) {
        throw "找不到 PFX:$pfxPath。请检查输出配置并重新下载生成。"
    }

    # 3. 使用生成 PFX 时设置的密码
    if ([string]::IsNullOrWhiteSpace($PfxPassword)) {
        throw '请填写 PFX 密码。'
    }

    $securePassword = ConvertTo-SecureString $PfxPassword -AsPlainText -Force

    # 4. 导入前核对 PFX 中的服务器证书及 SAN 域名
    $pfxData = Get-PfxData -FilePath $pfxPath -Password $securePassword
    $leafCertificates = @($pfxData.EndEntityCertificates)
    if ($leafCertificates.Count -ne 1) {
        throw 'PFX 必须包含且仅包含一张服务器证书。'
    }
    $leaf = $leafCertificates[0]
    $sanExtensions = @($leaf.Extensions | Where-Object { $_.Oid.Value -eq '2.5.29.17' })
    if ($sanExtensions.Count -ne 1) {
        throw '证书缺少 SAN 扩展,未执行部署。'
    }
    # 直接解码 SAN,只读取 DNS 项,不回退到 Subject CN
    $san = New-Object -ComObject X509Enrollment.CX509ExtensionAlternativeNames
    $san.InitializeDecode(1, [Convert]::ToBase64String($sanExtensions[0].RawData))
    $idn = New-Object System.Globalization.IdnMapping
    $targetName = $idn.GetAscii($BindingHost.TrimEnd('.')).ToLowerInvariant()
    if ($targetName.Contains('*')) { throw '绑定主机名必须为具体域名。' }
    $matched = $false
    foreach ($dnsName in $san.AlternativeNames) {
        if ([int]$dnsName.Type -ne 3) { continue }
        $name = [string]$dnsName.StrValue
        if ([string]::IsNullOrWhiteSpace($name)) { continue }
        $name = $name.TrimEnd('.').ToLowerInvariant()
        if ($name.StartsWith('*.')) {
            $name = '*.' + $idn.GetAscii($name.Substring(2)).ToLowerInvariant()
        } else { $name = $idn.GetAscii($name).ToLowerInvariant() }
        if ($name.StartsWith('*.')) {
            # 通配符只匹配一个标签,不匹配根域名或多级子域名
            $suffix = $name.Substring(2)
            $labels = $targetName.Split('.')
            if ($labels.Count -eq $suffix.Split('.').Count + 1 -and
                $targetName.EndsWith('.' + $suffix)) { $matched = $true }
        } elseif ($targetName -eq $name) { $matched = $true }
    }
    if (-not $matched) { throw '证书 SAN 不覆盖目标主机名,未执行部署。' }
    $now = Get-Date
    if ($leaf.NotBefore -gt $now -or $leaf.NotAfter -le $now) {
        throw '证书尚未生效或已经过期,未执行部署。'
    }

    # 5. 导入原绑定使用的本机证书存储
    $certificates = @(
        Import-PfxCertificate -FilePath $pfxPath `
            -CertStoreLocation ("Cert:\LocalMachine\{0}" -f $storeName) `
            -Password $securePassword |
            Where-Object { $_.HasPrivateKey }
    )

    if ($certificates.Count -ne 1) {
        throw 'PFX 中应包含且仅包含一张带私钥的服务器证书。'
    }

    $certificate = $certificates[0]
    $now = Get-Date

    if ($certificate.NotBefore -gt $now -or $certificate.NotAfter -le $now) {
        throw '证书尚未生效或已经过期,未更新 IIS 绑定。'
    }

    # 核对导入结果与刚验证的证书一致,再更新原绑定
    if ($certificate.Thumbprint -ne $leaf.Thumbprint) {
        throw '导入结果与已验证证书不一致,未更新绑定。'
    }
    $binding.AddSslCertificate($certificate.Thumbprint, $storeName)

    Write-Output (
        '部署成功:站点 [{0}],绑定 [{1}],指纹 [{2}],有效期至 [{3}]' -f
        $SiteName, $bindingInfo, $certificate.Thumbprint, $certificate.NotAfter
    )
}
catch {
    [Console]::Error.WriteLine('部署失败:' + $_.Exception.Message)
    exit 1
}
  • SiteName、BindingIP、BindingPort、BindingHost:填写第二步表格记录的值。
  • PfxFileName、PfxPassword:填写第三步 pfx_file 和 password 的值。密码含单引号时,在 PowerShell 单引号字符串内写成两个单引号。脚本也使用第一步目录的访问权限。

4.2 配置部署 Hook ​

确认脚本参数后,将 cert.yaml 中的 deploy: null 替换为以下配置并保存:

yaml
deploy: 'powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\CertiMan\deploy-iis.ps1'

-File 后填写刚保存的脚本完整路径,路径包含空格时用双引号括起来;使用示例路径时可直接保留。执行部署和注册计划任务仍需使用同一账号的管理员 PowerShell。保存配置后进入第五步。

第五步:在已有绑定上试部署并检查网站 ​

记下第二步目标绑定当前选中的“SSL 证书”,保留该证书,以便异常时重新选回。在同一账号的管理员 PowerShell 中执行,把 trustasia.com 换成第一步的订单主域名:

powershell
certiman deploy -d trustasia.com

若提示已部署而跳过,可加 -f 再次部署本地证书:

powershell
certiman deploy -d trustasia.com -f

看到“部署成功”且命令没有报错后,回到第二步的绑定窗口,点击所选证书旁的“查看”,确认其域名和有效期与本次部署的证书一致,IP、端口和主机名仍是原值。使用同一张证书接入时,有效期不会变化。

再在自己的电脑上用浏览器打开网站,例如 https://trustasia.com/。域名换成第二步的“主机名”;端口不是 443 时加上原端口,例如 https://trustasia.com:8443/。

  1. 确认打开的是自己的网站,浏览器没有“连接不安全”或证书错误提示。
  2. 点击地址栏左侧的网站信息按钮查看证书,确认域名正确、有效期与 CertCloud 订单中本次部署的证书一致。
  3. 浏览常用页面,确认图片、样式和登录等功能正常。

使用 CDN 等代理或怀疑仍显示旧证书时,按证书检查说明核对 IIS 上的证书。

IIS 部署后的访问验证

检查通过后继续第六步;有错误时先按本文异常恢复处理,脚本不会自动选回旧证书。

第六步:按需开启自动续期 ​

第五步验收通过后,修改原 renew 节点,保留已验证的 Hook:

yaml
renew:
  auto_cert: true
  auto_order: false

在安装和授权时的同一账号下,以管理员身份打开 PowerShell。尚未注册任务时执行 install;已注册时无需重复安装:

powershell
certiman cron install
certiman cron status

在 Windows“任务计划程序”中确认 certiman-cron 的运行账号、触发计划和权限,确认用户注销后仍能运行。计划任务由系统定期执行,不需要长期打开终端;已有企业任务时保留一个执行来源。详见计划任务注册说明。

CertiMan 注册计划任务及状态

任务已启用不等于自动续期已验证。未到续期时间时不会立即换证;首次真实续期后,按第五步确认网站已使用新证书。

从 PFX 提取私钥 ​

只有 PFX、没有单独 PEM 私钥时使用本节;已经有私钥的读者跳过。Windows 默认不自带 OpenSSL,没有该工具时可以从原 KeyManager 环境导出 PEM 私钥。

在能运行 openssl 的管理员 PowerShell 中,修改下面两个变量并执行。输出目录使用第一步已准备的 C:\CertiMan,改用其他目录时需先准备好:

powershell
$SourcePfx = 'C:\CertificateFiles\TrustAsia.pfx'
$OutputKey = 'C:\CertiMan\original-key.pem'

& {
    $ErrorActionPreference = 'Stop'
    if (Test-Path -LiteralPath $OutputKey) { throw '输出文件已存在,请另选路径,避免覆盖原私钥。' }
    if (-not (Test-Path -LiteralPath (Split-Path -Parent $OutputKey) -PathType Container)) {
        throw '请先准备仅运行账号及必要管理员可访问的输出目录。'
    }
    openssl pkcs12 -in $SourcePfx -nocerts -nodes -out $OutputKey
    if ($LASTEXITCODE -ne 0) { throw '私钥提取失败,请检查 PFX 文件及密码;不要导入失败产物。' }
    openssl pkey -in $OutputKey -check -noout
    if ($LASTEXITCODE -ne 0) { throw '私钥检查失败,停止导入。' }
    Write-Output "私钥已保存到:$OutputKey"
}
  • SourcePfx:现有 PFX 的完整路径。
  • OutputKey:提取后 PEM 私钥的保存路径,不能覆盖已有文件。

按提示输入 PFX 密码,看到“私钥已保存到……”后,回到 1.2,将该路径填入 --private-key 导入。输出为未加密 PEM 私钥;确实无法取得匹配私钥时再看下一节。

找不到原始私钥时重新签发 ​

仅在无法找回匹配私钥时使用。重颁发可能使旧证书失效,操作前安排换证时间,准备及时生成新 PFX、更新绑定并验收。

首次接管订单时执行:

powershell
certiman issue --reuse-order YOUR_ORDER_ID --on-existing-cert reissue

已由本机 CLI 管理时,改用 certiman issue -d trustasia.com --on-existing-cert reissue,域名替换为订单主域名。两种方式按接入状态选择一种。签发后返回第三至第五步,生成 PFX、部署并验收。

后续维护与异常恢复 ​

定期查看任务状态:

powershell
certiman cron status

遇到问题时,可检查运行账号下 .certiman\logs 中的日志。若使用 Hook 且证书已下载、但 IIS 部署失败,修复原因后重新执行 certiman deploy -d trustasia.com,无需重新下单。

auto_order: false 不会自动创建付费续订订单,请在服务期结束前安排续费,并及时处理审核或域名验证提示。

升级方法参阅 CertiMan 官方文档。

异常恢复 ​

先将对应 cert.yaml 的 renew.auto_cert 改为 false,确认更新任务已结束。若错误发生在更新绑定之前,原绑定未改变,修正参数、密码或材料后重新部署即可。

如果绑定已更新但访问异常,在 IIS 管理器中选择原网站,打开“绑定”,编辑同一个 HTTPS 条目,将“SSL 证书”重新选择为更新前仍有效且含私钥的旧证书。保持 IP、端口、主机名及 SNI 不变;旧证书及存储可按 Hook 输出的原绑定记录核对。不要删除原证书或重建网站。

旧证书不在计算机存储中时,先通过服务器节点的“服务器证书 → 导入”导入保留的旧 PFX,选择与原绑定相同的“个人(My)”或“Web Hosting”存储,再恢复绑定。随后按第五步验证实际域名及业务。旧证书已过期或已失效时不能用于恢复,应及时部署有效的新证书。问题解决并重新部署验收通过后,再开启自动续期。

停用自动化 ​

只停用此证书时,将 renew.auto_cert 改为 false,删除 deploy 字段,保留其他配置。其他证书仍需自动更新时,不卸载共用任务;当前账号全部证书都不再需要任务时,在管理员 PowerShell 中执行 certiman cron uninstall,再用 certiman cron status 确认。停用不会删除 IIS 已部署证书或网站绑定,后续换证需自行安排。

卸载 CertiMan(不再使用时) ​

如不再使用 CertiMan,请以安装和配置任务时的账号打开管理员 PowerShell,执行官方卸载脚本,并按提示确认:

powershell
irm https://certiman.trustasia.com/uninstall.ps1 | iex

卸载脚本会移除 CertiMan 注册的计划任务和程序,默认保留 %USERPROFILE%\.certiman 中的证书、私钥及授权数据。卸载后不再由该任务自动续期,请自行安排后续证书更新。详见官方卸载指引。