找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

5237

积分

0

好友

727

主题
发表于 5 小时前 | 查看: 4| 回复: 0

HTTPS 证书过期是运维工程师最不愿意面对但又最容易忽视的问题之一。证书过期后,用户访问网站时会看到浏览器显示的警告页面,直接影响业务可用性和用户信任度。更严重的是,某些场景下证书过期可能导致服务完全不可用。要知道,这可是实打实的线上事故。

本文系统讲解 HTTPS 证书的工作原理、证书过期的风险、监控方案以及自动化管理实践。内容涵盖了 Let's Encrypt 的申请与续期、主流证书管理工具、以及如何搭建一套可靠的监控告警体系。希望能帮你彻底摆脱证书过期带来的噩梦。

一、HTTPS 证书基础

1.1 加密通信的原理

HTTPS(HTTP Secure)是 HTTP 协议的安全版本,通过 TLS(Transport Layer Security)协议实现加密通信。

TLS 1.3 是当前最新的主推版本,于 2018 年发布,相比 TLS 1.2 有显著的握手性能提升和安全性增强。

加密通信的核心依赖于公钥密码学:

  • 服务器持有公钥和私钥,公钥用于加密,私钥用于解密。
  • 客户端从服务器的数字证书中获取公钥。
  • 客户端生成一个随机的会话密钥,并用服务器的公钥加密后发送给服务器。
  • 服务器用自己的私钥解密,拿回会话密钥。
  • 之后的通信全部使用这个会话密钥进行高效的对称加密。

1.2 数字证书的结构

数字证书就像是服务器的电子身份证,里面包含了这些关键信息:

  • 证书持有者信息:域名、公司名称等。
  • 颁发者信息:证书颁发机构(CA)的信息。
  • 公钥信息:证书持有者的公钥。
  • 有效期:证书的生效时间和失效时间。
  • 序列号:CA 分配的唯一的证书编号。
  • 签名:CA 对证书内容做的数字签名。

你可以用下面的命令来查看证书的详细信息:

# 查看证书的详细信息
openssl x509 -in certificate.crt -text -noout

# 证书内容示例
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number:
#             04:cd:3a:5b:f2:7c:8a:3e:9d:5e:8a:3c:1d:4e:8a:2b:3c
#         Signature Algorithm: sha256WithRSAEncryption
#         Issuer: C = US, O = Let's Encrypt, CN = R3
#         Validity
#             Not Before: Jan 15 00:00:00 2026 GMT
#             Not After : Apr 15 00:00:00 2026 GMT
#         Subject: CN = example.com
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#             RSA Public-Key: (2048 bit)

1.3 证书链的概念

浏览器验证证书时,不是只看你服务器自己提供的这一张纸,而是要顺着藤蔓摸到根上,验证整个证书链:

  • 端实体证书:你自己的服务器证书。
  • 中间证书:由 CA 颁发给中间机构的证书,用于隔离根证书。
  • 根证书:内置于浏览器或操作系统中的、绝对受信任的根 CA 证书。
# 查看证书链
openssl verify -CAfile chain.crt certificate.crt

# 查看证书链的完整路径
openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/null | \
  openssl x509 -noout -text | grep -A 2 "Certificate chain"

二、证书过期的风险

2.1 服务不可用风险

当证书过期那一刻,最直接的后果就是用户访问你的网站时,会迎面撞上一堵“此网站不安全”的警告墙。浏览器只会显示冷冰冰的安全警告页面,尽管用户可以强行点开“高级”按钮手动继续访问,但绝大多数普通用户的第一反应会是直接关掉页面,毫不犹豫地抛弃你。如果是移动端 App 的 API 证书过期,那更惨,所有功能直接报错、闪退,用户可能会以为你的公司倒闭了。

2.2 数据安全风险

不要以为只是访问不了那么简单。过期的证书就像一个形同虚设的门卫,无法为通信提供完整有效的加密保护。特别是在用户因为频繁看到警告而逐渐麻痹,开始习惯性地忽略安全提示的场景下,中间人攻击的风险会急剧增加。此外,如果你需要遵循某些安全合规要求,比如 PCI DSS,那过期的证书会让你直接掉出合规名单。

2.3 运维应急压力

