跳转至

SSL 证书运维补课:从原理到实战,再到面试该怎么聊

写在前面

很多运维同学的尴尬场景:

网站要上 HTTPS、客户经理把一个 .pem 和一个 .key 文件丢给你,让你"部署一下"。 你部署完了,过半年突然有用户反馈"网站不安全",一查发现证书过期了。

这就是 SSL 证书运维的全部典型形态:平时没存在感,出事就是 P0

这篇文章的目标,是让没有系统做过 SSL 证书运维的人,用一两个小时建立起一个能在面试里聊得清楚、能在生产里上手干活的基础认知

不会展开密码学细节,重点放在:

  • 这玩意儿到底解决什么问题
  • 证书是怎么来的、怎么验的、怎么会过期
  • 在 Nginx、阿里云 SLB/ALB、K8s Ingress 这三种场景里怎么部署
  • 自动续签怎么做(acme.sh / cert-manager)
  • 出问题的时候怎么排查
  • 面试官常问的几个坑

一句话理解 SSL/TLS 证书

证书 = 一张由权威机构盖章过的"身份证",用来证明这个域名是你的,并且把你的公钥交给对方,让对方可以加密通信。

拆开看:

  • 身份:证书里写了 CN=blog.example.com,告诉浏览器"这就是 blog.example.com 的证书"
  • 盖章:由 CA(证书颁发机构)用它的私钥对你的证书做签名,浏览器内置了 CA 的公钥,能验证签名
  • 公钥:证书里有你的公钥,客户端可以用它加密一个临时密钥,开始 HTTPS 通信

注意:HTTPS 不是用证书里的公钥来加密整个会话内容,而是用它来交换"会话密钥",之后用对称加密做实际传输。这是面试常见考点,下面会讲。

为什么需要证书:HTTPS 解决什么

HTTP 三个问题,HTTPS 全解决:

问题 HTTP HTTPS(=HTTP + TLS)
通信内容能被偷看吗? 不能(加密)
通信内容能被改吗? 不能(完整性校验)
你怎么知道你连的是真的 baidu.com,而不是中间人冒充的? 不知道 能(证书验证)

第三个最关键:没有证书,加密本身没意义——你和"中间人"加密得再好,他也是真的中间人。

证书的核心价值就是:让客户端确认对面的服务器是它声称的那个域名的合法持有者

证书链:根证书、中间证书、服务器证书

这是新手最容易绕晕的地方,但其实只需要记住一个画面:

flowchart TB
    Root["🏛️ Root CA<br/>(自签,浏览器内置)<br/>例如:DigiCert Global Root CA"]
    Inter["🏢 中间 CA<br/>(被 Root CA 签发)<br/>例如:DigiCert TLS RSA SHA256 2020 CA1"]
    Server["📜 你的服务器证书<br/>(被中间 CA 签发)<br/>blog.example.com"]
    Root -->|签发| Inter
    Inter -->|签发| Server

为什么要分三层?

  • 根证书私钥太重要,不能频繁拿出来用,所以放保险柜里离线保管
  • 日常签发用中间 CA,万一被攻破只需要吊销中间 CA,不影响根证书

浏览器验证的过程:

  1. 拿到你的服务器证书
  2. 看签发者是谁 → 中间 CA
  3. 中间 CA 的证书也带在握手数据里 → 看它的签发者 → 根 CA
  4. 根 CA 在浏览器/操作系统内置的信任列表里 → 通过

重点:服务端必须把"服务器证书 + 中间证书"一起发给客户端,这叫"证书链完整性"。如果你只配了服务器证书,没配中间证书,PC 浏览器可能没事(它本地缓存过中间证书),但 手机 App、curl、Java 程序、安卓老版本就会报"证书不被信任"

这是生产里最常见的 SSL 问题之一,必须刻进脑子。

一次完整的 HTTPS 握手在做什么

简化版(TLS 1.2 RSA 密钥交换):

sequenceDiagram
    participant C as 客户端(浏览器)
    participant S as 服务端(Nginx)
    C->>S: ClientHello(我支持哪些加密算法)
    S->>C: ServerHello + 证书链 + 公钥
    Note over C: 1. 验证证书链<br/>2. 验证域名<br/>3. 验证有效期
    C->>S: 用服务端公钥加密的 PreMasterSecret
    Note over C,S: 双方用 PreMasterSecret 算出对称密钥
    C->>S: 加密的"完成"消息
    S->>C: 加密的"完成"消息
    Note over C,S: 之后所有 HTTP 流量用对称密钥加密

面试要点:

  • 非对称加密只用在握手阶段(交换密钥),传输用对称加密(AES-GCM 等),因为非对称加密慢
  • TLS 1.3 简化了流程(1-RTT 甚至 0-RTT),且强制前向安全(PFS)
  • "中间人攻击"无法成功的根本原因:中间人没有私钥,无法用真证书的私钥签名 ServerHello,伪造的证书过不了 CA 链验证

证书是怎么来的:申请流程

