Nginx HTTPS 优化
Nginx HTTPS 优化
简介
HTTPS 优化的目标不是单纯"把证书配上",而是同时兼顾安全性、握手性能、连接复用、证书自动化和可观测性。对于高并发 Web/API 服务,TLS 协议版本、会话复用、OCSP Stapling、HTTP/2 以及证书续期流程,都会直接影响首包时间、CPU 占用和线上稳定性。
在生产环境中,一个未经优化的 HTTPS 配置可能导致:TLS 握手耗时 200ms+、CPU 空转在 RSA/ECC 运算上、证书过期导致服务中断、老旧客户端无法访问等严重问题。本文将从协议选型、密码套件、会话复用、证书链、OCSP Stapling、HTTP/2 推送、自动化续期到监控告警,系统性地讲解 Nginx HTTPS 全链路优化方案。
特点
TLS 协议原理与版本演进
TLS 1.0/1.1 的安全缺陷
TLS 1.0(RFC 2246)和 TLS 1.1(RFC 4346)存在多个已知安全漏洞:
- BEAST 攻击:利用 CBC 模式中的 IV 选择问题,可解密部分加密流量
- POODLE 攻击:降级到 SSL 3.0 后利用 CBC 填充漏洞
- RC4 弱加密:RC4 流密码已被证明存在统计偏差
- 缺乏前向保密:长期密钥泄露会导致历史通信被解密
# 测试是否禁用了旧协议
openssl s_client -connect www.example.com:443 -tls1
# 如果返回 handshake failure,说明已正确禁用 TLS 1.0
openssl s_client -connect www.example.com:443 -tls1_1
# 如果返回 handshake failure,说明已正确禁用 TLS 1.1TLS 1.2 的核心改进
TLS 1.2(RFC 5246)是目前兼容性和安全性最平衡的版本:
- 支持 AEAD 密码套件(GCM 模式),替代 CBC 模式
- 完整的密码套件协商机制,支持 ECDHE 密钥交换
- 前向保密(Forward Secrecy)可通过 DHE/ECDHE 实现
- PRF 基于 SHA-256,比 MD5/SHA-1 更安全
# 验证 TLS 1.2 连接是否使用前向保密
openssl s_client -connect www.example.com:443 -tls1_2 | grep "Cipher"
# 输出应包含 ECDHE-RSA 或 ECDHE-ECDSA 前缀TLS 1.3 的革命性变化
TLS 1.3(RFC 8446)是协议层面的重大重构:
- 1-RTT 握手:将握手从 2-RTT 缩短到 1-RTT,首包时间大幅降低
- 0-RTT 恢复:对已建立过连接的客户端,支持零往返时间恢复(需谨慎使用)
- 移除不安全算法:删除 RSA 密钥交换、CBC 模式、RC4、SHA-1、自定义 DH 组
- 强制前向保密:所有密钥交换都必须使用 DHE 或 ECDHE
- 精简密码套件:仅保留 5 个 AEAD 套件
# 验证 TLS 1.3 连接
openssl s_client -connect www.example.com:443 -tls1_3
# 查看支持的 TLS 1.3 密码套件
openssl ciphers -v -s -tls1_3TLS 1.3 的 0-RTT 模式
0-RTT 允许客户端在握手完成之前就发送应用数据,进一步降低延迟:
# 启用 TLS 1.3 的 0-RTT(注意重放攻击风险)
ssl_early_data on;
# 在 server 块中设置 0-RTT 缓冲区大小
ssl_early_data 1048576;
# 在 location 中通过变量判断是否为 0-RTT 请求
# 可用于限制 0-RTT 请求只访问安全接口
location /api/public/ {
if ($ssl_early_data) {
# 0-RTT 请求可能存在重放风险
# 建议只用于幂等的只读接口
}
proxy_pass http://api_backend;
}# 测试 0-RTT 是否生效
curl --tlsv1.3 --early-data "hello" https://www.example.com/api/public/health注意:0-RTT 存在重放攻击风险。攻击者可以录制合法的 0-RTT 请求并重复发送。因此,0-RTT 请求只应用于幂等操作(如 GET 请求),绝不能用于支付、登录等非幂等操作。
实现
TLS 基础安全配置
# /etc/nginx/conf.d/ssl_common.conf
# SSL 全局通用配置,可被各 server 块 include
# 协议版本:只启用 TLS 1.2 和 TLS 1.3
ssl_protocols TLSv1.2 TLSv1.3;
# TLS 1.3 下由客户端优先选择套件(协议规范要求)
# TLS 1.2 下由服务端优先选择更安全的套件
ssl_prefer_server_ciphers off;
# 密码套件配置
# TLS 1.3 套件由协议自动协商,以下主要针对 TLS 1.2
ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256: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';
# DH 参数文件(用于 DHE 密钥交换,增强前向保密)
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
# 会话缓存配置
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;# 生成 DH 参数文件(2048 位,生产建议 4096 位但生成较慢)
openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048
# 生成 4096 位 DH 参数(耗时较长,建议离线生成)
openssl dhparam -out /etc/nginx/ssl/dhparam.pem 4096server {
listen 443 ssl http2;
server_name www.example.com;
# 引入通用 SSL 配置
include /etc/nginx/conf.d/ssl_common.conf;
# 证书配置
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# 安全响应头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# 客户端最大上传大小
client_max_body_size 50m;
# 超时配置
client_body_timeout 60s;
client_header_timeout 60s;
send_timeout 60s;
keepalive_timeout 65s;
location / {
proxy_pass http://app_backend;
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(HSTS 升级前必需)
server {
listen 80;
server_name www.example.com;
# 使用 301 永久重定向(搜索引擎会更新索引)
return 301 https://$host$request_uri;
}
# 也支持 307 临时重定向(用于迁移过渡期)
# return 307 https://$host$request_uri;# 检查配置语法
nginx -t
# 重新加载配置(不中断现有连接)
nginx -s reload
# 检查实际支持的协议
openssl s_client -connect www.example.com:443 -tls1_2
openssl s_client -connect www.example.com:443 -tls1_3
# 检查响应头是否包含 HSTS
curl -I https://www.example.com 2>/dev/null | grep -i strict
# 使用 testssl.sh 进行全面的 SSL 检测(推荐)
# testssl.sh https://www.example.com密码套件详解与选型
密码套件由四个部分组成:密钥交换算法 + 身份认证算法 + 加密算法 + 消息认证码。
# 查看当前 OpenSSL 支持的密码套件
openssl ciphers -v 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'
# 按安全强度排序列出所有可用套件
openssl ciphers -v 'ALL:!COMPLEMENTOFDEFAULT:!eNULL'
# 查看哪些套件提供前向保密
openssl ciphers -v 'ECDHE+AESGCM:ECDHE+CHACHA20' | grep ECDHE常见密码套件对比:
| 套件 | 密钥交换 | 加密 | 认证 | 前向保密 | 说明 |
|---|---|---|---|---|---|
| TLS_AES_256_GCM_SHA384 | (TLS 1.3 内置) | AES-256-GCM | (内置) | 是 | TLS 1.3 默认,最安全 |
| TLS_CHACHA20_POLY1305_SHA256 | (TLS 1.3 内置) | CHACHA20-POLY1305 | (内置) | 是 | 无硬件 AES 时的最佳选择 |
| TLS_AES_128_GCM_SHA256 | (TLS 1.3 内置) | AES-128-GCM | (内置) | 是 | 性能与安全的平衡 |
| ECDHE-RSA-AES128-GCM-SHA256 | ECDHE | AES-128-GCM | RSA | 是 | TLS 1.2 兼容首选 |
| ECDHE-RSA-AES256-GCM-SHA384 | ECDHE | AES-256-GCM | RSA | 是 | TLS 1.2 高安全 |
| ECDHE-RSA-CHACHA20-POLY1305 | ECDHE | CHACHA20-POLY1305 | RSA | 是 | 移动端友好 |
| RSA-AES128-GCM-SHA256 | RSA | AES-128-GCM | RSA | 否 | 不推荐,无前向保密 |
# 移动端优化套件(优先 CHACHA20,因为很多手机没有 AES-NI 硬件加速)
# 将 CHACHA20 放在 AES 之前
ssl_ciphers 'TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';会话复用优化
TLS 握手是最耗时的环节之一(RSA 解密 + 密钥交换 + 证书验证)。会话复用可以让已建立过连接的客户端跳过完整握手。
方式一:Session Cache(服务端缓存)
# shared:SSL:50m — 所有 worker 进程共享 50MB 的会话缓存
# 1MB 约能存储 4000 个会话,50M 约能存储 20 万个
ssl_session_cache shared:SSL:50m;
# 会话超时时间(客户端在此时间内重连可复用会话)
ssl_session_timeout 1d;
# 禁用 session ticket(见下文解释)
ssl_session_tickets off;# 验证会话复用是否生效
# 第一次连接
openssl s_client -connect www.example.com:443 -reconnect 2>/dev/null | grep "New\|Reused"
# 第二次应显示 "Reused, TLSv1.3, Cipher is ..."
# 查看会话缓存命中情况(通过 Nginx stub_status)
curl http://127.0.0.1/nginx_status
# 关注 ssl_handshake 和 ssl_session_reused 指标方式二:Session Ticket(客户端缓存)
# Session Ticket 让客户端保存加密的会话信息
# 优势:无需服务端存储,适合多 Nginx 实例负载均衡场景
# 风险:ticket 密钥泄露会导致会话被伪造
# 启用 session ticket
ssl_session_tickets on;
# 定期轮换 ticket 密钥(建议每天轮换)
ssl_session_ticket_key /etc/nginx/ssl/session_ticket_1.key;
ssl_session_ticket_key /etc/nginx/ssl/session_ticket_2.key;
ssl_session_ticket_key /etc/nginx/ssl/session_ticket_3.key;# 生成 session ticket 密钥
openssl rand 80 > /etc/nginx/ssl/session_ticket_1.key
openssl rand 80 > /etc/nginx/ssl/session_ticket_2.key
openssl rand 80 > /etc/nginx/ssl/session_ticket_3.key
# 通过 cron 定期轮换密钥
cat >/etc/cron.d/nginx-ticket-rotate <<'EOF'
0 0 * * * root \
openssl rand 80 > /etc/nginx/ssl/session_ticket_new.key && \
cp /etc/nginx/ssl/session_ticket_3.key /etc/nginx/ssl/session_ticket_4.key && \
cp /etc/nginx/ssl/session_ticket_2.key /etc/nginx/ssl/session_ticket_3.key && \
cp /etc/nginx/ssl/session_ticket_1.key /etc/nginx/ssl/session_ticket_2.key && \
mv /etc/nginx/ssl/session_ticket_new.key /etc/nginx/ssl/session_ticket_1.key && \
nginx -s reload
EOFSession Cache vs Session Ticket 选择建议:
- 单机或少量 Nginx 实例:优先使用 Session Cache(共享内存)
- 大规模集群(多实例):Session Ticket 更合适(无需共享存储)
- 高安全要求场景:两者结合使用,但关闭 ticket 以减少攻击面
- 注意:TLS 1.3 的会话恢复机制(PSK)与 TLS 1.2 不同,session cache/ticket 配置对 TLS 1.3 会话复用的影响也不同
OCSP Stapling、HTTP/2 与证书链优化
OCSP Stapling 原理
浏览器在验证证书时,需要向 CA 的 OCSP 服务器查询证书状态(是否被吊销)。这会导致:
- 额外的网络请求延迟(OCSP 服务器可能在国外)
- CA 的 OCSP 服务器可能宕机或响应慢
- 隐私泄露(CA 知道哪些用户在访问你的网站)
OCSP Stapling 让 Nginx 主动定期查询 OCSP 响应并缓存,在 TLS 握手时直接将结果"装订"发送给客户端,消除了客户端自行查询的开销。
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# 信任链文件,用于验证 OCSP 响应签名
ssl_trusted_certificate /etc/nginx/ssl/chain.pem;
# 启用 OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
# DNS 解析器配置(用于查询 OCSP 服务器的地址)
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
# OCSP 响应缓存时间(默认 60 分钟)
ssl_stapling_verify on;
location / {
proxy_pass http://api_backend;
}
}# 查看 OCSP Stapling 状态
openssl s_client -connect api.example.com:443 -status -tlsextdebug 2>/dev/null | grep -A 3 "OCSP"
# 手动查询 OCSP 响应(验证 CA 的 OCSP 服务是否正常)
# 先从证书中提取 OCSP URL
openssl x509 -in /etc/nginx/ssl/fullchain.pem -text -noout | grep "OCSP"
# 然后查询 OCSP 响应
openssl ocsp -issuer /etc/nginx/ssl/chain.pem \
-cert /etc/nginx/ssl/fullchain.pem \
-url http://ocsp.digicert.com \
-resp_text
# 查看证书链是否完整
openssl x509 -in /etc/nginx/ssl/fullchain.pem -text -noout | grep -E 'Issuer|Subject|DNS'证书链完整性
# 验证证书链完整性
# 正确的证书链顺序:服务器证书 -> 中间证书 -> 根证书
# fullchain.pem 应包含服务器证书 + 所有中间证书
grep -c "BEGIN CERTIFICATE" /etc/nginx/ssl/fullchain.pem
# 验证私钥和证书是否匹配
openssl x509 -in /etc/nginx/ssl/fullchain.pem -noout -modulus | md5sum
openssl rsa -in /etc/nginx/ssl/privkey.pem -noout -modulus | md5sum
# 两个 MD5 值必须一致
# 验证证书链的完整性
openssl verify -CAfile /etc/nginx/ssl/chain.pem /etc/nginx/ssl/fullchain.pemHTTP/2 Server Push 配置
server {
listen 443 ssl http2;
server_name www.example.com;
# HTTP/2 推送关键资源(注意:HTTP/2 Push 已被 Chrome 弃用)
# 更推荐使用 rel="preload" 和 rel="preconnect" 替代
location / {
proxy_pass http://app_backend;
# 预连接提示
add_header Link "</assets/main.js>; rel=preload; as=script" always;
add_header Link "</assets/main.css>; rel=preload; as=style" always;
}
}HTTP/2 参数调优
http {
# HTTP/2 全局配置
# 每个 worker 进程的 HTTP/2 连接最大并发流数(默认 128)
http2_max_concurrent_streams 128;
# 输入缓冲区大小(默认 64k)
http2_recv_buffer_size 128k;
# 最大请求头大小
http2_max_field_size 16k;
# 最大请求头数量
http2_max_header_fields 100;
# 连接空闲超时(秒)
http2_idle_timeout 60s;
}Brotli/Gzip 压缩与缓存
# Brotli 压缩(需要 nginx-module-brotli 模块)
# Brotli 比 Gzip 压缩率高 15-25%
brotli on;
brotli_comp_level 6;
brotli_min_length 1024;
brotli_types text/plain text/css application/json application/javascript application/xml text/xml image/svg+xml;
# Gzip 作为 Brotli 的降级方案
gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript application/xml text/xml image/svg+xml;
gzip_vary on;
gzip_proxied any;
# 静态资源缓存策略
location /assets/ {
root /opt/app/public;
expires 30d;
add_header Cache-Control "public, immutable";
}
# HTML 文件较短缓存
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}Let's Encrypt 自动续期与热重载
Certbot 申请证书
# 首次申请证书(Nginx 插件方式,自动修改配置)
certbot certonly --nginx -d www.example.com -d api.example.com
# DNS 验证方式(适合通配符证书)
certbot certonly --dns-duckdns -d "*.example.com" -d example.com
# DNS 验证方式(Cloudflare)
certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials /etc/cloudflare/credentials.ini \
-d "*.example.com" -d example.com
# 手动指定 Webroot 方式
certbot certonly --webroot \
-w /var/www/html \
-d www.example.com \
-d api.example.com
# 强制续期(测试用)
certbot renew --dry-run# Let's Encrypt 证书路径
# /etc/letsencrypt/live/www.example.com/fullchain.pem (完整链)
# /etc/letsencrypt/live/www.example.com/privkey.pem (私钥)
# /etc/letsencrypt/live/www.example.com/chain.pem (中间证书链)
# /etc/letsencrypt/live/www.example.com/cert.pem (服务器证书)Cloudflare DNS 验证配置
# /etc/cloudflare/credentials.ini
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN# 设置文件权限
chmod 600 /etc/cloudflare/credentials.ini定时续期配置
# Cron 方式
cat >/etc/cron.d/certbot-renew <<'EOF'
# 每天凌晨 3:15 检查续期,续期成功后重载 Nginx
15 3 * * * root certbot renew --quiet --post-hook "systemctl reload nginx"
EOF
# 查看已配置的定时任务
crontab -l | grep certbot# systemd timer 方式(更现代)
cat >/etc/systemd/system/certbot-renew.service <<'EOF'
[Unit]
Description=Certbot Renewal
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
EOF
cat >/etc/systemd/system/certbot-renew.timer <<'EOF'
[Unit]
Description=Certbot Renewal Timer
[Timer]
OnCalendar=*-*-* 03:15:00
RandomizedDelaySec=3600
Persistent=true
[Install]
WantedBy=timers.target
EOF
# 启用并启动 timer
systemctl daemon-reload
systemctl enable --now certbot-renew.timer
# 查看 timer 状态
systemctl list-timers | grep certbotACME 协议与替代方案
# acme.sh 作为 Certbot 的替代(更轻量)
curl https://get.acme.sh | sh -s email=admin@example.com
# 使用 acme.sh 申请证书
~/.acme.sh/acme.sh --issue -d www.example.com --nginx
# 安装证书到 Nginx 目录
~/.acme.sh/acme.sh --install-cert -d www.example.com \
--key-file /etc/nginx/ssl/privkey.pem \
--fullchain-file /etc/nginx/ssl/fullchain.pem \
--reloadcmd "systemctl reload nginx"
# acme.sh 自动续期(默认已自动配置)
# 查看已安装的证书
~/.acme.sh/acme.sh --list多域名证书管理
# 多域名站点建议拆分 server 块
# 避免证书/配置混杂,便于独立管理
# 主站
server {
listen 443 ssl http2;
server_name www.example.com;
ssl_certificate /etc/nginx/ssl/www_fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/www_privkey.pem;
location / {
proxy_pass http://www_backend;
}
}
# API 站
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/api_fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/api_privkey.pem;
location / {
proxy_pass http://api_backend;
}
}
# 管理后台
server {
listen 443 ssl http2;
server_name admin.example.com;
ssl_certificate /etc/nginx/ssl/admin_fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/admin_privkey.pem;
location / {
proxy_pass http://admin_backend;
}
}
# 默认 server — 处理未知域名(返回 444 断开连接)
server {
listen 443 ssl default_server;
server_name _;
ssl_certificate /etc/nginx/ssl/default_fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/default_privkey.pem;
return 444;
}性能监控与调优
SSL 指标采集
# 开启 stub_status 模块查看 SSL 指标
server {
listen 127.0.0.1:8080;
server_name _;
location /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}
}# 查看 SSL 指标
# 编译时需要 --with-http_ssl_module
# Nginx Plus 提供更详细的 SSL 指标仪表盘
# 开源版可通过日志和 stub_status 获取基本信息
# 查看当前 SSL 连接数
# 通过 error_log level=info 可以看到 SSL 握手信息SSL 性能日志配置
# 在 http 块中配置 SSL 相关的日志格式
log_format ssl_timing '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'ssl_protocol=$ssl_protocol '
'ssl_cipher=$ssl_cipher '
'upstream_addr=$upstream_addr '
'request_time=$request_time '
'upstream_response_time=$upstream_response_time';
server {
listen 443 ssl http2;
access_log /var/log/nginx/ssl_access.log ssl_timing;
}# 分析 SSL 握手性能
# 筛选 TLS 1.2 和 TLS 1.3 的请求时间分布
awk '{print $NF}' /var/log/nginx/ssl_access.log | sort -n | uniq -c | sort -rn | head -20
# 统计各 TLS 版本的占比
grep -oP 'ssl_protocol=\S+' /var/log/nginx/ssl_access.log | sort | uniq -c | sort -rn性能基准测试
# 使用 ab 进行 HTTPS 压测
ab -n 10000 -c 100 -k https://www.example.com/
# 使用 wrk 进行压测(支持 TLS 会话复用)
wrk -t 4 -c 200 -d 30s --latency https://www.example.com/
# 使用 h2load 进行 HTTP/2 压测
h2load -n 10000 -c 100 -m 10 https://www.example.com/
# 使用 siege 压测
siege -c 100 -t 60s -b https://www.example.com/HSTS 详解与最佳实践
# HSTS 响应头配置
# max-age=31536000 — 1 年内强制使用 HTTPS
# includeSubDomains — 所有子域名也强制 HTTPS
# preload — 申请加入浏览器 HSTS 预加载列表
# 初期建议使用较短时间,验证无误后再延长
add_header Strict-Transport-Security "max-age=86400" always;
# 稳定运行后延长到 6 个月
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains" always;
# 最终配置(加入 preload 列表前)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;# 提交到 HSTS Preload 列表
# https://hstspreload.org/
# 注意:一旦加入 preload 列表,几乎无法移除
# Chrome、Firefox、Edge 等浏览器会内置这些域名HSTS 注意事项:
- 一旦开启 HSTS 且 max-age 较长,错误配置回滚成本极高
- 如果证书过期或 HTTPS 配置出问题,用户在 max-age 期间内无法访问
- includeSubDomains 会影响所有子域名,务必确保所有子域名都支持 HTTPS
- preload 是不可逆操作,提交前请仔细评估
优点
缺点
总结
Nginx HTTPS 优化要同时关注"安全"和"性能":TLS 1.2/1.3、合理套件、会话缓存、OCSP Stapling、HTTP/2 与证书自动续期,都是生产环境的核心项。建议先用一套稳妥基线配置跑通,再根据真实客户端兼容性和流量数据做细调。核心优化路径:协议升级 -> 套件调优 -> 会话复用 -> OCSP Stapling -> HTTP/2 -> 证书自动化 -> 监控告警。
关键知识点
fullchain.pem和chain.pem用错,常导致证书链不完整问题- TLS 1.3 下许多套件协商逻辑与 TLS 1.2 不完全相同
ssl_session_cache能明显降低重复握手 CPU 开销- HSTS 一旦开启且时间较长,错误配置回滚成本会变高
- OCSP Stapling 可以消除客户端 OCSP 查询延迟,但需要正确配置 resolver
- Session Ticket 适合多实例场景,但需要定期轮换密钥
- ECDSA 证书比 RSA 证书更小、握手更快,但兼容性略低
- HTTP/2 Push 已被 Chrome 弃用,推荐使用 rel="preload" 替代
- DH 参数文件需要预生成,4096 位更安全但生成耗时较长
项目落地视角
- 官网、API、后台可统一走 HTTPS,80 端口仅保留跳转
- 对接移动端或企业内网客户端时,要提前验证 TLS 兼容性
- 将证书续期、Nginx reload、证书过期监控纳入运维流程
- 静态资源配合 HTTP/2 与缓存头进一步优化首屏体验
- 建议使用 ECC 证书(如 ECDSA P-256)降低握手开销
- 高并发场景下,TLS 终止卸载到专用设备(如硬件负载均衡器)
常见误区
- 只配置证书,不检查证书链和 OCSP 状态
- 直接复制网络上的 cipher 配置,不验证客户端兼容性
- 开启 HSTS 后没有考虑测试域名、临时域名和回滚方案
- 证书续期后不 reload Nginx,线上仍在使用旧证书
- 使用 RSA 2048 证书而不考虑升级到 ECC 证书以提升性能
- 没有配置 ssl_trusted_certificate 导致 OCSP Stapling 无法验证
- 忽视 DNS resolver 配置,导致 OCSP Stapling 查询失败
- 在所有环境都使用同一个 HSTS preload 配置
进阶路线
- 研究 TLS 握手抓包与
openssl s_client诊断方法 - 结合 CDN/WAF/Ingress 设计分层 HTTPS 终止策略
- 使用 ACME 自动签发和集中证书管理平台
- 研究 HTTP/3 / QUIC 在 Nginx 或边缘层的部署方案
- 学习证书透明度(Certificate Transparency)日志监控
- 探索 TLS 指纹识别与反检测技术(如 uTLS)
适用场景
- 所有公网 Web / API 服务
- 需要满足企业安全合规的内网系统
- 高并发站点、API 网关、静态资源分发入口
- 多域名、多环境统一证书治理场景
- 移动端 API 服务(需要优化 TLS 握手性能)
- 金融、医疗等对安全合规有严格要求的行业
落地建议
- 先建立一套团队统一 HTTPS 基线配置模板
- 所有证书都记录域名、签发方式、过期时间和负责人
- 给证书过期、握手失败、OCSP 异常设置监控告警
- 上线前用
openssl、浏览器和真实客户端都做验证 - 建议使用 testssl.sh 或 SSL Labs 进行全面安全扫描
- 对证书过期时间进行分级告警(30 天、14 天、7 天)
排错清单
- 检查证书文件、私钥、完整链是否匹配
- 检查 Nginx 是否启用了预期的 TLS 版本和 HTTP/2
- 检查 OCSP Stapling 是否因 DNS/resolver 配置失败
- 检查续期任务是否执行、reload 是否真正生效
- 检查防火墙是否放行 443 端口和 OCSP 端口
- 检查客户端是否支持配置的密码套件
- 检查 ssl_trusted_certificate 是否包含正确的中间证书
- 检查 DNS 是否正确解析到服务器(尤其 CDN 场景)
- 检查 Nginx error_log 中是否有 SSL 相关错误
复盘问题
- 当前 HTTPS 配置更偏向安全还是兼容性?是否符合业务现实?
- 如果某张证书明天过期,团队能否及时发现并自动处理?
- HSTS、HTTP/2、缓存和证书治理是否已经形成一套标准流程?
- 线上 TLS 握手慢,究竟是证书、网络、会话复用还是上游问题?
- 团队是否有标准的 HTTPS 配置模板和上线检查清单?
- 是否定期使用 SSL Labs 等工具评估 HTTPS 安全评分?
