土法炼钢兴趣小组的算法知识备份

【网络工程】TLS 性能优化:会话恢复、OCSP Stapling 与硬件加速

文章导航

分类入口
network
标签入口
#tls#performance#session-ticket#ocsp-stapling#aes-ni#optimization

目录

每次 TLS 完整握手需要 1-2 RTT 的额外延迟,加上非对称加密运算的 CPU 开销——在高并发场景下,TLS 的性能成本不可忽视。Cloudflare 公开的数据显示,TLS 1.2 完整握手的服务端 CPU 开销约为纯 HTTP 的 3.5 倍,其中密钥交换(ECDHE)占了约 70%。

但”TLS 太慢”是一个过时的说法。现代硬件和协议优化已经把 TLS 的性能开销压缩到了几乎可以忽略的程度——前提是你做了正确的配置。Session Ticket 可以将握手从 2 RTT 降到 1 RTT(TLS 1.2)甚至 0 RTT(TLS 1.3),OCSP Stapling 可以消除证书验证时的额外 DNS + HTTP 请求,AES-NI 指令集让对称加密的吞吐量提升了 5-10 倍。

本文从三个维度系统性地分析 TLS 性能优化的工程实践。

一、TLS 握手的性能开销分解

在优化之前,先搞清楚 TLS 握手到底慢在哪里。TLS 握手的开销可以分为两部分:网络延迟和计算开销。

1.1 网络延迟

TLS 1.2 完整握手需要 2 RTT:

RTT 1: ClientHello → ServerHello + Certificate + ServerKeyExchange + ServerHelloDone
RTT 2: ClientKeyExchange + ChangeCipherSpec + Finished → ChangeCipherSpec + Finished

TLS 1.3 将完整握手压缩到 1 RTT:

RTT 1: ClientHello + KeyShare → ServerHello + EncryptedExtensions + Certificate + Finished
       (客户端收到后立即发送 Finished,开始传输应用数据)

加上 TCP 三次握手的 1 RTT,一个 HTTPS 连接在 TLS 1.2 下需要 3 RTT 才能开始传输数据,TLS 1.3 下需要 2 RTT。

对于跨洋连接(RTT ≈ 150 ms),TLS 1.2 的握手延迟就是 450 ms,仅握手就消耗了接近半秒。

1.2 计算开销

TLS 握手的计算开销主要来自三个部分:

操作 算法 典型耗时(现代 CPU) 说明
密钥交换 ECDHE P-256 ~0.5 ms 服务端和客户端各一次
签名验证 RSA-2048 ~0.1 ms 验证服务端证书签名
签名生成 RSA-2048 ~1.0 ms 服务端签名(最重的操作)
签名生成 ECDSA P-256 ~0.2 ms 比 RSA 签名快 5 倍
对称加密 AES-128-GCM ~1 GB/s (AES-NI) 握手完成后的数据传输

可以看出,服务端 RSA 签名是握手中最重的计算操作。这也是为什么 ECDSA 证书在高并发场景下越来越受欢迎——签名速度快 5 倍意味着同样的 CPU 可以处理 5 倍的新连接。

1.3 用 openssl speed 量化本机性能

在做任何优化之前,先用 openssl speed 了解你的服务器的加密性能基线:

# 测试 RSA 签名/验证性能
openssl speed rsa2048

# 测试 ECDSA 签名/验证性能
openssl speed ecdsap256

# 测试 ECDHE 密钥交换性能
openssl speed ecdh

# 测试 AES-GCM 对称加密性能
openssl speed -evp aes-128-gcm

# 对比有无 AES-NI 的性能差异
openssl speed -evp aes-128-gcm          # 使用 AES-NI(默认)
OPENSSL_ia32cap="~0x200000200000000" openssl speed -evp aes-128-gcm  # 禁用 AES-NI

一个典型的结果示例(Intel Xeon E5-2686 v4):

                   sign    verify    sign/s verify/s