flowchart LR
    A[1. 生成私钥<br/>private.key] --> B[2. 生成 CSR<br/>certificate signing request]
    B --> C{选哪种 CA?}
    C -->|免费| D[Let's Encrypt<br/>acme 协议自动签发]
    C -->|商业| E[DigiCert / GeoTrust<br/>提交 CSR 人工审核]
    C -->|云厂商| F[阿里云 SSL<br/>免费 DV / 付费 OV/EV]
    D --> G[拿到 fullchain.pem<br/>含服务器证书+中间证书]
    E --> G
    F --> G

关键概念:

  • 私钥(private.key)永远不要交给 CA,CSR 里只有公钥
  • CSR(Certificate Signing Request)= 你的公钥 + 域名 + 组织信息,CA 用自己的私钥对它签名后变成证书
  • DV / OV / EV
    • DV(Domain Validation):只验你拥有这个域名(最常见,Let's Encrypt 就是 DV)
    • OV(Organization Validation):还要验你公司的工商信息
    • EV(Extended Validation):最严格,浏览器地址栏以前会显示绿色公司名(现在淡化了)

95% 的业务场景 DV 够用,免费且能自动续签。除非合规要求或对外品牌站,不用花钱买 OV/EV。

三种典型部署场景

场景一:Nginx 单机部署

最朴素,文件级配置。

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

    # 注意:证书文件必须是"服务器证书 + 中间证书"拼接
    ssl_certificate     /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/private.key;

    # 推荐配置(兼顾安全和兼容)
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;

    # HSTS:强制浏览器后续都走 HTTPS(生产环境推荐)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    location / {
        proxy_pass http://backend;
    }
}

# 80 端口跳转到 443
server {
    listen 80;
    server_name blog.example.com;
    return 301 https://$server_name$request_uri;
}

重点fullchain.pem 不是单独的服务器证书,它是「服务器证书 + 中间证书」的拼接(顺序:自己的在前,中间 CA 在后)。Let's Encrypt 直接给你这个拼好的文件,但商业 CA 经常只给你两个独立文件,需要手动 cat

# 商业 CA 的常见拼接方式
cat server.crt intermediate.crt > fullchain.pem

场景二:阿里云 SLB / ALB / CDN

云上场景,证书放在接入层,后端可以走 HTTP。

flowchart LR
    User[用户] -->|HTTPS:443| ALB[阿里云 ALB<br/>挂证书]
    ALB -->|HTTP:80| Pod1[Pod 1]
    ALB -->|HTTP:80| Pod2[Pod 2]

