为什么你的 HTTPS 握手比别人多花几十毫秒
很多人给网站配好 HTTPS、跑完 SSL 测试拿到一个 A 就以为万事大吉了。但只要你在 curl -w 里看一下握手耗时,或者用 openssl s_client 观察一次完整连接,就会发现一个被忽略的环节:浏览器在建连时,除了验证服务器证书,还得单独去证书颁发机构(CA)的 OCSP 服务器查询"这张证书有没有被吊销"。
这次查询是独立的、串行的。浏览器拿到证书后,必须再发起一个到 CA 的请求,等它回复,才能确认证书有效。这个往返(RTT)短则几十毫秒,长则几百毫秒,而且 CA 的 OCSP 服务器经常不稳定、被墙、甚至整个挂掉。一旦它超时,浏览器要么等超时(拖慢首次连接),要么直接放弃校验(有安全风险)。有人访问你的站"有时快有时慢",或者用手机流量首次打开特别迟钝,很可能就是卡在这一步。
OCSP Stapling(OCSP 封套)就是来解决这个问题的。它让你的服务器提前向 CA 查询好吊销状态,把 CA 签名的响应结果"装订"在 TLS 握手时一起发出去。浏览器直接拿现成的结果,不用再自己跑一趟 CA。既省了 RTT,也避免了隐私泄露(CA 不再知道谁在访问你的站),还提高了可用性(不依赖浏览器能否连上 CA)。
本文把 Nginx 上开启 OCSP Stapling 的完整流程走一遍:从配置、验证是否真的生效,到最常见的"配了却没生效"的排查。适合已经用 Certbot 或 acme.sh 配好 HTTPS、想进一步榨取握手性能的站长。
第一步:确认你的证书支持 OCSP,并搞清楚 responder 地址
不是所有证书都带 OCSP 信息。证书里会写明一个 OCSP - URI 字段,指向 CA 的查询接口。先把它读出来,这是后面所有验证的基础。
# 把证书文件换成你自己的路径
openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -ocsp_uri
# 期望输出类似:
# http://r3.o.lencr.org (Let's Encrypt)
# http://ocsp.digicert.com (DigiCert)
如果这条命令没有任何输出,说明证书里没有 OCSP 地址,可能是自签证书、某些免费证书,或者该 CA 已经改用 CRL 等其他机制。这种情况下 OCSP Stapling 无从配置,跳过即可。
另一种判断方式是手动模拟一次响应器查询,看 CA 是否真的会返回状态:
# 直接问 CA 这张证书的状态(issuer 证书用于校验响应签名)
openssl ocsp \
-issuer /etc/letsencrypt/live/example.com/chain.pem \
-cert /etc/letsencrypt/live/example.com/cert.pem \
-url http://r3.o.lencr.org \
-resp_text -noverify
返回里出现 Cert Status: good 就说明 CA 侧正常,可以往下配。如果这一步就报连不上,那么服务器上大概率也拿不到 stapling 数据,问题在网络出口或 CA 侧,跟 Nginx 无关。
第二步:Nginx 开启 stapling,关键在 resolver
真正的坑在这里。Nginx 要在运行时主动去连 OCSP 响应器,而 Nginx 的 ssl_stapling 依赖 resolver 指令做域名解析——它不会使用系统 /etc/resolv.conf。如果没配 resolver,或者 resolver 配的是内网 DNS 且连不上外网,Nginx 就会静默地拿不到 stapling 数据,日志里只留一行不起眼的 no OCSP response received。
# /etc/nginx/nginx.conf 的 http 块,或在 server 块内
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# 必须显式指定 DNS,Nginx 运行时不用系统解析器
# 用公共 DNS 避免内网 DNS 解析不到 CA
resolver 223.5.5.5 119.29.29.29 valid=300s;
resolver_timeout 5s;
# 开启 stapling
ssl_stapling on;
# 校验 CA 对 OCSP 响应的签名(强烈建议开,否则等于接受任意响应)
ssl_stapling_verify on;
# 用来校验 stapling 响应签名的中间 CA 证书
# fullchain.pem 通常已包含,如需单独指定:
# ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
}
几个容易忽略的点:
resolver 必须是能解析公共域名的 DNS。如果你的 VPS 用了内网的 127.0.0.53(systemd-resolved 的 stub)或者云厂商内网 DNS,而它恰好代理不到 CA 域名,stapling 就废了。直接写公共 DNS 最省心。valid=300s 让解析结果缓存 5 分钟,避免频繁查询。如果开启了 ssl_stapling_verify on,还需要 ssl_trusted_certificate 指向能串到 CA 根的证书链,否则校验会失败导致不发送 stapling。
改完 nginx -t 检查语法,然后 systemctl reload nginx。注意:stapling 数据是异步获取的,reload 之后不会立刻就有,Nginx 会去后台拉取并缓存。要等几秒到几十秒再验证。
第三步:验证 stapling 到底生效了没
这是最容易被忽视的一步。很多人以为配了就是生效了,实际上 no OCSP response received 的情况非常普遍。用 openssl s_client 带 -status 参数发起一次请求,看服务端有没有真的把 OCSP 响应封套发回来。
# 用 SNI 指定域名,向本机 443 发起握手并索取 stapling
openssl s_client -connect 127.0.0.1:443 \
-servername example.com \
-status < /dev/null 2>/dev/null | grep -A5 "OCSP response"
判断标准很简单:
生效的样子——输出里有一行 OCSP Response Status: successful (0x0),紧跟 OCSP Response Data、Cert Status: good。
没生效的样子——输出是 OCSP response: no response sent。看到这句就说明 Nginx 没拿到或没发送 stapling,回到第二步查 resolver 和 trusted certificate。
也可以用 curl 从外部机器验证,模拟真实浏览器的视角:
# curl 7.63+ 支持 --cert-status 检查 OCSP(注意:这是校验,不是读取 stapling)
curl -sS --cert-status -o /dev/null https://example.com
# 没报错说明证书吊销状态校验通过
更直观的方法是直接上 SSL Labs 的在线测试,它会在结果的 Protocol 部分明确标出 "OCSP stapling: Yes/No"。如果显示 No,基本就是 resolver 或验证链的问题。
第四步:排查"配了却没生效"的四类常见原因
stapling 的失败机制是"静默"的——它绝不影响站点正常访问,只会悄悄退回让浏览器自己去查 CA。所以你必须主动排查。
原因一:resolver 没配或配错。这是头号杀手。确认 nginx -T(大写 T,打印完整生效配置)里能看到 resolver 指令,并且指向的 DNS 能解析 CA 域名。测试方法:在服务器上 dig @223.5.5.5 r3.o.lencr.org,能返回 A 记录才算通。
原因二:reload 后没等够时间。Nginx 是异步拉取的,reload 完立刻测往往还是 "no response sent"。等 30 秒再测,或者 systemctl reload nginx 后连发几次请求触发它去拉取。
原因三:ssl_trusted_certificate 缺失或链不全。开了 ssl_stapling_verify on 却没给可信证书链,Nginx 无法验证 OCSP 响应签名,就会选择不发送。加上 ssl_trusted_certificate /path/chain.pem 即可。Let's Encrypt 的 fullchain.pem 里已经包含中间证书,也可以直接指向它。
原因四:CA 的 OCSP 响应器不稳定或被墙。看 Nginx 错误日志:grep -i ocsp /var/log/nginx/error.log。如果反复出现 upstream timed out 或 connect() failed,那是服务器出口到 CA 的网络问题。可以换 resolver、或者接受 stapling 不生效(网站依然正常,只是回退到浏览器自行查询)。
顺便说说 OCSP Must-Staple
既然 stapling 是个好东西,能不能强制浏览器"必须看到封套才认这张证书"?可以,这叫 OCSP Must-Staple,通过在签发证书时往证书的 TLS Feature 扩展里写入一个标记实现。启用后,如果服务器没发送 stapling,浏览器会直接拒绝连接——安全性拉满,但风险也拉满:一旦你的 stapling 拉取失败(比如 DNS 抖了一下),网站会直接打不开。
# 用 Certbot 申请带 Must-Staple 的证书
certbot certonly --must-staple -d example.com -d www.example.com
# 验证证书里是否带了 must-staple 标记
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -text \
| grep -i "TLS Feature"
# 期望看到:TLS Feature: status_request
对个人站长我的建议是:先别开 Must-Staple。它把"stapling 失效"从"性能小损失"变成"全站不可用",而你很难 7×24 保证 DNS 和 CA 侧永远正常。先把普通 stapling 跑稳、跑出监控,确认长期不掉,再考虑升级。除非你有一个高可用的递归 DNS 和完整的告警体系,否则收益不抵风险。
给 stapling 加个监控
因为失效是静默的,建议用一条最简单的定时检查写进监控脚本,发现掉 stapling 就告警。放在 crontab 里每小时跑一次即可:
#!/bin/bash
# /usr/local/bin/check_stapling.sh
DOMAIN="example.com"
if openssl s_client -connect 127.0.0.1:443 -servername "$DOMAIN" -status \
< /dev/null 2>/dev/null | grep -q "OCSP Response Status: successful"; then
exit 0
else
echo "OCSP stapling DOWN for $DOMAIN" | mail -s "stapling alert" you@example.com
fi
配合 cron 0 * * * * /usr/local/bin/check_stapling.sh,每小时自查一次。这样即使某天 resolver 抽风导致 stapling 掉了,你也能第一时间知道,而不是等用户抱怨"首屏偶尔慢"。
常见故障速查
openssl -status 输出 no response sent:九成是 resolver 没配或配了无法解析 CA 的内网 DNS,先用 dig @你的resolver r3.o.lencr.org 验证解析是否通。
配了 ssl_stapling_verify on 后反而完全没 stapling:缺 ssl_trusted_certificate,Nginx 无法验证响应签名而选择不发,补上证书链即可。
reload 完立刻测是 no,等一会变 successful:正常现象,stapling 是异步拉取并缓存的,等 30 秒再测。
error.log 里反复 OCSP upstream timed out:服务器到 CA 的网络不通或被墙,换 resolver 也无法解决,属于出口网络问题。
换了证书后 stapling 消失:证书文件路径变了但 Nginx 里的 ssl_trusted_certificate 还指向旧链,或新版证书的 OCSP 地址变了,重新验证。
FAQ
Q:OCSP Stapling 能提升多少性能?
A:省掉一次到 CA 的额外往返。对已建立的连接无影响,主要优化的是"冷启动首次连接"的握手时间,实测通常减少 30~100ms,对移动网络和高延迟用户体感最明显。
Q:会影响兼容性吗?
A:不会。stapling 是协商出来的扩展,不支持的老客户端会自动忽略、回退到自己查 OCSP,站点评分和访问都不受影响,属于纯白赚的优化。
Q:为什么我的站点 SSL 测试是 A+ 但 stapling 显示 No?
A:评分和 stapling 是两回事,A+ 看的是协议版本、加密套件、HSTS。stapling 未生效不会扣分,但确实损失了性能,值得单独修。
Q:Let's Encrypt 的证书要配吗?
A:要,而且很值。LE 的 OCSP 响应器(r3.o.lencr.org)在国内访问经常不稳定,开启 stapling 后由你的服务器代查一次并缓存,能显著改善国内用户的首次连接体验。