rsa 2048 bits   0.000645s 0.000023s   1550   43478
                   sign    verify    sign/s verify/s
ecdsa P-256     0.000019s 0.000057s  52631   17544

RSA-2048 每秒签名 1550 次,ECDSA P-256 每秒签名 52631 次——ECDSA 签名快了 34 倍。但注意验证方向相反:RSA 验证比 ECDSA 验证快约 2.5 倍。对于服务端来说,签名次数远多于验证次数,所以 ECDSA 的优势更明显。

二、会话恢复:避免重复握手

会话恢复(Session Resumption)是最有效的 TLS 性能优化手段。它的核心思想是:既然完整握手的开销那么大,那对于已经建立过连接的客户端,能不能跳过大部分握手步骤?

2.1 TLS 1.2 的两种会话恢复机制

TLS 1.2 提供了两种会话恢复方式:Session ID 和 Session Ticket。

Session ID 是最早的方案。服务端在完整握手后为每个会话分配一个 32 字节的 ID,并把会话状态(主密钥、密码套件等)存储在服务端内存中。客户端下次连接时携带这个 ID,服务端查找到对应的状态就可以跳过完整握手。

Session ID 的工程问题:

Session Ticket(RFC 5077)解决了这些问题。服务端把会话状态加密后打包成一个 Ticket,发给客户端保存。下次连接时客户端携带 Ticket,服务端解密后即可恢复会话——不需要在服务端存储任何状态。

完整握手后:
Server → Client: NewSessionTicket(加密的会话状态)

恢复握手:
Client → Server: ClientHello + SessionTicket
Server → Client: ServerHello + ChangeCipherSpec + Finished
Client → Server: ChangeCipherSpec + Finished

恢复握手只需要 1 RTT(比完整握手少 1 RTT),而且跳过了最重的密钥交换和证书签名操作。

2.2 Nginx 配置 Session Ticket

http {
    # 启用 Session Ticket
    ssl_session_tickets on;

    # Session Ticket 密钥(48 字节,AES-256-CBC 加密)
    # 必须手动生成并在所有服务器间共享
    ssl_session_ticket_key /etc/nginx/ssl/ticket.key;

    # 可以指定多个密钥实现轮换
    # 第一个是当前密钥(用于加密新 Ticket),其余仅用于解密旧 Ticket
    ssl_session_ticket_key /etc/nginx/ssl/ticket-current.key;
    ssl_session_ticket_key /etc/nginx/ssl/ticket-previous.key;

    # Session 缓存(作为 Session ID 的回退)
    ssl_session_cache shared:SSL:50m;    # 50 MB 共享缓存,约 20 万个会话
    ssl_session_timeout 1h;              # 会话有效期 1 小时
}

生成 Session Ticket 密钥:

# 生成 48 字节的随机密钥
openssl rand 48 > /etc/nginx/ssl/ticket.key
chmod 600 /etc/nginx/ssl/ticket.key

# 密钥轮换脚本(建议每 12 小时轮换一次)
#!/bin/bash
TICKET_DIR="/etc/nginx/ssl"
mv "$TICKET_DIR/ticket-current.key" "$TICKET_DIR/ticket-previous.key"
openssl rand 48 > "$TICKET_DIR/ticket-current.key"
chmod 600 "$TICKET_DIR/ticket-current.key"
nginx -s reload

2.3 Session Ticket 的安全问题

Session Ticket 有一个严重的安全隐患:它破坏了前向保密性(Forward Secrecy)

即使握手使用了 ECDHE 实现前向保密,Session Ticket 密钥是长期存在的对称密钥。如果攻击者获取了 Ticket 密钥,就可以解密所有使用该密钥加密的 Session Ticket,进而恢复所有对应会话的主密钥,解密录制的流量。

这意味着:

2.4 TLS 1.3 的 PSK 恢复与 0-RTT

