用户反馈我们的网站打开慢了 300-400ms,主要慢在 TLS,在 HTTP 之上加一层 TLS 加密,防止中间人窃听和篡改。">HTTPS 握手。Chrome DevTools 看 SSL 阶段时间从 80ms 涨到 380ms,但服务器 CPU 没飙、网络也没拥堵。最后定位是 OCSP 在线校验,客户端阻塞等 CA 服务器响应。本文讲清楚 OCSP 是什么、为什么慢、用 OCSP stapling 修复的全过程。
HTTPS 握手到底慢在哪
用 openssl s_client 模拟一次握手,看每一步耗时:
# 看完整握手时序
openssl s_client -connect blog.biekanle.com:443 -servername blog.biekanle.com \
-showcerts < /dev/null 2>&1 | head -50
# 用 curl 看分阶段耗时
curl -w "@curl-format.txt" -o /dev/null -s "https://blog.biekanle.com"
# curl-format.txt 内容:
# time_namelookup: %{time_namelookup}\n
# time_connect: %{time_connect}\n
# time_appconnect: %{time_appconnect}\n ← TLS 握手完成时刻
# time_pretransfer: %{time_pretransfer}\n
# time_redirect: %{time_redirect}\n
# time_starttransfer: %{time_starttransfer}\n
# ----:
# time_total: %{time_total}\n
# 真实输出
# time_namelookup: 0.012345 # DNS 解析,12ms
# time_connect: 0.045678 # TCP 三次握手,33ms
# time_appconnect: 0.428901 # TLS 握手完成,这一步 383ms!
# time_pretransfer: 0.429012
# time_starttransfer: 0.512345 # TTFB
# time_total: 0.523456
TLS 握手 383ms,占整个请求时间的 73%。问题肯定出在 SSL 那段。
OCSP 是什么
HTTPS 证书可能被吊销(私钥泄漏 / CA 主动吊销 / Heartbleed 这种事故)。浏览器拿到服务器证书后,会向 CA 发起 OCSP(Online Certificate Status Protocol)查询,问"这个证书还有效吗?"
整个流程:
问题在第三步:浏览器要等 OCSP 响应才能继续握手。如果用户在中国,而 CA 的 OCSP 服务器在欧洲,RTT 200ms+。再加上 CA 服务器自己的处理时间,整个 OCSP 查询轻松 300+ ms。
更糟的情况:OCSP 服务器抽风,响应 2 秒,所有用户的 TLS 握手都慢 2 秒。我们 2024 年 9 月遇到过 Let's Encrypt OCSP 抽风,全站握手时间从 80ms 飙到 1.8s,持续 40 分钟。
OCSP Stapling 怎么救场
OCSP Stapling(中文叫"OCSP 装订"):服务器自己定期去 CA 查 OCSP 响应,把响应缓存起来,在 TLS 握手时直接"附带"给浏览器。浏览器不再需要单独去问 CA,握手时间立刻降回原值。
Nginx 启用 OCSP stapling 只要几行配置:
server {
listen 443 ssl http2;
server_name blog.biekanle.com;
ssl_certificate /etc/letsencrypt/live/blog.biekanle.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/blog.biekanle.com/privkey.pem;
# 关键 3 行
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/blog.biekanle.com/chain.pem;
# DNS 解析 OCSP 服务器域名(用 8.8.8.8 或者本地 DNS)
resolver 8.8.8.8 1.1.1.1 valid=60s;
resolver_timeout 5s;
# 其他 SSL 配置...
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
}
验证 OCSP stapling 是否生效:
openssl s_client -connect blog.biekanle.com:443 -servername blog.biekanle.com \
-status < /dev/null 2>&1 | grep -A 17 'OCSP response'
# 正常输出:
# OCSP response:
# ======================================
# OCSP Response Data:
# OCSP Response Status: successful (0x0)
# Response Type: Basic OCSP Response
# Version: 1 (0x0)
# Responder Id: ...
# Produced At: Nov 25 09:00:00 2024 GMT
# Responses:
# Certificate ID:
# ...
# Cert Status: good ← 这个状态是关键
# This Update: Nov 25 09:00:00 2024 GMT
# Next Update: Dec 1 09:00:00 2024 GMT
# 没生效会显示:
# OCSP response: no response sent
第一次踩坑:resolver 没配,stapling 不生效
开了 ssl_stapling on 但 openssl s_client -status 还是显示 no response。Nginx 错误日志:
2024/09/15 10:23:11 [warn] 12345#12345: no resolver defined to resolve r3.o.lencr.org while requesting certificate status
OCSP 需要 Nginx 主动去 CA 服务器查询,这要解析域名。resolver 这一行必须配。我们之前的配置漏了,导致 stapling 配了等于没配。
另一个坑:ssl_trusted_certificate 要指向颁发该证书的 CA 的证书链(不是你自己证书的 fullchain.pem)。Let's Encrypt 提供的 chain.pem 就是这个文件。
第二次踩坑:Nginx 第一个请求 stapling 还是空
Nginx 不会在启动时立即去拉 OCSP 响应,而是在第一个用户请求来了之后才异步去拉。所以你的服务刚启动,第一批用户的握手依然慢。
修法:让 Nginx 启动后主动预热一下:
# 在 reload Nginx 后,本机自己请求一次,触发 OCSP 缓存填充
sudo systemctl reload nginx
sleep 2
curl -s --resolve blog.biekanle.com:443:127.0.0.1 https://blog.biekanle.com -o /dev/null
sleep 5
# 再验证一次
openssl s_client -connect blog.biekanle.com:443 -status < /dev/null 2>&1 | grep 'Cert Status'
更彻底的方案:用 ssl_stapling_file 提前准备 OCSP 响应文件,Nginx 启动就能用:
# 用 openssl ocsp 拉一次响应,存成文件
CERT=/etc/letsencrypt/live/blog.biekanle.com/cert.pem
CHAIN=/etc/letsencrypt/live/blog.biekanle.com/chain.pem
OCSP_URL=$(openssl x509 -in $CERT -text -noout | grep -i 'OCSP - URI' | awk '{print $NF}')
openssl ocsp -no_nonce \
-respout /etc/nginx/ocsp/blog.biekanle.com.der \
-issuer $CHAIN \
-cert $CERT \
-url $OCSP_URL \
-header "Host=$(echo $OCSP_URL | awk -F/ '{print $3}')"
# Nginx 配置改为
# ssl_stapling_file /etc/nginx/ocsp/blog.biekanle.com.der;
这种方式 Nginx 启动立刻能用,响应来自文件而不是 Nginx 内部缓存。代价是要写一个 cron 每 24 小时刷新这个文件:
cat > /etc/cron.d/refresh-ocsp <<'EOF'
0 */6 * * * root /usr/local/bin/refresh-ocsp.sh > /var/log/refresh-ocsp.log 2>&1
EOF
cat > /usr/local/bin/refresh-ocsp.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
for domain in blog.biekanle.com api.biekanle.com; do
CERT=/etc/letsencrypt/live/$domain/cert.pem
CHAIN=/etc/letsencrypt/live/$domain/chain.pem
OCSP_URL=$(openssl x509 -in "$CERT" -text -noout | grep -i 'OCSP - URI' | awk '{print $NF}')
OUT=/etc/nginx/ocsp/$domain.der
NEW=$OUT.new
openssl ocsp -no_nonce \
-respout "$NEW" \
-issuer "$CHAIN" \
-cert "$CERT" \
-url "$OCSP_URL" \
-header "Host=$(echo $OCSP_URL | awk -F/ '{print $3}')" \
2>&1
# 验证新文件有效再替换
if openssl ocsp -respin "$NEW" -text -CAfile "$CHAIN" -noverify > /dev/null 2>&1; then
mv "$NEW" "$OUT"
echo "$(date) refreshed $domain ocsp"
else
echo "$(date) invalid response for $domain, keeping old"
rm -f "$NEW"
fi
done
# 不需要 reload Nginx,它会自动重新读 stapling 文件
EOF
chmod +x /usr/local/bin/refresh-ocsp.sh
TLS 1.3 + 0-RTT 是不是更香
TLS 1.3 把握手从 2-RTT 缩到 1-RTT,0-RTT(resume 时)甚至 0 个往返就发数据。和 OCSP stapling 是独立优化,两者叠加效果最好。
# 启用 TLS 1.3 + Session Resume
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:50m; # 50MB = 约 20 万 session
ssl_session_timeout 1d; # session 缓存 24h
ssl_session_tickets on; # 启用 session tickets(老客户端用)
# 0-RTT(谨慎用,有 replay 风险)
ssl_early_data on; # 只对幂等请求开,GET 类没事,POST 类要在应用层防 replay
0-RTT 有 replay 攻击风险:第一次发送的请求可能被攻击者拦下来反复重放。所以只能用于幂等 GET。如果应用混合了状态修改的 POST,要在应用层做幂等保护(请求 ID + Redis 去重)。
实测对比数据
同一台机器(腾讯云华东),Chrome 实测连续访问 10 次取平均:
配置 TLS 握手时间 总响应时间
裸 HTTPS,无优化 382ms 512ms
+ OCSP stapling 78ms 208ms
+ TLS 1.3 52ms 181ms
+ Session Resume(2nd req) 8ms 38ms
+ HTTP/2 复用 0ms (复用) 12ms
从 512ms 降到 12ms,对用户感知是天差地别。
SSL 性能优化清单
- 开 OCSP stapling:握手时间立刻降 70%
- 用 TLS 1.3:握手从 2-RTT 到 1-RTT
- 开 Session Cache + Session Tickets:同用户二次握手免 key 交换
- HTTP/2 keepalive:同用户多个请求复用一条连接
- 选轻量 cipher suite:ECDSA 比 RSA 快,256 位足够安全(别上 4096 bit RSA)
- CDN 边缘卸载 SSL:如果用 CDN,SSL 在边缘节点解决,源站只走 HTTP
把这 6 条做齐,你的网站 TLS 性能不会成为瓶颈。
验证一切都生效的 6 行命令
DOMAIN=blog.biekanle.com
# 1. OCSP stapling 验证
openssl s_client -connect $DOMAIN:443 -status < /dev/null 2>&1 | grep 'Cert Status'
# 2. TLS 1.3 协议
openssl s_client -connect $DOMAIN:443 -tls1_3 < /dev/null 2>&1 | grep 'Protocol'
# 3. Session Reuse
openssl s_client -connect $DOMAIN:443 -reconnect < /dev/null 2>&1 | grep 'Reused'
# 4. HTTP/2
curl -I --http2 https://$DOMAIN/ 2>&1 | head -1
# 5. 完整 SSL 评级(在线工具,但要等几分钟)
echo "看 https://www.ssllabs.com/ssltest/analyze.html?d=$DOMAIN"
# 6. 简单测握手延迟(10 次取平均)
for i in {1..10}; do
curl -w "%{time_appconnect}\n" -o /dev/null -s https://$DOMAIN/
done | awk '{sum+=$1} END {print "平均握手:", sum/NR*1000, "ms"}'
这次优化后,我们在 SSL Labs 评测从 B 升到 A+,Lighthouse 评分上首屏速度 +12 分。OCSP stapling 是 SSL 性能优化里 ROI 最高的一件事 —— 配置 5 分钟,效果立竿见影。
—— 别看了 · 2026