强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Nginx 从入门到精通 / 07 - HTTPS 与 TLS / HTTPS & TLS

HTTPS 与 TLS / HTTPS & TLS

🟢 基础 / Basics — 快速启用 HTTPS

Let’s Encrypt + Certbot(免费证书)

# 1. 安装 Certbot
sudo apt install -y certbot python3-certbot-nginx

# 2. 自动获取证书并配置 Nginx
sudo certbot --nginx -d example.com -d www.example.com

# 3. 验证自动续期
sudo certbot renew --dry-run

# 4. 设置自动续期 cron
echo "0 3 * * * root certbot renew --quiet --post-hook 'systemctl reload nginx'" \
    | sudo tee /etc/cron.d/certbot-renew

执行后 Certbot 会自动修改 Nginx 配置,生成类似:

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;   # HTTP 强制跳转 HTTPS
}

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

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

手动配置 SSL

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

    # 证书文件
    ssl_certificate     /etc/nginx/ssl/example.com.crt;   # 证书链(含中间证书)
    ssl_certificate_key /etc/nginx/ssl/example.com.key;    # 私钥

    # SSL 会话缓存
    ssl_session_cache shared:SSL:10m;    # 10MB 共享缓存,约 40,000 个会话
    ssl_session_timeout 1d;              # 会话有效期 1 天
    ssl_session_tickets off;             # 禁用 session tickets(更安全)

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

HTTP 强制跳转 HTTPS

# 方式 1:独立 server 块(推荐)
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

# 方式 2:在同一个 server 块中用 if
server {
    listen 80;
    listen 443 ssl;
    server_name example.com;

    if ($scheme = http) {
        return 301 https://$host$request_uri;
    }
    # ⚠️ 不推荐,if 在 server 块中有坑
}

🟡 进阶 / Intermediate — TLS 安全配置

现代 TLS 配置(Mozilla 推荐)

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

    ssl_certificate     /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;

    # 协议版本:只允许 TLS 1.2 和 1.3
    ssl_protocols TLSv1.2 TLSv1.3;

    # 密码套件(TLS 1.2)
    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:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

    # 优先使用服务端密码套件
    ssl_prefer_server_ciphers off;    # TLS 1.3 建议 off

    # DH 参数(TLS 1.2 需要)
    ssl_dhparam /etc/nginx/ssl/dhparam.pem;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/nginx/ssl/chain.pem;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;
}
# 生成 DH 参数(耗时较长,建议 2048 位)
sudo openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048

HSTS(HTTP 严格传输安全)

server {
    listen 443 ssl;
    server_name example.com;

    # 告诉浏览器:未来 1 年内只用 HTTPS 访问
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    # ⚠️ 注意:
    # 1. 一旦启用 preload 并提交到浏览器预加载列表,将无法轻易撤销
    # 2. 子域名也会被强制 HTTPS
    # 3. 先用小的 max-age 测试,确认无误后再加大
}
HSTS 工作流程:

首次访问(无 HSTS 缓存):
Client → http://example.com → 301 → https://example.com ✓

后续访问(有 HSTS 缓存):
Client → 直接访问 https://example.com(浏览器自动跳转,不发 HTTP 请求)
         ↑ 完全消除了首次 HTTP→HTTPS 跳转中的中间人攻击窗口

多域名 / 通配符证书

# 通配符证书(*.example.com)
server {
    listen 443 ssl;
    server_name example.com *.example.com;
    ssl_certificate     /etc/nginx/ssl/wildcard.example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/wildcard.example.com.key;
}

# SAN 证书(多域名共用一个证书)
server {
    listen 443 ssl;
    server_name example.com www.example.com api.example.com;
    ssl_certificate     /etc/nginx/ssl/multi-domain.crt;
    ssl_certificate_key /etc/nginx/ssl/multi-domain.key;
}

# SNI:不同域名使用不同证书
server {
    listen 443 ssl;
    server_name site-a.com;
    ssl_certificate     /etc/nginx/ssl/site-a.crt;
    ssl_certificate_key /etc/nginx/ssl/site-a.key;
}

server {
    listen 443 ssl;
    server_name site-b.com;
    ssl_certificate     /etc/nginx/ssl/site-b.crt;
    ssl_certificate_key /etc/nginx/ssl/site-b.key;
}
SNI (Server Name Indication) 原理:

Client                              Nginx
  │                                   │
  │── ClientHello ─────────────────►  │
  │   SNI: example.com                │   ← 客户端在握手阶段就告诉服务器域名
  │                                   │
  │◄── ServerHello ──────────────────│   ← Nginx 选择对应的证书
  │   Certificate: *.example.com      │
  │                                   │
  │   TLS 握手完成,开始 HTTP 通信       │

