一、一次证书过期引发的线上事故
去年某个周一早上 9 点,业务方反馈"网站打不开"。排查发现不是服务器挂了,而是 SSL 证书在周五晚上过期——浏览器直接拦截,用户看到的是"您的连接不是私密连接"大红屏。运维组没人注意到证书还有 3 天到期,因为证书是手动购买手动续签的,没人设提醒。
那次事故暴露了一个核心问题:证书管理不自动化的系统,迟早会过期出事。 SSL 证书不是一次性配置,而是持续运营——签发、部署、续签、轮换、监控,每个环节都必须自动化。
二、证书生命周期全景
签发 ──→ 部署 ──→ 运行 ──→ 监控 ──→ 续签 ──→ 轮换
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
ACME/DV nginx OCSP 告警 自动续签 双证书
自建CA/OV reload Stapling 30d 60d前 并行切换
EV+SAN reload HSTS 阈值 触发 旧证书7天
经验法则:证书续签必须比过期时间提前 至少 30 天触发,给自己留够容错窗口。60 天前触发更安全——万一 ACME 服务器故障、域名 DNS 变更等导致续签失败,还有 30 天缓冲期。
三、acme.sh:Let's Encrypt 自动签发与续签
3.1 安装 acme.sh
# 安装(自动创建 cron 续签任务)
curl https://get.acme.sh | sh -s email=ops@scrcx.cn
# 安装后自动注册 cron:
# 0 0 * * * "/home/ops/.acme.sh"/acme.sh --cron --home "/home/ops/.acme.sh" > /dev/null
3.2 DNS API 签发(推荐,不需要停服务)
Let's Encrypt 验证域名所有权有两种方式:HTTP(在网站根目录放验证文件)和 DNS(在域名 TXT 记录放验证值)。DNS 方式更安全——不需要暴露验证文件,且支持通配符证书。
# 阿里云 DNS API(acme.sh 内置支持)
# 先设置 AccessKey
export Ali_Key="LTAI5tXXXXXXX"
export Ali_Secret="XXXXXXXXXXXXXX"
# 签发通配符证书
acme.sh --issue -d scrcx.cn -d '*.scrcx.cn' --dns dns_ali
# 签发成功后证书文件:
# ~/.acme.sh/scrcx.cn_ecc/fullchain.cer (证书链)
# ~/.acme.sh/scrcx.cn_ecc/scrcx.cn.key (私钥)
# ~/.acme.sh/scrcx.cn_ecc/scrcx.cn.cer (域名证书)
# ~/.acme.sh/scrcx.cn_ecc/ca.cer (CA 证书)
其他 DNS 提供商的 API 也内置支持:
dns_cf(Cloudflare)、dns_dp(DNSPod/腾讯云)、dns_aws(Route53)、dns_gd(GoDaddy)等。查看完整列表:acme.sh --help。
3.3 安装证书到 nginx
# 安装证书(acme.sh 会 copy 到指定目录并记录路径,续签后自动更新)
acme.sh --install-cert -d scrcx.cn -d '*.scrcx.cn' \
--ecc \
--key-file /etc/nginx/ssl/scrcx.cn.key \
--fullchain-file /etc/nginx/ssl/scrcx.cn.fullchain.cer \
--reloadcmd "systemctl reload nginx"
# 关键:--reloadcmd 确保续签后 nginx 自动 reload 加载新证书
# --ecc 指定使用 ECC 证书(比 RSA 更短更快更安全)
3.4 nginx SSL 配置
# /etc/nginx/conf.d/blog.conf — HTTPS server block
server {
listen 443 ssl http2;
server_name www.scrcx.cn scrcx.cn blog.scrcx.cn;
# === 证书 ===
ssl_certificate /etc/nginx/ssl/scrcx.cn.fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/scrcx.cn.key;
# === 协议与密码套件 ===
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
# === 会话复用 ===
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off; # 安全考虑:禁用 session ticket
# === OCSP Stapling ===
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# === HSTS ===
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# === 其他安全头 ===
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
root /opt/blog/site;
# ... 其他配置
}
3.5 ECC + RSA 双证书部署
部分老旧客户端(Java 6、Windows XP IE 等)不支持 ECC。双证书方案让现代客户端用 ECC(快),老客户端回退 RSA(兼容):
server {
listen 443 ssl http2;
server_name www.scrcx.cn;
# ECC 证书(优先)
ssl_certificate /etc/nginx/ssl/scrcx.cn_ecc.fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/scrcx.cn_ecc.key;
# RSA 证书(回退)
ssl_certificate /etc/nginx/ssl/scrcx.cn_rsa.fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/scrcx.cn_rsa.key;
}
# 分别签发 ECC 和 RSA 证书
acme.sh --issue -d scrcx.cn -d '*.scrcx.cn' --dns dns_ali --keylength ec-256
acme.sh --issue -d scrcx.cn -d '*.scrcx.cn' --dns dns_ali --keylength 2048
# 分别安装
acme.sh --install-cert -d scrcx.cn -d '*.scrcx.cn' --ecc \
--key-file /etc/nginx/ssl/scrcx.cn_ecc.key \
--fullchain-file /etc/nginx/ssl/scrcx.cn_ecc.fullchain.cer \
--reloadcmd "systemctl reload nginx"
acme.sh --install-cert -d scrcx.cn -d '*.scrcx.cn' \
--key-file /etc/nginx/ssl/scrcx.cn_rsa.key \
--fullchain-file /etc/nginx/ssl/scrcx.cn_rsa.fullchain.cer \
--reloadcmd "systemctl reload nginx"
四、OCSP Stapling:减少证书验证延迟
4.1 为什么需要 OCSP Stapling
浏览器验证 SSL 证书有效性时,需要向 CA 的 OCSP 服务器查询证书状态。这个额外请求:
- 增加 100~300ms 连接延迟
- 暴露用户访问了哪些网站(CA 知道谁在验证哪个证书)
- 如果 CA OCSP 服务器宕机,用户无法访问你的网站
OCSP Stapling 让 nginx 主动从 CA 拿到 OCSP 响应并"钉"在 TLS 握手中发给浏览器,浏览器无需额外查询。
4.2 配置验证
# 测试 OCSP Stapling 是否生效
openssl s_client -connect www.scrcx.cn:443 -status -servername www.scrcx.cn 2>&1 | \
grep -A2 "OCSP response"
# 期望输出:
# OCSP response status: OCSP Response Status: successful (0x0)
# Response has this single OCSP response
# 如果输出 "no response sent",说明 Stapling 未生效
# 可能原因:nginx 启动时无法连接 OCSP 服务器(DNS/防火墙问题)
五、证书到期监控与告警
5.1 Prometheus + Blackbox Exporter
# prometheus.yml — SSL 证书到期监控
scrape_configs:
- job_name: 'ssl_expiry'
metrics_path: /probe
params:
module: [ssl_cert]
static_configs:
- targets:
- https://www.scrcx.cn
- https://app.scrcx.cn
- https://blog.scrcx.cn
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox:9115 # Blackbox Exporter 地址
# 告警规则
groups:
- name: ssl_expiry
rules:
- alert: SSLCertExpiringSoon
expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 30
for: 1h
labels:
severity: warning
annotations:
summary: "SSL certificate expiring in < 30 days"
description: "Certificate for {{ $labels.instance }} expires in {{ $value | humanizeDuration }}"
- alert: SSLCertExpiringCritical
expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 7
for: 30m
labels:
severity: critical
annotations:
summary: "SSL certificate expiring in < 7 days!"
5.2 简单脚本方案(无 Prometheus 时)
#!/bin/bash
# check_cert_expiry.sh —— 证书到期检查脚本
# 放到 cron:每天 8:00 执行
THRESHOLD_DAYS=30
ALERT_EMAIL="ops@scrcx.cn"
check_cert() {
local host=$1
local port=${2:-443}
expiry_epoch=$(echo | openssl s_client -servername "$host" -connect "$host:$port" 2>/dev/null | \
openssl x509 -noout -enddate | sed 's/notAfter=//' | \
date -d "$(cat)" +%s 2>/dev/null)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
echo "$host: $days_left days remaining"
if [ "$days_left" -lt "$THRESHOLD_DAYS" ]; then
echo "WARNING: $host certificate expires in $days_left days!" | \
mail -s "SSL Cert Expiry Alert: $host" "$ALERT_EMAIL"
fi
}
# 检查所有域名
check_cert www.scrcx.cn
check_cert app.scrcx.cn
check_cert blog.scrcx.cn
六、证书轮换与回滚策略
6.1 证书轮换流程
签发新证书 → 部署到 nginx(与旧证书并存)→ reload → 验证新证书生效 → 7天后删除旧证书
# 1. 续签新证书(acme.sh 自动完成)
acme.sh --renew -d scrcx.cn -d '*.scrcx.cn' --ecc --force
# 2. 安装新证书(自动 reload nginx)
acme.sh --install-cert -d scrcx.cn -d '*.scrcx.cn' --ecc \
--key-file /etc/nginx/ssl/scrcx.cn.key \
--fullchain-file /etc/nginx/ssl/scrcx.cn.fullchain.cer \
--reloadcmd "systemctl reload nginx"
# 3. 验证新证书
openssl s_client -connect www.scrcx.cn:443 -servername www.scrcx.cn 2>/dev/null | \
openssl x509 -noout -subject -dates
# 4. 确认新证书生效后,7 天后旧证书文件会被 acme.sh 自动清理
6.2 回滚预案
# 如果新证书有问题(域名不匹配、链不完整等),立即回滚
# 1. 从备份目录恢复旧证书
cp /etc/nginx/ssl/scrcx.cn.key.bak /etc/nginx/ssl/scrcx.cn.key
cp /etc/nginx/ssl/scrcx.cn.fullchain.cer.bak /etc/nginx/ssl/scrcx.cn.fullchain.cer
# 2. reload nginx
systemctl reload nginx
# 所以在部署新证书前,养成备份旧证书的习惯:
cp /etc/nginx/ssl/scrcx.cn.key /etc/nginx/ssl/scrcx.cn.key.bak
cp /etc/nginx/ssl/scrcx.cn.fullchain.cer /etc/nginx/ssl/scrcx.cn.fullchain.cer.bak
七、企业内网自建 CA 与 PKI
7.1 什么时候需要自建 CA
Let's Encrypt 只签发 DV(Domain Validation)证书,只验证域名所有权。以下场景需要自建 CA:
- 内网服务互相通信(不暴露在公网,Let's Encrypt 无法验证)
- 需要客户端证书认证(双向 TLS)
- 多个内部微服务之间的 mTLS 零信任网络
- 需要自定义有效期(Let's Encrypt 最长 90 天)
7.2 自建根 CA
# 1. 创建根 CA 私钥和证书
mkdir -p /opt/pki/root /opt/pki/intermediate /opt/pki/issued
# 根 CA 私钥(4096 RSA,离线保存)
openssl genrsa -aes256 -out /opt/pki/root/ca.key 4096
# 根 CA 证书(有效期 20 年)
openssl req -config /opt/pki/root/ca.cnf \
-key /opt/pki/root/ca.key \
-new -x509 -days 7300 \
-sha256 -extensions v3_ca \
-out /opt/pki/root/ca.crt
# 2. 创建中间 CA(日常签发用,根 CA 离线保存)
openssl genrsa -aes256 -out /opt/pki/intermediate/intermediate.key 4096
openssl req -new -sha256 \
-key /opt/pki/intermediate/intermediate.key \
-out /opt/pki/intermediate/intermediate.csr
# 用根 CA 签发中间 CA 证书
openssl ca -config /opt/pki/root/ca.cnf \
-extensions v3_intermediate_ca \
-days 3650 -notext -md sha256 \
-in /opt/pki/intermediate/intermediate.csr \
-out /opt/pki/intermediate/intermediate.crt
# 3. 创建证书链
cat /opt/pki/intermediate/intermediate.crt /opt/pki/root/ca.crt > \
/opt/pki/intermediate/chain.crt
7.3 签发服务端证书
# 签发内网服务证书
openssl genrsa -out /opt/pki/issued/ha-system.key 2048
# CSR(包含 SAN 扩展)
openssl req -new -sha256 \
-key /opt/pki/issued/ha-system.key \
-subj "/C=CN/O=SCRCX/CN=ha-system.internal" \
-out /opt/pki/issued/ha-system.csr
# 用 SAN 扩展签发(支持多域名)
cat > /opt/pki/issued/ha-system.ext << EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage=digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName=@alt_names
[alt_names]
DNS.1=ha-system.internal
DNS.2=ha.scrcx.cn
DNS.3=10.10.4.17
IP.1=10.10.4.17
EOF
openssl ca -config /opt/pki/intermediate/ca.cnf \
-days 365 -notext -md sha256 \
-in /opt/pki/issued/ha-system.csr \
-out /opt/pki/issued/ha-system.crt \
-extfile /opt/pki/issued/ha-system.ext
7.4 签发客户端证书(mTLS 场景)
# 签发运维人员客户端证书
openssl genrsa -out /opt/pki/issued/ops-xuwang.key 2048
openssl req -new -sha256 \
-key /opt/pki/issued/ops-xuwang.key \
-subj "/C=CN/O=SCRCX/CN=xuwang@scrcx.cn" \
-out /opt/pki/issued/ops-xuwang.csr
cat > /opt/pki/issued/ops-xuwang.ext << EOF
basicConstraints=CA:FALSE
keyUsage=digitalSignature,keyEncipherment
extendedKeyUsage=clientAuth
subjectAltName=email:xuwang@scrcx.cn
EOF
openssl ca -config /opt/pki/intermediate/ca.cnf \
-days 365 -notext -md sha256 \
-in /opt/pki/issued/ops-xuwang.csr \
-out /opt/pki/issued/ops-xuwang.crt \
-extfile /opt/pki/issued/ops-xuwang.ext
# 导出为 PKCS12(浏览器/客户端导入用)
openssl pkcs12 -export \
-in /opt/pki/issued/ops-xuwang.crt \
-inkey /opt/pki/issued/ops-xuwang.key \
-certfile /opt/pki/intermediate/chain.crt \
-out /opt/pki/issued/ops-xuwang.p12
7.5 nginx mTLS 配置
# 双向 TLS:客户端必须提供有效证书才能访问
server {
listen 443 ssl http2;
server_name ha-system.internal;
ssl_certificate /opt/pki/issued/ha-system.crt;
ssl_certificate_key /opt/pki/issued/ha-system.key;
ssl_client_certificate /opt/pki/intermediate/chain.crt;
ssl_verify_client on;
ssl_verify_depth 2;
# 只允许特定 OU 的客户端证书
ssl_verify_client optional_no_ca;
location / {
if ($ssl_client_verify != SUCCESS) {
return 403;
}
# 检查客户端证书的 CN
# 通过 Lua 或变量做更精细的 RBAC
proxy_pass http://127.0.0.1:8080;
}
}
7.6 证书吊销(CRL / OCSP)
# 生成 CRL(证书吊销列表)
openssl ca -config /opt/pki/intermediate/ca.cnf \
-gencrl -out /opt/pki/intermediate/ca.crl
# 吊销某个证书
openssl ca -config /opt/pki/intermediate/ca.cnf \
-revoke /opt/pki/issued/ops-xuwang.crt
# 更新 CRL
openssl ca -config /opt/pki/intermediate/ca.cnf \
-gencrl -out /opt/pki/intermediate/ca.crl
# nginx 配置 CRL 检查
ssl_crl /opt/pki/intermediate/ca.crl;
八、Windows 服务器证书管理
8.1 IIS 证书绑定
# 导入 PFX 证书到 Windows 证书存储
$cert = Import-PfxCertificate -FilePath "D:\certs\www.scrcx.cn.p12" `
-CertStoreLocation Cert:\LocalMachine\My `
-Password (Get-Credential).Password
# 绑定到 IIS 站点
New-WebBinding -Name "Default Web Site" -Protocol https -Port 443 -IPAddress * `
-HostHeader www.scrcx.cn
# 绑定证书
$binding = Get-WebBinding -Name "Default Web Site" -Protocol https -Port 443
$binding.AddSslCertificate($cert.Thumbprint, "My")
8.2 Windows 自动续签(Win-ACME)
# 安装 win-acme(Windows 版 ACME 客户端)
# https://github.com/win-acme/win-acme/releases
# 签发并自动绑定到 IIS
wacs.exe --target iis --siteid 1 --host www.scrcx.cn --host app.scrcx.cn `
--installation iis --store certificatestore `
--acme-directory https://acme-v02.api.letsencrypt.org/directory
# win-acme 会自动创建 Windows 计划任务续签
九、证书合规与安全检查
9.1 SSL Labs 测试
# 在线测试(最权威):https://www.ssllabs.com/ssltest/analyze.html?d=www.scrcx.cn
# 期望评级:A 或 A+
# 本地快速检查
nmap --script ssl-enum-ciphers -p 443 www.scrcx.cn
# 检查关键项:
# ✅ TLS 1.2 + 1.3 only(禁用 TLS 1.0/1.1)
# ✅ 无弱密码套件(RC4、DES、3DES 等)
# ✅ 证书链完整(含中间 CA)
# ✅ OCSP Stapling 开启
# ✅ HSTS 开启且 max-age >= 6 个月
# ✅ 证书与域名匹配(含 SAN)
9.2 证书信息快速检查脚本
#!/bin/bash
# ssl_check.sh —— 一行命令查证书详情
# 用法: ssl_check.sh www.scrcx.cn
HOST=${1:?Usage: ssl_check.sh <host>}
echo | openssl s_client -servername "$HOST" -connect "$HOST:443" 2>/dev/null | \
openssl x509 -noout -subject -issuer -dates -ext subjectAltName
# 输出示例:
# subject=CN = *.scrcx.cn
# issuer=CN = R3, O = Let's Encrypt
# notBefore=Jun 15 00:00:00 2026 GMT
# notAfter=Sep 13 23:59:59 2026 GMT
# Subject Alternative Name: DNS:*.scrcx.cn, DNS:scrcx.cn
十、避坑清单
| # | 坑 | 现象 | 解决 |
|---|---|---|---|
| 1 | 证书链不完整 | 浏览器显示"不信任",Java 报 PKIX 错误 | 部署 fullchain.cer(含中间 CA),不是只部署域名证书 |
| 2 | 中间 CA 证书缺失 | 手机浏览器能访问但 Java/Python 客户端报 SSL 错误 | 检查 fullchain 是否含完整链:openssl verify -CAfile chain.crt cert.crt |
| 3 | SAN 不含旧域名 | Chrome 报 ERR_CERT_COMMON_NAME_INVALID |
CSR 里加 SAN 扩展覆盖所有域名/IP |
| 4 | 续签后 nginx 不 reload | 新证书签发了但访问还是旧证书 | --reloadcmd "systemctl reload nginx" |
| 5 | 通配符证书不含根域名 | *.scrcx.cn 不覆盖 scrcx.cn |
签发时 -d scrcx.cn -d '*.scrcx.cn' 两个都加 |
| 6 | DNS API 缓存导致验证失败 | ACME 查 TXT 记录时读到了旧值 | DNS TTL 设为 600 秒以下,或等 TTL 过期后再签 |
| 7 | 自建 CA 证书未被客户端信任 | 内网服务浏览器报不信任 | 客户端导入根 CA 证书到信任存储 |
| 8 | RSA 证书过长 | TLS 握手慢(RSA 4096 私钥运算重) | 换 ECC(ec-256),性能提升 5~10 倍 |
| 9 | cron 续签任务被误删 | 证书过期无人知晓 | 监控证书到期天数作为兜底 |
| 10 | HSTS preload 提交后想撤回 | 域名被浏览器内置强制 HTTPS,无法撤销 | 提交前充分测试,preload 撤回需数月 |
十一、小结
SSL/TLS 证书管理的核心原则是自动化 + 监控 + 回滚:
- 自动化:acme.sh 一键签发续签,cron 定时触发,
--reloadcmd自动 reload 服务——消除人为遗忘。 - 监控:证书到期天数纳入 Prometheus 告警,30 天 warning + 7 天 critical——即使自动化失败也有兜底。
- 回滚:部署新证书前备份旧证书,出问题 1 分钟回滚——不把业务赌在续签一定成功上。
内网 PKI 是公网证书管理的延伸——当你需要 mTLS 零信任、客户端认证、自定义有效期时,自建 CA 提供了比 Let's Encrypt 更灵活的能力。核心安全要求不变:根 CA 离线保存、中间 CA 日常签发、证书吊销机制(CRL/OCSP)必须配套。
当你第一次看到 acme.sh 在凌晨自动续签证书、nginx 无感 reload、业务全程零中断时,你就理解了"证书管理不是一次性配置,而是持续运营"这句话。