HTTPS 慢半拍?一文讲透 TLS 握手原理与性能优化实战

分类:测速指南 发布时间:2026-05-14

一、为什么 TLS 握手值得你花一篇文章的时间去搞懂

先看一组实测数据。同样一个网站,从北京电信发起测速:

阶段优化前优化后
DNS Lookup32ms32ms
TCP Connect48ms48ms
TLS Handshake890ms145ms
TTFB180ms180ms
Content Download92ms92ms
总耗时1242ms497ms

什么都没改,只调了 TLS 配置,总耗时直接砍掉 60%。这种"高 ROI"的优化点,在性能优化里其实非常少见。

而且 TLS 握手有一个让人头疼的特性:它每建立一个新连接都要重来一次。用户每点开一个新页面、每打开一个新标签、CDN 每对接一个新源站——只要冷启动了,TLS 就得重新握手。所以这一段的优化,是真正的"每个用户都受益"。

下面我们从底层协议开始拆。

二、TLS 握手到底在做什么?

TLS 握手要解决三件事:

  1. 身份认证——客户端怎么确认服务器真的是它声称的那个?(防止中间人)
  2. 密钥协商——双方在不安全的网络上,怎么协商出一把只有彼此知道的加密钥匙?
  3. 参数协商——用什么版本、什么加密算法、什么压缩方式?

解决这三件事,TLS 1.2 需要 2 个 RTT,TLS 1.3 只需要 1 个 RTT。这就是为什么升级版本是最容易的性能提升。

TLS 1.2 完整握手(4 步)

客户端                                     服务器
   │                                          │
   │ ──── ClientHello ──────────────────────▶ │   ← RTT #1 开始
   │      (支持的 TLS 版本、加密套件、随机数)   │
   │                                          │
   │ ◀──── ServerHello ────────────────────── │
   │       Certificate                        │
   │       ServerKeyExchange                  │
   │       ServerHelloDone                    │   ← RTT #1 结束
   │                                          │
   │ ──── ClientKeyExchange ────────────────▶ │   ← RTT #2 开始
   │      ChangeCipherSpec                    │
   │      Finished                            │
   │                                          │
   │ ◀──── ChangeCipherSpec ────────────────── │
   │       Finished                           │   ← RTT #2 结束
   │                                          │
   │       开始传输加密数据                     │

整个过程需要 2 次完整的网络往返。假设 RTT 是 50ms,光 TLS 就要 100ms 起步。

TLS 1.3 完整握手(2 步)

客户端                                     服务器
   │                                          │
   │ ──── ClientHello ──────────────────────▶ │   ← RTT #1 开始
   │      (key_share, 直接带上密钥协商参数)     │
   │                                          │
   │ ◀──── ServerHello ────────────────────── │
   │       (EncryptedExtensions)              │
   │       Certificate                        │
   │       CertificateVerify                  │
   │       Finished                           │   ← RTT #1 结束
   │                                          │
   │ ──── Finished ─────────────────────────▶ │
   │      开始传输加密数据 (可以同包发送)        │

TLS 1.3 把密钥协商的关键信息合并到了第一次往返里,少了一整个 RTT。同样 50ms 的 RTT,TLS 1.3 只要 50ms。

更进一步,TLS 1.3 还支持 0-RTT 模式:如果客户端之前连接过这个服务器,下次可以直接在 ClientHello 里塞加密数据,实现"零等待"。这个后面会讲。

三、让 TLS 变慢的 5 个常见原因

原因 1:服务器还在用 TLS 1.2 甚至更老的版本

这是最常见、也是最容易解决的问题。

检测方法:

openssl s_client -connect www.example.com:443 -servername www.example.com < /dev/null 2>/dev/null | grep "Protocol"
# 输出示例:
#     Protocol  : TLSv1.2     ← 还在 1.2
#     Protocol  : TLSv1.3     ← 已经升级

或者用浏览器开发者工具查看:F12 → Security → Connection。

原因 2:OCSP 验证阻塞了握手

这个是隐藏最深的性能杀手。

浏览器在验证服务器证书时,需要确认这张证书没有被 CA 提前吊销(比如私钥泄露了)。验证方式叫 OCSP(Online Certificate Status Protocol)。

默认情况下,浏览器会自己向证书颁发机构(CA)的 OCSP 服务器发起一次查询。这就有几个问题:

  • CA 的 OCSP 服务器大多在海外,单次查询 RTT 可能就 200~500ms
  • CA 服务器偶尔会卡顿,最坏情况下要超时几秒
  • 这次查询是串行在 TLS 握手之后的,用户感知就是"HTTPS 慢"

解决方案:OCSP Stapling。让服务器自己提前定期查询 OCSP 状态,把签名后的结果"装订(staple)"到 TLS 握手响应里。浏览器拿到证书的同时就拿到了吊销状态证明,省掉了那次查询。

