握手开销从哪来
经典 TLS 1.2 完整握手需要两个来回:ClientHello 到 ServerHello,中间可能插入证书请求的往返,客户端验证证书后才是 ChangeCipherSpec 与 Finished。Nginx 开启 TLS 1.3 后,握手压缩到一个来回。
会话复用是削减重复握手成本的主要手段。它分两种实现:服务端会话缓存(session cache)由 Nginx 保存会话状态,客户端携带 session id 回来复用;Session Ticket 则把状态加密后交给客户端保存,服务端只留密钥。
Nginx 默认启用 Session Ticket 并关闭会话缓存。要改用缓存方案时,必须同时设置 ssl_session_cache 并显式关闭 tickets:两者同时启用会让部分客户端优先用 ticket,导致缓存命中率看起来异常低。
server {
listen 443 ssl;
http2 on;
ssl_certificate /etc/ssl/certs/site.pem;
ssl_certificate_key /etc/ssl/private/site.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:20m;
ssl_session_timeout 1d;
ssl_session_tickets off;
}TLS 1.3 的实际差异
TLS 1.3 删除了大量旧算法:静态 RSA 密钥交换、CBC 模式套件、以及所有 SHA-1 与 MD5 的签名算法,并移除了压缩扩展。握手消息也精简,去掉了 ServerHelloDone 等冗余往返。
前向保密成为默认:仅 TLS 1.3 的密钥交换套件都基于椭圆曲线或 DH,所以不需要额外配置就能获得前向保密,临时密钥会被自动擦除。相应地,SSLv3/TLS 1.0/1.1 应从 ssl_protocols 中移除。
Nginx 上启用 TLS 1.3 只需在 ssl_protocols 里加上 TLSv1.3,它在 OpenSSL 1.1.1 及以上默认可用。需要注意旧客户端兼容:仅支持 TLS 1.2 的客户端会直接握手失败,因此保留 TLSv1.2 作为过渡。开启 http2 时 TLS 1.3 的握手成本进一步摊薄。
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off; # TLS 1.3 下客户端选套件,1.2 段建议单独指定
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;OCSP Stapling 去掉一次往返
证书吊销信息可以在线查询(OCSP),但客户端直连 CA 的查询会增加延迟,还可能因为 CA 不可达而阻塞页面。Stapling 让服务端在握手时顺便把 CA 签名的吊销状态一起递给客户端,客户端不必再单独查询。
Nginx 开启需要三步:启用 ssl_stapling;提供包含颁发者证书的 CA bundle 作为 ssl_trusted_certificate;配置 resolver 指向一个可用的 DNS。因为握手阶段 Nginx 需要解析 CA 的 OCSP 域名,没有 resolver 会导致 stapling 静默失效。
开启后用 `nginx -T` 查看 stapling 状态,并用外部检测工具从客户端侧验证。常见失败原因是 CA bundle 里缺少中间证书,或 ssl_trusted_certificate 指向的文件权限不可读。调试期间可用 ssl_stapling_verify off 关掉颁发者证书校验,但仅用于排查。
resolver 223.5.5.5 223.6.6.6 valid=300s;
resolver_timeout 5s;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/ssl/certs/ca-bundle.crt; # 含中间证书
# 排障用:nginx -T | grep -i stapling短连接站点的优化顺序
第一优先是握手次数。会话复用(缓存或 ticket,二选一)能把重复握手的成本降到接近零,收益最大且改动最小。确认生效的方式是观察同一客户端第二次连接是否明显更快。
第二优先是减少新建连接本身。上游之间用 keepalive 连接池,客户端侧启用 HTTP/2 多路复用,都能让一次握手服务更多请求。对 Nginx 作为反向代理的场景,上游 keepalive 往往比调 TLS 参数收益更直接。
第三才是加密套件与证书链的微调,包括挑选更短证书链、避免不必要的 DH 参数交换等。同时用指标验证:查看 $ssl_session_reused 变量统计复用比例,$ssl_protocol 与 $ssl_cipher 确认实际协商结果,别只看配置文件就宣布优化完成。
log_format ssl "$remote_addr $ssl_protocol $ssl_cipher reused=$ssl_session_reused rt=$request_time";
access_log /var/log/nginx/ssl.log ssl;
upstream backend {
server 10.0.0.11:8080;
keepalive 64; # 上游连接复用
}
location / {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://backend;
}HSTS 与证书运维
会话复用的收益建立在客户端真的连回来的前提上。开启 HSTS(add_header Strict-Transport-Security)让浏览器在 max-age 周期内强制走 HTTPS,避免用户先命中一个 301 跳转,那一次额外往返会抵消大部分优化效果。
证书与私钥的日常运维决定了握手能否顺利完成。用 ssl_certificate 指向完整链(叶子 + 中间)而不是只放叶子证书,链不全是最常见的握手失败原因;证书到期前应接入自动续期并配好 reload 钩子,否则一次到期会让整站瞬间不可用。
改配置后一定要先 `nginx -t` 再 reload,并验证协商结果:用 `openssl s_client -connect` 检查实际协议版本、套件与证书链,再从浏览器侧确认没有降级到 HTTP。HSTS 的 max-age 不宜一开始设成极大值,先用较短值验证所有子域名都已支持 HTTPS,再逐步延长。
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
ssl_certificate /etc/ssl/certs/fullchain.pem;
ssl_certificate_key /etc/ssl/private/site.key;
nginx -t && nginx -s reload
openssl s_client -connect example.com:443 -servername example.com -tls1_3 </dev/null 2>/dev/null | openssl x509 -noout -dates