TLS 1.3 重新设计了会话恢复机制,使用 PSK(Pre-Shared Key)模式。完整握手后,服务端发送 NewSessionTicket 消息,其中包含一个 PSK 身份标识。客户端在后续连接中使用这个 PSK 进行恢复握手。

TLS 1.3 PSK 恢复的改进:

TLS 1.3 的 0-RTT 模式更进一步——允许客户端在握手完成之前就发送应用数据:

Client → Server: ClientHello + PSK + KeyShare + EarlyData(0-RTT 应用数据)
Server → Client: ServerHello + EncryptedExtensions + Finished

0-RTT 的延迟收益巨大(节省 1 RTT),但有重放攻击风险——详见TLS 1.3 工程实践

Nginx 中启用 TLS 1.3 0-RTT:

server {
    ssl_early_data on;

    # 在后端请求中添加标记,让应用层感知 0-RTT
    proxy_set_header Early-Data $ssl_early_data;

    # 应用层对 0-RTT 请求应拒绝非幂等操作
    # 例如:if (request.header("Early-Data") == "1") { reject POST/PUT/DELETE }
}

2.5 会话恢复的监控

验证会话恢复是否生效:

# 用 openssl s_client 测试 Session Ticket
# 第一次连接,保存 Session
openssl s_client -connect example.com:443 -sess_out /tmp/session.pem -brief
# 输出中应有: New, TLSv1.3, ...

# 第二次连接,使用保存的 Session
openssl s_client -connect example.com:443 -sess_in /tmp/session.pem -brief
# 输出中应有: Reused, TLSv1.3, ...

# 检查 Nginx 的 Session 命中率
# 通过 stub_status 或日志变量
# 在 log_format 中添加 $ssl_session_reused(值为 r 表示恢复)

用 curl 测量握手耗时:

# 全新连接(完整握手)
curl -w "TCP: %{time_connect}s\nTLS: %{time_appconnect}s\nTotal: %{time_total}s\n" \
     -o /dev/null -s https://example.com

# 对比有无 Session Ticket 的差异
# 完整握手: TLS 耗时约 60-80 ms(跨地域)
# 恢复握手: TLS 耗时约 30-40 ms(节省 1 RTT)

三、OCSP Stapling:消除证书验证延迟

当客户端收到服务端的证书时,需要验证证书是否被吊销。传统方式是客户端向 CA 的 OCSP(Online Certificate Status Protocol)服务器发起查询——这需要一次额外的 DNS 解析加 HTTP 请求,延迟通常在 100-300 ms。

3.1 OCSP 的工程问题

传统 OCSP 查询有三个严重的工程问题:

延迟:客户端在 TLS 握手过程中发起 OCSP 查询,这个查询是阻塞的。如果 OCSP 服务器响应慢(常见于 Let’s Encrypt 等免费 CA),用户感受到的连接建立时间会显著增加。

隐私:客户端向 CA 的 OCSP 服务器查询特定证书的状态,CA 因此能知道用户在访问哪些网站。

可用性:如果 OCSP 服务器不可达(宕机、网络问题、被防火墙阻断),客户端必须在”拒绝连接(硬失败)“和”跳过验证(软失败)“之间选择。大多数浏览器选择软失败——这意味着证书吊销在实际中形同虚设。

3.2 OCSP Stapling 的工作原理

OCSP Stapling(RFC 6066 / RFC 6961)将验证工作从客户端转移到服务端。服务端定期向 CA 获取自己证书的 OCSP 响应(一个由 CA 签名的”证书未被吊销”证明),并在 TLS 握手时随证书一起发给客户端。

传统 OCSP:
Client → CA OCSP Server: 这个证书有效吗?(额外 DNS + HTTP 请求)
CA OCSP Server → Client: 有效(延迟 100-300 ms)

