Skip to content

部署示例 ​

将证书部署到不同服务的 deploy hook 示例。用法与通用注意事项见 完整示例 · 概览。

示例脚本仅供参考,使用后果由使用者自行承担

这些脚本是参考模板,不是 CertiMan 产品的一部分。请根据自身业务、系统、安全策略充分调整并用 certiman hook test 试运行;使用导致的任何损失由使用者承担,TrustAsia 不承担相关责任。完整免责与凭证建议见 概览。

脚本适用要点
nginxnginxnginx -t 校验后 reload;失败自动回滚旧证书
apacheApache / httpdconfigtest 后 graceful;自动识别 RHEL/Debian 命令名
systemd(通用)dovecot / postfix / mosquitto 等任意 systemd 服务修改 RELOAD_UNIT 即可切换服务;reload 失败时改用 restart
Windows IISIIS导入 PFX 并换绑;前提是 output.formats: [pfx] + shell: powershell

nginx ​

sh
#!/bin/sh
# certiman deploy hook:把新证书装到 nginx 并 reload。
#
#   cert.yaml:  deploy: /home/<用户名>/.certiman/hooks/nginx.sh
#   试运行:       certiman hook test -d <域名> --step deploy
set -eu

# ── 改这里 ────────────────────────────────────────────────────────────────
# 这两个路径必须与 nginx 配置里 ssl_certificate / ssl_certificate_key
# 两行写的完全一致。CERTIMAN_CERT_NAME 是证书目录名(主域名派生,通配符的 * 换成了 _)。
TARGET_DIR="/etc/nginx/ssl"
TARGET_CERT="${TARGET_DIR}/${CERTIMAN_CERT_NAME}.pem"
TARGET_KEY="${TARGET_DIR}/${CERTIMAN_CERT_NAME}.key"
RELOAD_UNIT="nginx"
# ──────────────────────────────────────────────────────────────────────────

# CERTIMAN_CERT_PATH 给的是 fullchain.pem(叶子 + 中间链),nginx 要的正是它。
# 名字容易让人以为是叶子证书——换成 cert.pem 会让一部分客户端握手失败,
# 而桌面浏览器通常不报错,移动端访问时才会发现。

# 失败要能回到原状:证书已经落盘之后 nginx -t 才报错的话,文件已经是新的了,
# 下一次 reload(哪怕是别人因为别的改动触发的)就会让服务带着没验过的证书起来。
#
# 一律用 if 而不是 `[ -f x ] && cp`:后者在文件不存在时整条语句返回 1,
# set -e 会立即终止脚本:备份阶段将无实际效果,还原阶段则导致私钥未被还原。
restore() {
    if [ -f "${TARGET_CERT}.certiman-bak" ]; then
        mv "${TARGET_CERT}.certiman-bak" "$TARGET_CERT"
    fi
    if [ -f "${TARGET_KEY}.certiman-bak" ]; then
        mv "${TARGET_KEY}.certiman-bak" "$TARGET_KEY"
    fi
    echo "部署失败,已还原 ${TARGET_CERT}" >&2
}

install -d -m 755 "$TARGET_DIR"
if [ -f "$TARGET_CERT" ]; then
    cp -p "$TARGET_CERT" "${TARGET_CERT}.certiman-bak"
fi
if [ -f "$TARGET_KEY" ]; then
    cp -p "$TARGET_KEY" "${TARGET_KEY}.certiman-bak"
fi
trap restore EXIT

install -m 644 "$CERTIMAN_CERT_PATH" "$TARGET_CERT"
install -m 600 "$CERTIMAN_KEY_PATH" "$TARGET_KEY"

# 先校验后 reload。反过来的话,配置或证书有问题时 nginx 会停在没有证书可用的状态
nginx -t

if command -v systemctl >/dev/null 2>&1; then
    systemctl reload "$RELOAD_UNIT"
else
    nginx -s reload
fi

trap - EXIT # 走到这里才算成功,撤掉回滚
rm -f "${TARGET_CERT}.certiman-bak" "${TARGET_KEY}.certiman-bak"

echo "已部署 ${CERTIMAN_CERT_NAME} → ${TARGET_CERT}(有效期至 ${CERTIMAN_NOT_AFTER})"

apache ​