🔴 高级 / Advanced — TLS 高级主题

双向 TLS(mTLS / 客户端证书认证)

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

    # 服务端证书
    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;

    # 客户端证书验证
    ssl_client_certificate /etc/nginx/ssl/ca.crt;   # CA 证书
    ssl_verify_client on;                             # 要求客户端证书
    ssl_verify_depth 2;

    location /api/ {
        # 将客户端证书信息传给后端
        proxy_set_header X-Client-Cert-DN $ssl_client_s_dn;
        proxy_set_header X-Client-Cert-Serial $ssl_client_serial;
        proxy_set_header X-Client-Cert-Verify $ssl_client_verify;
        proxy_pass http://backend;
    }

    # 可选:某些路径不要求客户端证书
    location /health {
        ssl_verify_client optional;   # 可选认证
        return 200 "OK";
    }
}
mTLS 流程:

Client                              Nginx
  │                                   │
  │── ClientHello ─────────────────►  │
  │◄── ServerHello + ServerCert ─────│   ← 标准 TLS
  │                                   │
  │◄── CertificateRequest ──────────│   ← 要求客户端证书
  │── ClientCert + ClientVerify ────►│   ← 客户端发送证书
  │                                   │
  │   双方验证证书,建立加密通道          │

TLS 1.3 0-RTT(Early Data)

server {
    listen 443 ssl;
    server_name example.com;

    ssl_protocols TLSv1.3;

    # 启用 0-RTT
    ssl_early_data on;

    # 将 Early-Data 头传给后端(防止重放攻击)
    proxy_set_header Early-Data $ssl_early_data;
}
传统 TLS 1.2(2-RTT):
Client → ServerHello     → 1 RTT
Client → ApplicationData → 2 RTT

TLS 1.3(1-RTT):
Client → ServerHello     → 1 RTT
Client → ApplicationData

TLS 1.3 0-RTT(重连时):
Client → ApplicationData → 0 RTT(首次数据和握手同时发送)
         ↑ 但有重放攻击风险,只能用于幂等请求

OCSP Stapling 详解

server {
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/nginx/ssl/chain.pem;

    # OCSP 响应缓存
    resolver 1.1.1.1 8.8.8.8 valid=300s;
    resolver_timeout 5s;
}
无 OCSP Stapling:
Client → OCSP Responder(证书颁发机构)
         1. 额外的网络请求
         2. 暴露用户浏览记录给 CA
         3. 增加延迟

有 OCSP Stapling:
Nginx → OCSP Responder(定期查询,缓存响应)
Client → Nginx(直接返回缓存的 OCSP 响应)
         1. 无额外请求
         2. 用户隐私不受影响
         3. 更快的握手

SSL 性能优化

server {
    # 1. 会话缓存(减少完整握手)
    ssl_session_cache shared:SSL:50m;    # 50MB,约 200,000 个会话
    ssl_session_timeout 4h;
    ssl_session_tickets on;
    ssl_session_ticket_key /etc/nginx/ssl/ticket.key;   # 轮换 key

    # 2. 硬件加速(如果有 AES-NI)
    # Nginx 自动使用,无需额外配置
    # 验证: openssl engine -t

    # 3. 早发送(TCP_CORK / TCP_NODELAY)
    ssl_buffer_size 4k;    # 默认 16k,小值可减少 TTFB
}
# 验证 SSL 配置
# 在线工具
# https://www.ssllabs.com/ssltest/

# 命令行测试
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3

# 查看证书信息
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -text -noout

自签名证书(开发环境)

# 生成自签名证书
openssl req -x509 -nodes -days 365 \
    -newkey rsa:2048 \
    -keyout /etc/nginx/ssl/selfsigned.key \
    -out /etc/nginx/ssl/selfsigned.crt \
    -subj "/CN=localhost"

# 或使用 mkcert(更方便,自动信任)
# 安装: https://github.com/FiloSottile/mkcert
mkcert -install
mkcert localhost 127.0.0.1 ::1
# 生成 localhost+2.pem 和 localhost+2-key.pem

小结 / Summary

层级你需要知道的 / What You Need to Know
🟢 基础Let’s Encrypt + Certbot,HTTP → HTTPS 跳转
🟡 进阶TLS 1.2/1.3 配置,HSTS,OCSP Stapling,SNI 多证书
🔴 高级mTLS 双向认证,TLS 1.3 0-RTT,会话缓存优化,SSL 性能调优

下一章:缓存机制 / Caching