4、HTTPS证书配置:SSL/TLS原理、免费证书申请(Let's Encrypt)、Nginx证书配置、证书自动续期

说实话,HTTPS 配置这块,很多开发觉得「不就是买个证书挂上去吗」。但我在生产环境踩过的坑告诉我——证书配置从来不是挂上去就完事。你想想看,一个证书过期导致整个支付接口挂掉,这种事故我见过不止一次。

这一章,我会把 SSL/TLS 的核心原理讲清楚,然后手把手带你走一遍免费证书申请、Nginx 配置、自动续期的完整流程。嗯,都是我在线上环境验证过的做法。

核心观点:HTTPS 不是「安全加成」,而是现代接口的基本准入条件。iOS ATS、Chrome 标记「不安全」、小程序强制 HTTPS——这些都在逼你做好证书配置。

HTTPS 证书配置知识体系 HTTPS 证书配置 SSL/TLS 原理 免费证书申请 Nginx 证书配置 自动续期 握手过程 证书链验证 Let's Encrypt ACME 协议 SSL 配置指令 HSTS cron 定时任务 续期验证 ⚠️ 避坑要点 • 证书链不完整 → 部分老旧客户端握手失败 • 续期脚本未验证 → 证书过期导致线上事故 • 密钥权限过大 → 安全审计不过关

4.1 SSL/TLS 原理:别被「握手」吓到

SSL/TLS 说白了就是在 TCP 之上加了一层加密隧道。客户端和服务端在正式传数据之前,先「握手」协商出一把对称密钥,之后所有通信都用这把密钥加密。

握手过程大致分四步:

  1. Client Hello:客户端发来支持的 TLS 版本、加密套件列表、一个随机数。
  2. Server Hello:服务端选定加密套件,发回自己的证书(含公钥)和另一个随机数。
  3. 证书验证 & 密钥交换:客户端验证证书合法性,然后用公钥加密一个「预主密钥」发给服务端。
  4. 握手完成:双方用三个随机数算出会话密钥,之后所有数据对称加密传输。

我的经验:很多人以为 HTTPS 慢是因为加密计算。其实握手阶段的网络往返才是主要开销。我曾在海外节点上做过测试,TLS 1.3 相比 1.2 减少了一次 RTT,接口延迟直接降了 30%。所以能用 TLS 1.3 就别用 1.2。

证书链验证这块容易出问题。服务端不仅要发自己的证书,还要发中间证书。我曾经遇到过只配了域名证书、没配中间证书的情况——Chrome 访问正常,但一个老版本 Android 客户端死活连不上。查了半天,就是证书链不完整。

4.2 免费证书申请:Let's Encrypt + ACME

Let's Encrypt 是目前最主流的免费 CA。它基于 ACME 协议自动验证域名所有权。我个人习惯用 certbot 这个客户端,简单直接。

申请流程分两步:

  • 验证域名所有权:常用 HTTP-01 挑战(在网站根目录放一个指定文件)或 DNS-01 挑战(在 DNS 记录加 TXT 记录)。
  • 签发证书:验证通过后,CA 返回证书文件(fullchain.pem)和私钥(privkey.pem)。

安装 certbot 并申请证书:

# 安装 certbot(Ubuntu/Debian)
sudo apt update
sudo apt install certbot python3-certbot-nginx

# 申请证书(自动配置 Nginx)
sudo certbot --nginx -d api.example.com

# 或者只申请证书,手动配置
sudo certbot certonly --nginx -d api.example.com

申请成功后,证书文件默认放在 /etc/letsencrypt/live/api.example.com/ 目录下:

文件 用途
fullchain.pem 域名证书 + 中间证书(Nginx 配置用这个)
privkey.pem 私钥文件(权限必须设为 600)
chain.pem 仅中间证书(某些场景需要)
cert.pem 仅域名证书(一般不单独用)

⚠️ 注意:Let's Encrypt 证书有效期只有 90 天。这不是 bug,是设计——强制你自动化续期,避免证书长期有效带来的安全风险。所以千万别手动续期,一定要配自动续期。

4.3 Nginx 证书配置:别只配个路径就完事

Nginx 配置 HTTPS 其实就几个指令,但细节决定成败。我见过太多人只配了 ssl_certificate 和 ssl_certificate_key,其他全默认——结果安全评级是 C,或者兼容性一塌糊涂。

下面是我在生产环境用的配置模板:

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

    # 证书路径
    ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

    # 协议和加密套件
    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 on;

    # 会话缓存(提升性能)
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # HSTS(告诉浏览器强制 HTTPS)
    add_header Strict-Transport-Security "max-age=63072000" always;

    # 其他配置
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

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

几个关键点:

  • ssl_protocols:只开 TLSv1.2 和 TLSv1.3,关掉 TLSv1.0/1.1(有安全漏洞)。
  • ssl_ciphers:只选前向安全的加密套件(ECDHE 开头)。
  • HSTS:配了之后,浏览器下次直接走 HTTPS,不再尝试 HTTP。我建议 max-age 至少设半年。
  • http2:在 listen 指令加上 http2,多路复用能显著提升接口并发性能。

配置完记得检查语法并重载:

sudo nginx -t
sudo systemctl reload nginx

4.4 证书自动续期:别让证书过期毁了你的周末

Let's Encrypt 证书 90 天过期,手动续期不现实。certbot 自带续期功能,配一个 cron 定时任务就行。

先测试续期是否正常:

sudo certbot renew --dry-run

如果没问题,添加 cron 任务(每天执行两次,但只有到期前 30 天才会真正续期):

# 编辑 crontab
sudo crontab -e

# 添加以下行(每天凌晨 3 点和下午 3 点执行)
0 3,15 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"

我的习惯:我会在续期脚本里加一个日志输出,方便排查问题:

0 3,15 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx" >> /var/log/certbot-renew.log 2>&1

这样如果续期失败,我能从日志里看到具体错误。我曾经遇到过 DNS 解析临时故障导致续期失败,就是靠日志发现的。

⚠️ 避坑指南:

  • 不要用 root 运行 certbot:用 sudo 或专用用户,避免权限问题。
  • 续期后一定要 reload Nginx:证书文件更新了,但 Nginx 不会自动加载新证书。我见过有人配了 cron 但忘了加 --post-hook,结果证书过期了 Nginx 还在用旧证书。
  • 监控续期状态:建议加一个外部监控(比如 Prometheus + Blackbox Exporter),定期检查证书过期时间。不要等到用户报错才发现。

最后,验证证书是否配置正确,可以用 OpenSSL 命令测试:

echo | openssl s_client -connect api.example.com:443 -servername api.example.com 2>/dev/null | openssl x509 -noout -dates

这个命令会输出证书的生效时间和过期时间。我每次部署完都会跑一遍,确认无误再切流量。