更让人血压飙升的是,证书过期常常精准地发生在你最不希望的时间——比如某个安静的凌晨或悠闲的假期。届时,你将面对的不只是技术问题,还有连环夺命 Call、领导关切的目光、客户愤怒的投诉,以及在一片焦头烂额中,手动操作续期可能因为手抖而犯下的更多低级错误。

2.4 实际案例:证书过期导致的服务中断

某电商平台就曾经历过这样一次惨痛的教训:凌晨 3 点,客服突然被潮水般的投诉淹没,用户反馈无法下单。团队一顿手忙脚乱的排查后,发现根源竟是支付接口的 HTTPS 证书过期了,导致握手失败。整个服务中断持续了整整 2 个小时,事后估算的直接业务损失超过百万。复盘时发现,根本原因很简单:证书续期流程完全依赖某个开发人员手动处理,并且没有任何监控和告警。

三、证书管理的基本操作

3.1 证书的获取方式

获取证书通常有几种途径:

  • 商业 CA:像 DigiCert、GlobalSign 这样的大厂,提供付费的高信任度证书。
  • Let's Encrypt:一个免费、自动化的 CA 新星,主打 90 天有效期的证书,是个人和小微企业的首选。
  • 私有 CA:自己动手,丰衣足食,适合企业内网环境。

3.2 Let's Encrypt 证书申请

Let's Encrypt 是目前最流行的免费证书提供商,通过 ACME 协议实现了自动化的证书申请和续期。

3.2.1 安装 certbot

# Ubuntu/Debian
sudo apt update
sudo apt install certbot python3-certbot-nginx

# CentOS/RHEL
sudo dnf install certbot python3-certbot-nginx

# 查看版本
certbot --version
# certbot 3.0.1

3.2.2 申请证书

# 为单个域名申请证书
sudo certbot certonly --nginx -d example.com

# 为多个域名申请证书
sudo certbot certonly --nginx -d example.com -d www.example.com

# 使用 webroot 模式申请(需要预先配置 webroot)
sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com

# 使用 DNS 验证方式申请(适合无法通过 HTTP 验证的场景)
sudo certbot certonly --manual --preferred-challenges dns -d example.com

3.2.3 证书存放位置

# Let's Encrypt 证书存放位置
ls -la /etc/letsencrypt/live/example.com/

# 文件说明
# fullchain.pem    - 完整证书链(服务器证书 + 中间证书)
# privkey.pem      - 私钥
# cert.pem         - 服务器证书
# chain.pem        - 中间证书

3.3 证书的部署

3.3.1 Nginx 配置

server {
    listen 443 ssl http2;
    server_name example.com;

    # 证书配置
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # TLS 版本配置(禁用旧版本)
    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;
    ssl_prefer_server_ciphers off;

    # OCSP stapling 配置
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;
}

# HTTP 重定向到 HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

3.3.2 Apache 配置

<VirtualHost *:443>
    ServerName example.com

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
    SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem

    # TLS 配置
    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
</VirtualHost>

3.4 证书的验证

# 检查证书信息
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -dates

# 检查证书链
openssl verify /etc/letsencrypt/live/example.com/fullchain.pem

# 在线检查证书
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
  openssl x509 -noout -dates -issuer -subject

# 检查 TLS 版本和加密套件
echo | openssl s_client -connect example.com:443 -tls1_2 -servername example.com 2>/dev/null | \
  openssl s_client -connect example.com:443 -tls1_3 2>/dev/null | grep "Protocol"

3.5 证书的续期

Let's Encrypt 的证书只有 90 天寿命,续期是永恒的话题。

# 手动续期所有证书
sudo certbot renew

# 手动续期特定证书
sudo certbot renew --cert-name example.com

# 测试续期流程(不实际执行)
sudo certbot renew --dry-run

# 强制续期(忽略过期时间)
sudo certbot renew --force-renewal

四、自动化证书管理

4.1 certbot 自动续期机制

certbot 在安装后就非常体贴地为你创建了定时任务。

# 查看定时任务
sudo systemctl list-timers | grep certbot

# 查看详细配置
sudo cat /etc/cron.d/certbot
# 0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew

# 查看 systemd timer
sudo systemctl status certbot-renew.timer

4.2 Nginx 自动部署

certbot 的 nginx 插件可以一键搞定申请和部署。