OCSP Stapling:
Server → CA OCSP Server: 给我一份 OCSP 响应(服务端后台定期获取)
Server → Client: 证书 + OCSP 响应(在 TLS 握手中,零额外延迟)

好处显而易见:

3.3 Nginx 配置 OCSP Stapling

server {
    listen 443 ssl;

    ssl_certificate     /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;

    # 启用 OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;

    # 指定信任链证书(用于验证 OCSP 响应)
    # 通常是中间 CA 证书 + 根 CA 证书
    ssl_trusted_certificate /etc/nginx/ssl/chain.pem;

    # DNS 解析器(用于解析 OCSP 服务器地址)
    # 建议使用本地缓存 DNS 或公共 DNS
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;
}

验证 OCSP Stapling 是否生效:

# 方法 1:用 openssl s_client 检查
openssl s_client -connect example.com:443 -status </dev/null 2>&1 | \
    grep -A 20 "OCSP Response"
# 应看到 "OCSP Response Status: successful"

# 方法 2:检查 OCSP 响应详情
echo | openssl s_client -connect example.com:443 -status 2>&1 | \
    openssl ocsp -respin /dev/stdin -text -noverify
# 查看 Cert Status: good 和有效期

# 方法 3:用 curl 的 verbose 模式
curl -v https://example.com 2>&1 | grep -i "OCSP"

3.4 OCSP Stapling 的常见问题

问题 1:Nginx 首次启动时没有 OCSP 响应

Nginx 在第一次收到客户端请求后才会去获取 OCSP 响应。这意味着启动后的第一个请求不会包含 OCSP Stapling。解决方案:

# 预获取 OCSP 响应
OCSP_URL=$(openssl x509 -in cert.pem -noout -ocsp_uri)
openssl ocsp -issuer chain.pem -cert cert.pem \
    -url "$OCSP_URL" -respout /etc/nginx/ssl/ocsp.resp -noverify

# 在 Nginx 配置中指定预获取的响应
ssl_stapling_file /etc/nginx/ssl/ocsp.resp;

配合 cron 定期更新:

# /etc/cron.d/ocsp-update
0 */6 * * * root /usr/local/bin/update-ocsp.sh && nginx -s reload

问题 2:OCSP 服务器不可达

如果 Nginx 无法联系 OCSP 服务器,它会静默地不提供 Stapling。排查方法:

# 检查 Nginx 错误日志
grep -i "ocsp" /var/log/nginx/error.log

# 手动测试 OCSP 服务器连通性
OCSP_URL=$(openssl x509 -in cert.pem -noout -ocsp_uri)
curl -v "$OCSP_URL"

# 检查 DNS 解析
dig $(echo "$OCSP_URL" | sed 's|http://||;s|/.*||')

问题 3:多级证书链的 OCSP Stapling

如果你的证书链有多个中间 CA,需要确保 ssl_trusted_certificate 包含完整的信任链:

# 构建完整信任链
cat intermediate.pem root.pem > chain.pem

3.5 OCSP Must-Staple

OCSP Must-Staple(RFC 7633)更进一步:在证书中嵌入一个扩展,告诉客户端”这个证书必须带有 OCSP Stapling 响应,否则拒绝连接”。

这解决了 OCSP Stapling 的一个局限:普通 OCSP Stapling 是可选的——如果服务端不提供 Stapling,客户端会回退到自行查询或直接跳过。攻击者可以利用这一点,在中间人攻击中剥离 Stapling 响应,迫使客户端软失败从而跳过吊销检查。

Must-Staple 的使用注意事项:

生成 Must-Staple 证书的 CSR:

# 在 CSR 中添加 Must-Staple 扩展
openssl req -new -key server.key -out server.csr \
    -addext "tlsfeature = status_request"

# 或者使用 certbot
certbot certonly --must-staple -d example.com

四、AES-NI 硬件加速