操作流程:

  1. 在「阿里云 SSL 证书服务」申请证书(DV 免费一年,可领多张)
  2. 在 ALB / SLB 的 HTTPS 监听里绑定证书 ID
  3. 后端服务器组用 HTTP 协议(SSL 卸载在 ALB 完成

好处:

  • 业务方不用改代码、不用关心证书续签部署
  • ALB 一键开启 HTTP/2、HSTS、强加密套件
  • 阿里云 SSL 证书服务本身能提醒你过期

坑:

  • 阿里云免费 DV 证书有效期从 1 年降到 3 个月(向 Let's Encrypt 看齐),必须做自动化续签和自动化下发到 ALB
  • 如果业务里有"读取真实客户端 IP"的需求,要在 ALB 上启用 X-Forwarded-For,并在后端 Nginx 配置 real_ip_header

场景三:Kubernetes Ingress + cert-manager

云原生标准方案,强烈推荐,因为它把证书的"申请、续签、下发"全自动化了。

flowchart TB
    User[用户] -->|HTTPS| Ingress[Ingress Controller<br/>Nginx/ALB Ingress]
    Ingress --> Svc[Service]
    Svc --> Pod[Pod]

    CM[cert-manager] -->|签发请求| LE[Let's Encrypt]
    LE -->|证书| CM
    CM -->|写入 Secret| K8s[(Kubernetes Secret)]
    Ingress -.->|读取证书| K8s

最小可用配置:

# 1. ClusterIssuer:定义"用 Let's Encrypt 签证书"
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-key
    solvers:
    - http01:
        ingress:
          class: nginx
---
# 2. Ingress:声明要哪个域名的证书
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: blog
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
  - hosts:
    - blog.example.com
    secretName: blog-tls   # cert-manager 会自动把证书写到这个 Secret
  rules:
  - host: blog.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: blog
            port:
              number: 80

部署完 cert-manager 会自动签发、自动续签、自动滚动到 Ingress。生产环境是真香方案。

自动化续签:Let's Encrypt + acme.sh

非 K8s 场景的轻量方案,几乎所有 Linux 服务器都能用:

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

# 申请证书(DNS 验证方式,推荐,不用开 80 端口)
export Ali_Key="阿里云 AccessKey"
export Ali_Secret="阿里云 SecretKey"
acme.sh --issue --dns dns_ali -d blog.example.com -d "*.example.com"

# 安装到 Nginx 目录,并配置 reload 钩子
acme.sh --install-cert -d blog.example.com \
    --key-file       /etc/nginx/ssl/private.key \
    --fullchain-file /etc/nginx/ssl/fullchain.pem \
    --reloadcmd      "nginx -s reload"

acme.sh 会自动加一个 cron 任务,每天凌晨检查证书有效期,剩 30 天就自动续签 + reload。一次配好,半年后才会想起它的存在。

生产里最常见的 5 类故障

1. 证书过期

最低级也最常见。每个证书都必须有监控

  • 简单粗暴:阿里云 SSL 服务自带过期提醒(短信/钉钉/邮件)
  • 自建监控:Prometheus blackbox_exporter 探测目标域名的证书剩余天数,剩 14 天告警
  • 业务监控:APM 平台(ARMS、Datadog)大多内置 SSL 过期检查

2. 证书链不全

服务端只发了服务器证书,没发中间证书。

排查命令:

# 看完整握手过程,注意 Certificate chain 部分
openssl s_client -connect blog.example.com:443 -servername blog.example.com -showcerts

# 在线工具:SSL Labs(最权威)
# https://www.ssllabs.com/ssltest/

修复:把中间 CA 证书拼接进 fullchain.pem,reload。

3. 域名不匹配 / SAN 缺失

证书签的是 example.com,但用户访问 www.example.com 报错。

现代证书必须带 SAN(Subject Alternative Name)扩展,单 CN 字段已经被浏览器废弃。申请时记得把所有要支持的域名都加进 -d 参数(acme.sh)或 SAN 字段(商业 CA)。

4. SNI 配置错误

SNI:一台服务器一个 IP,挂多个 HTTPS 域名时,客户端要在握手最开始告诉服务器"我要访问哪个域名",服务器才知道返回哪张证书。

# 不带 SNI(老客户端、curl 默认)→ 返回默认证书,可能错
openssl s_client -connect 1.2.3.4:443

# 带 SNI(现代客户端)→ 返回正确证书
openssl s_client -connect 1.2.3.4:443 -servername blog.example.com

故障表现:浏览器没事,某些老 SDK / Java 6 客户端报错。修复:升级客户端,或服务端把默认证书设成兜底证书。

5. 私钥和证书不匹配

部署时拿错了文件。验证方法:

# 两个 md5 必须一致
openssl x509 -noout -modulus -in fullchain.pem | openssl md5
openssl rsa  -noout -modulus -in private.key   | openssl md5

实用命令速查

# 看证书内容(颁发者、有效期、SAN 等)
openssl x509 -in fullchain.pem -text -noout

# 只看有效期
openssl x509 -in fullchain.pem -noout -dates

# 远程探测证书(最常用)
echo | openssl s_client -connect blog.example.com:443 -servername blog.example.com 2>/dev/null \
  | openssl x509 -noout -dates -subject -issuer

# 生成自签证书(内部测试用,不要用于生产)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \
  -days 365 -nodes -subj "/CN=test.local"

# 检查证书链是否完整
openssl s_client -connect blog.example.com:443 -showcerts < /dev/null

面试可能被问到的点(重点)

Q1:HTTPS 比 HTTP 多了哪几步?

握手阶段:协商加密套件、传证书、验证身份、交换密钥。 传输阶段:所有数据用对称加密。

Q2:证书过期了,业务影响是什么?怎么避免?

影响:浏览器拦截、App 信任链断裂、API 调用方报错(尤其 Java HttpClient)。 避免:监控提醒 + 自动化续签(acme.sh / cert-manager)+ 人工 backup。

Q3:你怎么管理生产环境的私钥?

  • 私钥不入 Git(哪怕加密的)
  • 阿里云场景:放在 KMS / 凭据管家,应用按需拉取
  • K8s:私钥存 Secret,配合 cert-manager 自动管理,避免人工接触
  • 堡垒机审计:私钥文件的访问、下载行为留痕

Q4:自签证书和 CA 签发证书的区别是什么?为什么浏览器不信任自签?

CA 签发的证书可以追溯到一个预装在操作系统/浏览器里的根证书。自签证书没有这条信任链,所以浏览器会拦截。 内部场景可以用自签 +「把内部 CA 证书装到所有员工电脑」的方式做内网 HTTPS(很多公司这么做)。

Q5:你怎么保证证书的及时续签?

我们的方案是:

  • 接入层(ALB / Nginx):cert-manager 或 acme.sh 自动续签
  • 监控告警:Prometheus blackbox 探测剩余天数 + 阿里云 SSL 服务的过期提醒
  • 灰度验证:续签后小流量探测,确认握手成功才滚到全量
  • 应急预案:证书续签脚本失败会触发告警到运维群,最低也有 14 天处置窗口

Q6:HTTPS 一定安全吗?

不一定。证书私钥泄露、客户端被植入了恶意根证书、TLS 配置降级、心血漏洞等都能击穿。所以才要 HSTS、证书钉扎(HPKP/CT)、定期轮换私钥、关闭老 TLS 版本。

一句话总结

证书运维 = "这个域名是我的"这句话怎么自动化、可监控、可审计地告诉全世界。

把「证书链、续签、监控、私钥保护」四件事做对,90% 的场景就稳了。