sh
#!/bin/sh
# certiman deploy hook:把新证书装到 Apache/httpd 并 graceful 重载。
#
#   cert.yaml:  deploy: /home/<用户名>/.certiman/hooks/apache.sh
#   试运行:       certiman hook test -d <域名> --step deploy
set -eu

# ── 改这里 ────────────────────────────────────────────────────────────────
# 与 vhost 里 SSLCertificateFile / SSLCertificateKeyFile 两行保持一致。
TARGET_DIR="/etc/httpd/ssl"
TARGET_CERT="${TARGET_DIR}/${CERTIMAN_CERT_NAME}.pem"
TARGET_KEY="${TARGET_DIR}/${CERTIMAN_CERT_NAME}.key"
# ──────────────────────────────────────────────────────────────────────────

# Apache 2.4.8 起 SSLCertificateFile 直接收全链,不必再写 SSLCertificateChainFile
# (那条指令已废弃)。CERTIMAN_CERT_PATH 给的正是全链。
# 若仍在 2.4.8 之前的版本上,用 CERTIMAN_CHAIN_PATH 单独配 SSLCertificateChainFile

restore() {
    if [ -f "${TARGET_CERT}.certiman-bak" ]; then
        mv "${TARGET_CERT}.certiman-bak" "$TARGET_CERT"
    fi
    if [ -f "${TARGET_KEY}.certiman-bak" ]; then
        mv "${TARGET_KEY}.certiman-bak" "$TARGET_KEY"
    fi
    echo "部署失败,已还原 ${TARGET_CERT}" >&2
}

install -d -m 755 "$TARGET_DIR"
if [ -f "$TARGET_CERT" ]; then
    cp -p "$TARGET_CERT" "${TARGET_CERT}.certiman-bak"
fi
if [ -f "$TARGET_KEY" ]; then
    cp -p "$TARGET_KEY" "${TARGET_KEY}.certiman-bak"
fi
trap restore EXIT

install -m 644 "$CERTIMAN_CERT_PATH" "$TARGET_CERT"
install -m 600 "$CERTIMAN_KEY_PATH" "$TARGET_KEY"

# 各发行版的命令名不同:RHEL 系是 apachectl/httpd,Debian 系是 apache2ctl/apache2
if command -v apachectl >/dev/null 2>&1; then
    CTL="apachectl"
else
    CTL="apache2ctl"
fi

"$CTL" configtest # 先校验,配置有错时 graceful 会让 Apache 停在旧进程上
"$CTL" graceful   # 平滑重载,不断开正在处理的连接

trap - EXIT
rm -f "${TARGET_CERT}.certiman-bak" "${TARGET_KEY}.certiman-bak"

echo "已部署 ${CERTIMAN_CERT_NAME} → ${TARGET_CERT}(有效期至 ${CERTIMAN_NOT_AFTER})"

systemd(通用) ​

该脚本不绑定具体服务,修改证书路径、私钥路径与服务名三个变量后,即可用于 dovecot、postfix、mosquitto、rspamd 等通过重新读取证书文件并 reload 生效的服务。

sh
#!/bin/sh
# certiman deploy hook:拷贝证书后 reload 任意 systemd 服务。
#
#   cert.yaml:  deploy: /home/<用户名>/.certiman/hooks/systemd.sh
#   试运行:       certiman hook test -d <域名> --step deploy
set -eu

# ── 改这里 ────────────────────────────────────────────────────────────────
TARGET_CERT="/etc/ssl/certs/${CERTIMAN_CERT_NAME}.pem"
TARGET_KEY="/etc/ssl/private/${CERTIMAN_CERT_NAME}.key"
RELOAD_UNIT="dovecot"
# 服务以哪个用户读证书。留空表示只有 root 能读(最安全);
# 服务若以非 root 身份运行且读不到私钥,填它的属主,如 "dovecot" 或 "postfix"
KEY_OWNER=""
# ──────────────────────────────────────────────────────────────────────────

# CERTIMAN_CERT_PATH 是 fullchain(叶子 + 中间链)。邮件类服务尤其要用全链:
# 客户端不像浏览器那样会去补下发缺失的中间证书,缺链就是连不上
install -d -m 755 "$(dirname "$TARGET_CERT")"
install -d -m 710 "$(dirname "$TARGET_KEY")"
install -m 644 "$CERTIMAN_CERT_PATH" "$TARGET_CERT"
install -m 640 "$CERTIMAN_KEY_PATH" "$TARGET_KEY"