TLS 握手完成后,所有的应用数据都使用对称加密(通常是 AES-GCM)保护。现代 CPU 几乎都支持 AES-NI(Advanced Encryption Standard New Instructions)指令集,可以在硬件层面加速 AES 运算。

4.1 检查 AES-NI 支持

# 检查 CPU 是否支持 AES-NI
grep -o aes /proc/cpuinfo | head -1
# 输出 "aes" 表示支持

# 更详细的检查
cat /proc/cpuinfo | grep flags | head -1 | tr ' ' '\n' | grep -E "aes|avx|sse"

# 检查 OpenSSL 是否使用了 AES-NI
openssl speed -evp aes-128-gcm 2>&1 | head -5
# 如果使用了 AES-NI,吞吐量通常 > 3 GB/s

4.2 AES-NI 的性能影响

openssl speed 对比有无 AES-NI 的性能差异:

# 启用 AES-NI(默认)
openssl speed -evp aes-128-gcm
# 典型结果: ~5 GB/s

# 禁用 AES-NI
OPENSSL_ia32cap="~0x200000200000000" openssl speed -evp aes-128-gcm
# 典型结果: ~0.5 GB/s

性能对比(Intel Xeon Platinum 8275CL):

算法 无 AES-NI 有 AES-NI 加速比
AES-128-GCM 0.5 GB/s 5.0 GB/s 10x
AES-256-GCM 0.4 GB/s 4.2 GB/s 10.5x
ChaCha20-Poly1305 1.8 GB/s 1.8 GB/s 1x(不使用 AES-NI)

注意 ChaCha20-Poly1305 不使用 AES-NI——它使用通用的 SIMD 指令(SSE/AVX)。这意味着在不支持 AES-NI 的平台(如早期 ARM 处理器)上,ChaCha20 可能比 AES 更快。这就是为什么 Google 在 Android 上优先使用 ChaCha20-Poly1305。

4.3 密码套件选择与硬件加速

密码套件的选择应该考虑硬件加速能力:

# 推荐的密码套件配置(优先 AES-GCM,利用 AES-NI)
ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers on;

但如果你的客户端大量使用移动设备(特别是旧版 Android),考虑优先提供 ChaCha20:

# 移动优先的配置:让客户端选择(移动设备会优先选 ChaCha20)
ssl_prefer_server_ciphers off;

4.4 Intel QAT 与 SSL 加速卡

对于超高并发场景(每秒数万新连接),可以考虑使用硬件 SSL 加速卡,如 Intel QuickAssist Technology(QAT)。

QAT 的工作原理是将非对称加密运算(RSA/ECDSA 签名、ECDHE 密钥交换)卸载到专用硬件上,释放 CPU 资源给应用逻辑。

# 检查 QAT 设备
lspci | grep -i quickassist

# QAT 引擎配置(OpenSSL 引擎模式)
# 需要安装 QAT 驱动和 OpenSSL 引擎
openssl engine -t qatengine
# (qatengine) Reference implementation of QAT crypto engine
# [ available ]

Nginx 使用 QAT 引擎:

# 需要编译 Nginx with --with-openssl 并配置 QAT 引擎
ssl_engine qatengine;

# 或在 OpenSSL 配置文件中全局启用
# /etc/ssl/openssl.cnf
[engine_section]
qatengine = qatengine_section

[qatengine_section]
engine_id = qatengine
default_algorithms = RSA,EC,DH

QAT 的工程考量:

五、证书算法对性能的影响

证书使用的签名算法对 TLS 握手性能有直接影响。

5.1 RSA vs ECDSA 性能对比

# RSA-2048 签名性能
openssl speed rsa2048
# sign: ~1550/s   verify: ~43000/s

# RSA-4096 签名性能
openssl speed rsa4096
# sign: ~280/s    verify: ~16000/s

# ECDSA P-256 签名性能
openssl speed ecdsap256
# sign: ~52000/s  verify: ~17500/s