# 使用 nginx 插件申请证书并自动配置
sudo certbot --nginx -d example.com -d www.example.com

# 插件会自动:
# 1. 验证域名
# 2. 申请证书
# 3. 修改 Nginx 配置文件
# 4. 配置自动续期

4.3 续期后自动重载服务

续期后最重要的是让 Web 服务器用上新证书,这靠 hook 脚本来实现。

# 查看 certbot 的 hook 配置
sudo cat /etc/letsencrypt/renewal/example.com.conf

# 配置示例
[renewalparams]
account = xxxxxxxx
authenticator = nginx
installer = nginx
server = https://acme-v02.api.letsencrypt.org/directory
post_hook = systemctl reload nginx

4.4 完整的自动化脚本

#!/bin/bash
# /opt/scripts/cert-auto-renew.sh

LOGFILE="/var/log/cert-renewal.log"
CERT_DIR="/etc/letsencrypt/live"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOGFILE
}

# 执行证书续期
log "Starting certificate renewal check..."
sudo certbot renew --quiet --deploy-hook "systemctl reload nginx" 2>&1 | tee -a $LOGFILE

# 检查续期结果
if [ $? -eq 0 ]; then
    log "Certificate renewal completed successfully"

    # 列出需要更新的证书
    for cert in $(ls $CERT_DIR); do
        expiry=$(sudo openssl x509 -in $CERT_DIR/$cert/cert.pem -noout -enddate 2>/dev/null | cut -d= -f2)
        log "Certificate $cert expires: $expiry"
    done
else
    log "ERROR: Certificate renewal failed"
    # 发送告警
    # 这里接入告警通知
fi

五、证书监控方案

主动出击,比亡羊补牢要有效得多。

5.1 证书有效期监控

5.1.1 脚本监控

#!/bin/bash
# /opt/scripts/check-cert-expiry.sh

ALERT_DAYS=30
CERT_FILE="/etc/letsencrypt/live/example.com/cert.pem"
ALERT_EMAIL="ops@example.com"

# 获取证书过期时间
EXPIRY_DATE=$(sudo openssl x509 -in $CERT_FILE -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_UNTIL_EXPIRY=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))

echo "Certificate expires in $DAYS_UNTIL_EXPIRY days (on $EXPIRY_DATE)"

if [ $DAYS_UNTIL_EXPIRY -le $ALERT_DAYS ]; then
    echo "WARNING: Certificate will expire soon!" | mail -s "Certificate Expiry Alert" $ALERT_EMAIL
    exit 1
fi

5.1.2 OpenSSL 命令检查远程证书

#!/bin/bash
# 检查远程网站的证书过期时间

DOMAIN=$1
ALERT_DAYS=${2:-30}

# 获取证书过期时间
EXPIRY_DATE=$(echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2>/dev/null | \
  openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)

if [ -z "$EXPIRY_DATE" ]; then
    echo "ERROR: Cannot get certificate for $DOMAIN"
    exit 1
fi

# 计算剩余天数
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_UNTIL_EXPIRY=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))

echo "$DOMAIN: expires in $DAYS_UNTIL_EXPIRY days (on $EXPIRY_DATE)"

if [ $DAYS_UNTIL_EXPIRY -le $ALERT_DAYS ]; then
    echo "ALERT: $DOMAIN certificate will expire in $DAYS_UNTIL_EXPIRY days"
    exit 1
fi

使用示例:

# 检查多个域名
for domain in example.com api.example.com shop.example.com; do
    /opt/scripts/check-cert-expiry.sh $domain 30
done

5.2 Prometheus 监控

利用业界标准的 Prometheus 和 blackbox_exporter 可以轻松实现证书监控。

配置 prometheus.yml:

# prometheus 配置
scrape_configs:
  - job_name: 'ssl-certificate-exporter'
    static_configs:
      - targets:
        - example.com:443
        - api.example.com:443
    metrics_path: /probe
    params:
      module: [https_02]
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: ssl-certificate-exporter:9119

用容器启动 blackbox_exporter

docker run -d \
  --name ssl_exporter \
  -p 9119:9119 \
  prom/blackbox_exporter:latest

告警规则:

# /etc/prometheus/rules/cert-alerts.yml
groups:
  - name: certificate-expiry
    rules:
      - alert: CertificateExpiringSoon
        expr: ssl_cert_expires_in_seconds < 86400 * 30
        for: 1h
        labels:
          severity: warning
        annotations:
          summary: "Certificate {{ $labels.instance }} expiring in less than 30 days"
          description: "Certificate expires on {{ $value | humanizeDuration }}"

      - alert: CertificateExpired
        expr: ssl_cert_expires_in_seconds < 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Certificate {{ $labels.instance }} has expired"
          description: "Certificate expired on {{ $labels.instance }}"

5.3 Grafana 仪表板

你也可以在 Grafana 中创建一个可视化面板,让过期风险一目了然所需的关键指标有:

  • 证书剩余有效期(天)
  • 证书剩余有效期的百分比
  • 证书过期的倒计时
  • 按域名分组的证书列表
{
  "panels": [
    {
      "title": "Certificate Expiry",
      "type": "stat",
      "targets": [
        {
          "expr": "ssl_cert_expires_in_seconds / 86400",
          "legendFormat": "{{ instance }}"
        }
      ],
      "fieldConfig": {
        "defaults": {
          "thresholds": {
            "mode": "absolute",
            "steps": [
              { "value": 0, "color": "red" },
              { "value": 7, "color": "orange" },
              { "value": 30, "color": "green" }
            ]
          }
        }
      }
    }
  ]
}

5.4 云服务商监控

主流云服务商也都提供了证书监控服务,比如:

  • AWS Certificate Manager:可设置过期前 30/45/60/90 天的通知。
  • 阿里云 SSL 证书:支持证书到期提醒。
  • 腾讯云 SSL 证书:提供到期自动续费功能。

六、证书管理工具

6.1 acme.sh

acme.sh 是一个用 Shell 编写的轻量级 Let's Encrypt 客户端,功能强大且零依赖。

# 安装 acme.sh
curl https://get.acme.sh | sh -s email=my@example.com

# 申请证书
.acme.sh/acme.sh --issue -d example.com -d www.example.com --nginx /etc/nginx/nginx.conf

# 安装证书
.acme.sh/acme.sh --install-cert -d example.com \
  --key-file /etc/nginx/ssl/key.pem \
  --fullchain-file /etc/nginx/ssl/fullchain.pem \
  --reloadcmd "systemctl reload nginx"

6.2 Vault PKI

HashiCorp Vault 则提供了企业级的 PKI 解决方案,适合内部服务的证书管理。

# 启用 PKI secrets engine
vault secrets enable pki

# 配置 CA
vault write pki/root/generate/internal \
  common_name="example.com Internal CA" \
  ttl=87600h

# 创建角色
vault write pki/roles/example-dot-com \
  allowed_domains="example.com" \
  allow_subdomains=true \
  max_ttl=72h

# 生成证书
vault read pki/issue/example-dot-com \
  common_name="app.example.com"

6.3 cert-manager(Kubernetes)

对于运行在 Kubernetes 上的服务,cert-manager 是事实上的标准,它以云原生的方式管理证书。

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: ops@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
      - http01:
          ingress:
            class: nginx
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-com-tls
  namespace: production
spec:
  secretName: example-com-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - example.com
    - www.example.com
  duration: 2160h # 90天
  renewBefore: 360h # 提前15天续期

七、私有证书管理

7.1 建立私有 CA

对于完全在内网的环境,自建一个私有 CA 是最高效省事的办法。

# 创建私有 CA 私钥
openssl genrsa -aes256 -out ca.key 4096

# 创建 CA 证书
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
  -out ca.crt \
  -subj "/CN=Internal Root CA/O=Example Inc"

# 分发 CA 证书到服务器
sudo cp ca.crt /usr/local/share/ca-certificates/internal-ca.crt
sudo update-ca-certificates

7.2 签发内部证书

# 为内部服务签发证书
openssl genrsa -out internal.key 2048

# 创建证书签名请求
openssl req -new -key internal.key \
  -out internal.csr \
  -subj "/CN=internal.example.com/O=Example Inc"

# 签发证书
openssl x509 -req -in internal.csr \
  -CA ca.crt -CAkey ca.key \
  -CAcreateserial \
  -out internal.crt \
  -days 365 \
  -sha256

# 验证证书
openssl verify -CAfile ca.crt internal.crt

八、常见问题处理

8.1 证书链不完整