原因 3:没启用会话恢复

同一个用户多次访问同一个网站时,理论上不需要每次都完整握手。TLS 提供了两种会话恢复机制:

  • Session ID(TLS 1.2 老方案):服务器在内存里维护一个会话状态表,客户端下次带上 ID 就能恢复
  • Session Ticket(推荐):服务器把会话状态加密后发给客户端,下次客户端带回来,服务器解密恢复——好处是服务器无状态,扩容方便

会话恢复后,TLS 1.2 握手从 2 RTT 降到 1 RTT,TLS 1.3 从 1 RTT 降到 0 RTT。

这个功能默认是不开的,需要手动配置。

原因 4:证书链过长或包含不必要的中间证书

服务器在握手时会把整个证书链发给客户端。如果链路里有 3~4 张中间证书,每张都是 1~2KB,加起来就要传几 KB 的数据。

更糟的是,如果服务器只发了叶子证书没发中间证书,客户端就要自己去取,又是几百毫秒。

检测方法:

echo | openssl s_client -showcerts -connect www.example.com:443 -servername www.example.com 2>/dev/null | grep -c "BEGIN CERTIFICATE"
# 输出数字 = 证书链长度,>= 3 就值得优化

原因 5:密码套件选择不当

典型问题:

  • 用 RSA 做密钥交换(应该用 ECDHE)——慢,且不支持前向安全
  • 启用了 CBC 模式的加密套件——比 GCM 慢,且有历史漏洞
  • 支持了过老的 SHA1、3DES——浏览器要花更多时间协商

四、可直接复制的 Nginx 优化配置

下面这份是经过实战检验的 Nginx TLS 配置模板。直接用在 server 块里即可:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name www.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.3 用默认的就好,TLS 1.2 推荐 ECDHE + GCM
    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;
    ssl_prefer_server_ciphers off;

    # ============ 会话恢复 ============
    ssl_session_cache shared:SSL:50m;     # 50MB 内存,约能存 20 万个会话
    ssl_session_timeout 1d;               # 会话保留 1 天
    ssl_session_tickets on;               # 启用 Session Ticket

    # ============ OCSP Stapling ============
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/nginx/ssl/fullchain.pem;
    resolver 1.1.1.1 8.8.8.8 valid=300s;  # 用于查询 OCSP 服务器的 DNS
    resolver_timeout 5s;

    # ============ HSTS(可选但推荐) ============
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    # ============ 其他业务配置 ============
    # ...
}

配置应用后,强烈建议用下面这些命令验证:

# 检查 TLS 1.3 是否生效
openssl s_client -connect www.example.com:443 -tls1_3 < /dev/null 2>/dev/null | grep "Protocol"

# 检查 OCSP Stapling 是否生效(看到 "OCSP Response Status: successful" 就对了)
openssl s_client -connect www.example.com:443 -status < /dev/null 2>/dev/null | grep -A 5 "OCSP response"

# 检查会话恢复是否生效(第二次连接看到 "Reused, TLSv1.3" 就对了)
openssl s_client -connect www.example.com:443 -reconnect < /dev/null 2>/dev/null | grep -E "(New|Reused)"

五、进阶优化:证书本身的优化

ECDSA 证书:又快又小的选择

大多数人申请的是 RSA 证书(2048 位)。但其实现在主流 CA 都支持 ECDSA 证书(P-256 曲线),它的优势很明显:

对比项 RSA 2048 ECDSA P-256
证书大小 ~1.2 KB ~400 B
握手时签名计算 快 2~4 倍
等效安全强度 112 位 128 位
浏览器支持 所有 所有现代浏览器

Let's Encrypt、ZeroSSL、阿里云、腾讯云的免费证书都支持 ECDSA,申请时选 ECC 即可。

稳妥起见,可以同时部署 RSA + ECDSA 双证书,Nginx 自动根据客户端能力选择:

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

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

精简证书链

检查你的 fullchain.pem 里有几张证书:

grep -c "BEGIN CERTIFICATE" /etc/nginx/ssl/fullchain.pem

理想值是 2(你的叶子证书 + 一张中间证书)。如果是 3 张甚至更多,看看能否换一个证书链更短的 CA。

注意:千万不要把根证书也塞进去——根证书早就预装在浏览器里了,发送它纯粹是浪费流量。

六、HTTP/3 与 0-RTT:把握手优化到极致

HTTP/3 (QUIC) 的优势

HTTP/3 基于 UDP 上的 QUIC 协议,把 TLS 1.3 握手和传输层握手合并了。也就是说:

  • HTTP/2 over TLS 1.3:建连要 2 RTT(TCP 1 RTT + TLS 1 RTT)
  • HTTP/3 over QUIC:建连只要 1 RTT
  • HTTP/3 + 0-RTT(已建立过会话):0 RTT,请求和握手同时发送