# ECDSA P-384 签名性能
openssl speed ecdsap384
# sign: ~7200/s   verify: ~3400/s
算法 签名/秒 验证/秒 证书大小 安全强度
RSA-2048 ~1,550 ~43,000 ~1,200 B 112 bit
RSA-4096 ~280 ~16,000 ~2,400 B 128 bit
ECDSA P-256 ~52,000 ~17,500 ~300 B 128 bit
ECDSA P-384 ~7,200 ~3,400 ~400 B 192 bit

ECDSA P-256 在服务端签名速度上比 RSA-2048 快 33 倍,且证书体积只有 1/4。对于高并发场景,ECDSA 是明确的赢家。

5.2 双证书部署

为了兼容不支持 ECDSA 的老客户端,Nginx 支持同时配置 RSA 和 ECDSA 两套证书:

server {
    listen 443 ssl;

    # RSA 证书(兼容老客户端)
    ssl_certificate     /etc/nginx/ssl/rsa-cert.pem;
    ssl_certificate_key /etc/nginx/ssl/rsa-key.pem;

    # ECDSA 证书(现代客户端优先使用)
    ssl_certificate     /etc/nginx/ssl/ecdsa-cert.pem;
    ssl_certificate_key /etc/nginx/ssl/ecdsa-key.pem;
}

Nginx 会根据客户端的 ClientHello 中支持的签名算法,自动选择合适的证书。现代客户端会优先使用 ECDSA。

5.3 证书链长度优化

证书链中每增加一个中间证书,TLS 握手的数据传输量就增加约 1-2 KB。在移动网络(高延迟、高丢包)上,这个增量对握手时间的影响不可忽略。

优化建议:

# 检查证书链长度
echo | openssl s_client -connect example.com:443 2>/dev/null | \
    grep -E "Certificate chain|s:|i:"

# 理想的证书链:
# 0 s:CN = example.com        (你的证书)
# 1 i:CN = Let's Encrypt R3   (中间 CA)
# 2 i:CN = ISRG Root X1       (根 CA)

# 不要发送根证书!客户端已经内置了根证书
# 只发送你的证书 + 必要的中间 CA 证书
cat server.crt intermediate.crt > fullchain.pem
# 不要加入 root.crt

六、TLS 性能基准测试方法论

优化后如何验证效果?需要一套系统化的基准测试方法。

6.1 使用 h2load 测试 TLS 握手吞吐量

# 安装 h2load(来自 nghttp2)
apt install nghttp2-client

# 测试新连接吞吐量(每个请求都建立新的 TLS 连接)
h2load -n 10000 -c 100 -t 4 --h1 https://example.com/
# -n: 总请求数
# -c: 并发连接数
# -t: 线程数
# --h1: 使用 HTTP/1.1(避免 HTTP/2 多路复用干扰测试)

# 关键指标:
# - req/s: 每秒请求数(包含 TLS 握手)
# - time for connect: 平均 TCP + TLS 建连时间
# - time for 1st byte: TTFB

6.2 使用 openssl s_time 测试纯 TLS 握手性能

# 测试 30 秒内能完成多少次完整 TLS 握手
openssl s_time -connect example.com:443 -new -time 30
# 输出: N connections in M seconds => X connections/s

# 测试会话恢复的 TLS 握手
openssl s_time -connect example.com:443 -reuse -time 30
# 恢复握手的速率通常是完整握手的 3-5 倍

6.3 使用 wrk 测试端到端性能

# 安装 wrk
apt install wrk

# 基线测试:HTTPS 端到端性能
wrk -t 4 -c 100 -d 30s https://example.com/api/health
# -t: 线程数
# -c: 并发连接数
# -d: 测试持续时间

# wrk 默认复用连接(Keep-Alive),所以测的是对称加密的吞吐量
# 如果要测试握手性能,需要强制每次建立新连接

6.4 关键性能指标

建立 TLS 性能基线时应关注的指标:

指标 说明 健康阈值
新连接速率 每秒完整 TLS 握手数 > 5000/s(单核 ECDSA)
会话恢复率 恢复握手占总握手的比例 > 80%
握手延迟 P50 完整握手的中位延迟 < 20 ms(同地域)
握手延迟 P99 完整握手的 99 分位延迟 < 50 ms(同地域)
OCSP Stapling 命中率 携带 OCSP 响应的握手比例 > 99%
对称加密吞吐量 AES-GCM 加密的数据吞吐 > 3 GB/s(有 AES-NI)

6.5 Prometheus 监控 TLS 性能

用 Nginx 的 ssl_session_reused 变量和 Prometheus exporter 采集 TLS 指标:

# 在 log_format 中记录 TLS 相关信息
log_format tls_metrics '$remote_addr $ssl_protocol $ssl_cipher '
                       '$ssl_session_reused $ssl_early_data '
                       '$request_time $upstream_response_time';

# $ssl_session_reused: "r" 表示恢复,"." 表示完整握手
# $ssl_early_data: "1" 表示 0-RTT 数据

配合 mtail 或 grok_exporter 将日志转为 Prometheus 指标:

# mtail 规则示例
counter tls_handshakes_total by type
/^.* (?P<reused>[r.]) / {
    $reused == "r" {
        tls_handshakes_total["resumed"]++
    }
    $reused == "." {
        tls_handshakes_total["full"]++
    }
}

七、TLS 1.2 vs TLS 1.3 性能对比

TLS 1.3 在性能上的改进不仅仅是”少了 1 RTT”。

7.1 握手延迟对比

TLS 1.2 完整握手:   TCP(1 RTT) + TLS(2 RTT) = 3 RTT
TLS 1.2 恢复握手:   TCP(1 RTT) + TLS(1 RTT) = 2 RTT

TLS 1.3 完整握手:   TCP(1 RTT) + TLS(1 RTT) = 2 RTT
TLS 1.3 恢复握手:   TCP(1 RTT) + TLS(1 RTT) = 2 RTT(但可以发 0-RTT 数据)
TLS 1.3 0-RTT:     TCP(1 RTT) + TLS(0 RTT) = 1 RTT(应用数据随 ClientHello 发送)

在 RTT = 50 ms 的典型场景下:

场景 TLS 1.2 TLS 1.3 节省
完整握手 150 ms 100 ms 50 ms (33%)
恢复握手 100 ms 100 ms* 0 ms
0-RTT 不支持 50 ms 50 ms

*TLS 1.3 恢复握手的 RTT 数与 TLS 1.2 恢复相同,但 TLS 1.3 可以在第一个 RTT 就发送应用数据(0-RTT),这是 TLS 1.2 做不到的。

在高延迟场景(如跨洋连接、卫星网络)中,这些差异被成倍放大。当 RTT 从 50 ms 增加到 200 ms 时,TLS 1.3 完整握手(200 ms)比 TLS 1.2 完整握手(400 ms)节省了整整 200 ms。

7.2 计算开销对比

TLS 1.3 移除了 RSA 密钥交换,只保留了 ECDHE。这意味着:

7.3 升级建议

# 推荐配置:优先 TLS 1.3,兼容 TLS 1.2
ssl_protocols TLSv1.2 TLSv1.3;

# TLS 1.3 密码套件(在 OpenSSL 1.1.1+ 中自动配置)
# 不需要手动指定 TLS 1.3 密码套件

# TLS 1.2 密码套件(仅保留安全的 ECDHE + AEAD 组合)
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
ssl_prefer_server_ciphers off;  # 让客户端选择最适合的密码套件

八、完整的 Nginx TLS 性能优化配置

把前面所有优化整合到一个配置模板中:

# /etc/nginx/conf.d/tls-optimized.conf

# 全局 TLS 配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
ssl_prefer_server_ciphers off;

# ECDHE 参数
ssl_ecdh_curve X25519:P-256:P-384;