# 问题:浏览器显示证书链无效
# 解决:确保使用 fullchain.pem 而不是 cert.pem

# Nginx 配置
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;  # 正确
ssl_certificate /etc/letsencrypt/live/example.com/cert.pem;    # 错误

# 验证证书链
openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/null | \
  openssl x509 -noout -text | grep "Certificate chain"

8.2 私钥权限问题

# 问题:Nginx 无法读取私钥
# 解决:确保私钥权限为 600

ls -la /etc/letsencrypt/live/example.com/privkey.pem
# -rw------- 1 root root 1675 Jan 15 00:00 privkey.pem

# 如果权限不正确
sudo chmod 600 /etc/letsencrypt/live/example.com/privkey.pem
sudo chown root:root /etc/letsencrypt/live/example.com/privkey.pem

8.3 OCSP 响应器问题

# 问题:OCSP stapling 验证失败
# 解决:更新中间证书或禁用 OCSP stapling

# 查看中间证书
openssl x509 -in /etc/letsencrypt/live/example.com/chain.pem -text -noout | grep Issuer

# 临时禁用 OCSP stapling
ssl_stapling off;
ssl_stapling_verify off;

8.4 续期失败处理

# 查看续期失败原因
sudo certbot renew --dry-run 2>&1

# 常见失败原因及解决方案

# 1. Web 服务未运行
sudo systemctl start nginx
sudo certbot renew

# 2. 域名 DNS 解析问题
dig example.com
# 确保 DNS 解析正常后再续期

# 3. 80 端口被占用
sudo netstat -tlnp | grep :80
# 释放端口或使用 DNS 验证方式

# 4. Let's Encrypt 限流
# 等待限流解除,或使用 --staging 参数测试
sudo certbot renew --staging

九、最佳实践

9.1 证书管理规范

  • 统一使用 Let's Encrypt 证书,除非有商业证书的强制要求。
  • 证书有效期统一设置为 90 天,续期设置为到期前 30 天。
  • 证书文件统一存放在 /etc/letsencrypt/live/{domain}/ 目录。
  • 私钥文件权限设置为 600,所有者为 root。

9.2 监控告警规范

  • 设置双重告警:证书到期前 30 天(提醒)和 7 天(严重)各一次。
  • 告警内容应包含:域名、剩余天数、过期时间、处理建议。
  • 告警渠道应多渠道送达:邮件、短信、即时通讯(如钉钉、飞书)。
  • 定期测试告警通道是否畅通,别让告警成了摆设。

9.3 自动化规范

  • 所有证书申请、部署、续期必须实现自动化。
  • 自动续期后必须自动重载 Web 服务配置。
  • 保留证书历史的审计日志,方便回溯。
  • 定期执行灾备演练,验证证书恢复流程。

9.4 应急预案

  • 准备手动续期的操作手册,别让线上紧急情况无章可循。
  • 保留历史证书的备份,以便紧急回滚。
  • 关键系统的证书应准备多套备用,以防万一。
  • 建立 24 小时应急响应机制,就是大家俗称的“On-Call”。

总结

HTTPS 证书管理是运维工作中不可忽视的重要环节。证书过期的后果很严重,从用户无法访问到业务中断,再到数据安全风险,都可能造成重大损失。

理解 HTTPS 证书的工作原理是做好管理的基础:公钥密码学保证了通信安全,证书链验证确保了身份可信,证书有效期控制着信任范围。

Let's Encrypt 为免费自动化打开了大门,配合 certbot 或 acme.sh 这类工具,你已经可以把大部分人力解脱出来。

监控告警是防止证书过期的最后一道防线。我始终认为,主动监控远比被动响应更重要,至少设置 30 天和 7 天两档告警是一个普遍认可的好习惯。

日常运维中,不要等到火烧眉毛才想起管理流程。建立规范的证书管理流程,包括申请、部署、监控、应急预案,并定期演练和优化,才能真正做到高枕无忧。如果遇到疑难杂症,也欢迎来云栈社区和大家一起交流探讨,抱团取暖总比单打独斗强。




上一篇:百度拆掉职级墙:数字标签时代的人才军备竞赛
下一篇:Cursor AI代理9秒清空生产数据库:全备份一同消失,安全护栏形同虚设
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-29 08:51 , Processed in 0.929215 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表