Linux运维16 min read

SSL/TLS 证书管理与自动化签发:从 Let's Encrypt 到企业级 PKI 部署

一、一次证书过期引发的线上事故

去年某个周一早上 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、业务全程零中断时,你就理解了"证书管理不是一次性配置,而是持续运营"这句话。

分享:

评论区