# Session Ticket(会话恢复)
ssl_session_tickets on;
ssl_session_ticket_key /etc/nginx/ssl/ticket-current.key;
ssl_session_ticket_key /etc/nginx/ssl/ticket-previous.key;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1h;

# 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;

# TLS 1.3 0-RTT(仅在应用层支持幂等检查时启用)
# ssl_early_data on;

# 缓冲区优化
ssl_buffer_size 4k;  # 小页面用 4k(减少 TTFB),大文件传输可设为 16k

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

    # 双证书(ECDSA 优先,RSA 兼容)
    ssl_certificate     /etc/nginx/ssl/ecdsa-cert.pem;
    ssl_certificate_key /etc/nginx/ssl/ecdsa-key.pem;
    ssl_certificate     /etc/nginx/ssl/rsa-cert.pem;
    ssl_certificate_key /etc/nginx/ssl/rsa-key.pem;

    # HSTS(强制 HTTPS)
    add_header Strict-Transport-Security "max-age=63072000" always;
}

8.1 ssl_buffer_size 的工程权衡

ssl_buffer_size 是一个经常被忽略但影响显著的参数。它控制了 TLS 记录层每次加密的数据块大小。

# API 服务(小响应体,追求低 TTFB)
ssl_buffer_size 4k;

# 文件下载服务(大文件,追求高吞吐)
ssl_buffer_size 16k;

Cloudflare 的实践是使用动态的 TLS 记录大小:先发送小记录(1369 字节,刚好不分片),在连接”预热”后逐渐增大到 16 KB。Nginx 不原生支持这个行为,但 OpenResty 的 ssl_dyn_rec_size_lo/ssl_dyn_rec_size_hi 补丁可以实现类似效果。

九、性能优化的优先级矩阵

不是所有优化都需要做。根据你的场景选择优先级:

优化措施 延迟收益 吞吐收益 实施难度 推荐优先级
升级 TLS 1.3 ★★★ ★★ P0
启用 Session Ticket ★★★ ★★★ P0
启用 OCSP Stapling ★★ P0
使用 ECDSA 证书 ★★★ ★★ P1
双证书部署 ★★ ★★ P1
优化证书链 P1
调整 ssl_buffer_size ★★ P2
启用 0-RTT ★★★ ★★★ P2
部署 QAT 加速卡 ★★★ ★★★ P3
Must-Staple ★★★ P3

P0 是每个 HTTPS 服务都应该做的,只需要几行配置。P1 在高并发场景下收益明显。P2 和 P3 只在特定场景下值得投入。

十、总结

TLS 性能优化的核心思路只有三个:减少握手次数(会话恢复)、减少握手延迟(TLS 1.3、OCSP Stapling)、降低计算开销(ECDSA、AES-NI、QAT)。

几个值得记住的数字:

最后一个建议:先测量,再优化。在没有基准数据的情况下做优化,你不知道是否真的改善了性能,还是只是”感觉”快了。openssl speedh2loadopenssl s_time——这三个工具足以建立可靠的 TLS 性能基线。


上一篇:mTLS 工程实践:服务间双向认证

下一篇:TLS 攻防:降级攻击、中间人与协议漏洞

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-22 · network

网络工程索引

汇总本站网络工程系列文章,覆盖分层模型、以太网、IP、TCP、DNS、TLS、HTTP/2/3、CDN、BGP 与故障诊断。

2025-07-23 · network

【网络工程】零拷贝网络:sendfile、splice 与 MSG_ZEROCOPY

数据从磁盘到网卡的传统路径涉及 4 次拷贝和多次上下文切换。本文系统剖析 sendfile、splice、vmsplice、MSG_ZEROCOPY 四种零拷贝技术的内核实现、适用场景与性能差异,并以 Kafka 和 Nginx 为案例分析零拷贝在生产系统中的工程实践。


By .