if [ -n "$KEY_OWNER" ]; then
    chown "root:${KEY_OWNER}" "$TARGET_KEY"
fi

# reload 不成再 restart:有些服务(如 postfix 的部分版本)不支持热加载证书,
# reload 会成功返回却仍旧握着老证书,直到下次重启才换过来
if systemctl reload "$RELOAD_UNIT"; then
    echo "已 reload ${RELOAD_UNIT}"
else
    echo "reload 失败,改用 restart ${RELOAD_UNIT}" >&2
    systemctl restart "$RELOAD_UNIT"
fi

echo "已部署 ${CERTIMAN_CERT_NAME} → ${TARGET_CERT}(有效期至 ${CERTIMAN_NOT_AFTER})"

Windows IIS ​

使用前须同时满足以下两个条件:

  1. cert.yaml.output.formats 含 pfx,且已配置 output.password 或 output.password_file。本脚本部署 PFX 文件,未满足该条件时 CertiMan 不会生成 PFX
  2. 在该证书配置或其引用的 profile 模板中设置 shell: powershell。hook 默认通过 cmd /C 执行,未设置时整个脚本将被当作命令行解析

$ErrorActionPreference = "Stop" 与外部命令

PowerShell 5.1 在 Stop 模式下会把 netsh / appcmd / certutil 等外部命令写入 stderr 的任何一行视为终止错误并抛出,即使命令退出码为 0,2>$null 也无法抑制。可改用 cmdlet(如本示例中的 Import-PfxCertificate)替代外部命令,或按 $LASTEXITCODE 显式判断退出码,不应依赖 $ErrorActionPreference 拦截错误。

powershell
# certiman deploy hook:把新证书导入 Windows 证书存储并绑定到 IIS 站点。
#
#   cert.yaml:  deploy: C:\Users\<用户名>\.certiman\hooks\iis.ps1
#   试运行:       certiman hook test -d <域名> --step deploy

$ErrorActionPreference = "Stop"

# ── 改这里 ────────────────────────────────────────────────────────────────
$SiteName = "Default Web Site"
$PfxName = "$($env:CERTIMAN_CERT_NAME).pfx"
# PFX 口令:与 cert.yaml 里 output.password / password_file 配的那个一致。
# 口令以明文存于文件,须收紧本脚本的 ACL;
# 想从环境变量读就写 $PfxPassword = $env:PFX_PASSWORD
$PfxPassword = "填入实际 PFX 口令"
$BindingPort = 443
# ──────────────────────────────────────────────────────────────────────────

$PfxPath = Join-Path $env:CERTIMAN_CONVERTED_DIR $PfxName
if (-not (Test-Path $PfxPath)) {
    throw "找不到 $PfxPath;确认 cert.yaml 的 output.formats 里有 pfx"
}
if ([string]::IsNullOrEmpty($PfxPassword)) {
    throw "未填写 PFX 口令;见脚本顶部的「改这里」"
}

$secure = ConvertTo-SecureString -String $PfxPassword -AsPlainText -Force
$cert = Import-PfxCertificate -FilePath $PfxPath `
    -CertStoreLocation Cert:\LocalMachine\WebHosting -Password $secure

Import-Module WebAdministration

# 换绑而不是新增:同一端口上留着旧绑定,IIS 会继续用先匹配到的那条,
# 于是证书明明已经导入、站点却还在发旧的
$binding = Get-WebBinding -Name $SiteName -Protocol "https" -Port $BindingPort `
    -ErrorAction SilentlyContinue
if ($null -eq $binding) {
    New-WebBinding -Name $SiteName -Protocol "https" -Port $BindingPort -SslFlags 0
}
$binding = Get-WebBinding -Name $SiteName -Protocol "https" -Port $BindingPort
$binding.AddSslCertificate($cert.Thumbprint, "WebHosting")

Write-Output "已部署 $($env:CERTIMAN_CERT_NAME) 到 $SiteName:$BindingPort(指纹 $($cert.Thumbprint))"

相关文档 ​