对于跨国访问场景(RTT 200ms+),这个差异极其明显。

Nginx 启用 HTTP/3

Nginx 1.25 起原生支持 HTTP/3,配置示例:

server {
    listen 443 ssl;
    listen 443 quic reuseport;        # 启用 QUIC(UDP 443)
    listen [::]:443 quic reuseport;

    http3 on;
    http3_hq on;

    # 通知浏览器升级到 HTTP/3
    add_header Alt-Svc 'h3=":443"; ma=86400';

    # 其他 TLS 配置同上
    ssl_protocols TLSv1.3;            # HTTP/3 要求 TLS 1.3
    # ...
}

别忘了在防火墙里放行 UDP 443 端口。

关于 0-RTT 的安全注意事项

0-RTT 虽然快,但有一个安全权衡:它对重放攻击没有完美的防护。如果攻击者抓到了 0-RTT 的数据包,可以重新发送一次。

所以原则上:

  • 幂等的 GET 请求可以走 0-RTT
  • 修改数据的 POST / PUT / DELETE 不要走 0-RTT

Nginx 配置:

ssl_early_data on;                # 启用 0-RTT
proxy_set_header Early-Data $ssl_early_data;   # 把标记传给后端,后端按需拒绝非幂等请求

七、实战案例:把 TLS 从 890ms 砍到 145ms

背景:某公司后台系统,用户反馈 HTTPS 打开慢。从北京电信发起 多节点网站测速,TLS 握手阶段平均 800~900ms。

第一步:定位症状

openssl s_client -connect admin.example.com:443 < /dev/null 2>&1 | grep -E "(Protocol|Cipher|OCSP)"
# 输出:
# Protocol  : TLSv1.2          ← 还在 1.2
# Cipher    : ECDHE-RSA-AES256-SHA384
# OCSP response: no response sent   ← 没开 Stapling

两个明确问题:

  1. 还在 TLS 1.2,多用 1 个 RTT
  2. 没开 OCSP Stapling,浏览器要自己去查 CA

第二步:抓握手过程定位 OCSP 阻塞

用 Wireshark 抓包,看到浏览器在 TLS 握手完成后,立即向 ocsp.digicert.com 发了一次 HTTP 查询,整个过程多用了 480ms。

第三步:修改 Nginx 配置

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

- ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+ ssl_protocols TLSv1.2 TLSv1.3;

+ ssl_session_cache shared:SSL:50m;
+ ssl_session_timeout 1d;
+ ssl_session_tickets on;

+ ssl_stapling on;
+ ssl_stapling_verify on;
+ ssl_trusted_certificate /etc/nginx/ssl/fullchain.pem;
+ resolver 1.1.1.1 8.8.8.8 valid=300s;

nginx -s reload 之后重测:

测试点优化前 TLS 耗时优化后 TLS 耗时下降幅度
北京电信890ms145ms-84%
广州移动820ms165ms-80%
上海联通760ms120ms-84%
新加坡(海外)1100ms220ms-80%

整个优化的工作量:修改 10 行配置,重载一次 Nginx。但每个用户每次新建连接都能省 600~900ms。

八、TLS 性能优化 8 条军规(速查表)

序号 优化项 预期收益 难度
1 启用 TLS 1.3 少 1 个 RTT(50~200ms)
2 启用 OCSP Stapling 免去 OCSP 查询(200~500ms)
3 启用 Session Resumption 重复访问省 1 个 RTT
4 使用 ECDHE 而非 RSA 密钥交换 支持前向安全 + 略快
5 使用 ECDSA 证书 证书更小、签名更快 ★★
6 精简证书链到 2 张 减少握手数据量 ★★
7 启用 HTTP/2 多路复用,避免连接开销
8 启用 HTTP/3 + 0-RTT 极限场景下 0 RTT 建连 ★★★

难度 ★ 的优化基本上就是改几行配置。难度 ★★ 的可能要换证书、调整证书链。难度 ★★★ 的要升级 Nginx、调整防火墙。

建议的优化顺序:先把所有 ★ 全部启用,再考虑 ★★,最后才上 ★★★——前面三条已经能拿到 80% 的收益。

九、总结

HTTPS 慢从来不是"加密的代价",而是"配置不到位的代价"。一次正常的 TLS 握手应该在 100~200ms 之间完成,如果你的网站测速里 TLS 段超过 500ms,几乎可以肯定是上面 5 个原因里的一个或几个。

排查路径很简单:

  1. 多节点网站测速 看 TLS 阶段耗时
  2. openssl s_client 抓握手细节,确认 TLS 版本、OCSP、密码套件
  3. 对照本文八条军规,逐条修复
  4. 修复后再次测速验证

这个流程走一遍,大部分情况下能把网站的 HTTPS 性能提升 30~60%——而你只动了几行配置。

← 返回测速指南列表 返回首页