HTTP/3依赖QUIC协议,握手过程与传统的TCP加TLS方式完全不同。当业务链路中存在CDN或反向代理回源到自建Nginx的场景时,握手失败的排查难度会明显上升,因为中间链路可能已经把QUIC转换成了HTTP/1.1或HTTP/2回源。很多人在配置完HTTP/3后发现客户端访问异常,第一反应是检查边缘节点,却忽略了Nginx这一侧的日志其实记录了大量握手细节。这篇文章围绕回源场景,系统讲解如何通过Nginx日志定位HTTP/3与TLS1.3握手问题。

一、先搞清楚回源链路里到底发生了什么
在典型的HTTP/3架构中,客户端通过QUIC与CDN边缘节点通信,边缘节点完成TLS1.3握手后,再将请求回源到源站的Nginx。这里有一个关键点:绝大多数CDN目前并不会以QUIC协议回源,而是降级为HTTP/1.1或HTTP/2走TCP加TLS回源。也就是说,源站Nginx日志里看到的协议版本,并不等于客户端实际使用的协议。
理解这一点非常重要,因为它决定了排查方向。如果Nginx的access日志里$server_protocol显示的是HTTP/2.0,不代表HTTP/3没生效;反过来,如果握手直接失败,连接根本建立不起来,access日志里也不会有任何记录,这时只能去error日志或者更底层抓包找线索。回源握手失败的表现通常是边缘节点收到connection reset、handshake timeout或者502/504错误。
另一个常见误解是把TLS1.3握手失败归咎于客户端。实际上回源场景中,握手的客户端是CDN节点,握手失败往往是证书配置、协议版本不匹配或者加密套件协商问题,需要在Nginx侧逐一验证。
二、配置Nginx日志输出握手相关信息
默认的Nginx日志格式对排查握手问题帮助有限,需要手动扩展log_format,把SSL协议版本、加密套件、会话复用状态等字段加进去。下面是一个可直接使用的配置示例:
http {
log_format ssl_debug '$remote_addr - [$time_local] '
'"$request" $status $body_bytes_sent '
'protocol=$server_protocol '
'ssl_protocol=$ssl_protocol '
'ssl_cipher=$ssl_cipher '
'ssl_session_reused=$ssl_session_reused '
'ssl_client_sni=$ssl_server_name';
access_log /var/log/nginx/access.log ssl_debug;
server {
listen 443 ssl http2;
# 如果Nginx版本支持,可同时监听QUIC端口
listen 443 quic reuseport;
server_name www.ipipp.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
}
}配置中几个字段的含义需要重点关注:$ssl_protocol记录本次连接实际协商出的协议版本,如果回源握手正常,这里应该出现TLSv1.3;$ssl_session_reused为r时表示复用了会话,握手耗时会更短;$ssl_server_name即SNI,如果回源请求的SNI与证书域名不匹配,握手虽然可能成功,但部分严格的CDN会直接判定失败。
除了access日志,error日志的级别也很关键。默认的error级别日志不会记录握手失败的细节,建议排查期间临时调整为info级别,这样能看到类似SSL_do_handshake failed的详细报错,包括错误码和原因。
error_log /var/log/nginx/error.log info;
常见的握手失败错误包括:SSL_do_handshake() failed (SSL: error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure),这通常是客户端与服务器没有共同的加密套件或者协议版本;no shared cipher则明确指向套件协商失败;而certificate verify failed说明对端校验证书链时出了问题,重点检查fullchain是否包含了中间证书。
三、用命令行工具验证源站TLS1.3支持情况
日志只能告诉我们失败了,具体是哪一环失败还需要主动验证。openssl命令是验证TLS握手最直接的武器。在CDN节点或者任意一台机器上执行以下命令,模拟回源握手:
openssl s_client -connect 源站IP:443 \
-servername www.ipipp.com \
-tls1_3 -alpn h2,http/1.1执行后重点看三行输出:New协议行显示的协议版本、Cipher行显示的协商套件,以及ALPN protocol。如果输出中没有TLSv1.3而是降级到TLSv1.2,说明Nginx配置里ssl_protocols没有启用TLSv1.3,或者OpenSSL版本过旧(TLS1.3需要OpenSSL 1.1.1以上)。如果ALPN协商结果为空,回源请求可能无法走HTTP/2,某些CDN会因此报错。
如果要在Nginx本机确认QUIC端口是否正常监听,可以用ss命令查看UDP 443端口的状态。QUIC走UDP,传统的telnet或curl默认测不了,这一点和TCP服务的验证方式完全不同。
ss -lunp | grep 443 curl --http3-only -v https://www.ipipp.com/ --resolve www.ipipp.com:443:127.0.0.1
注意curl需要编译了HTTP/3支持才能使用--http3-only参数。如果本机测试QUIC正常但CDN回源异常,问题大概率出在CDN到源站之间的网络上,比如防火墙或者安全组把UDP或高位端口拦截了。
四、高频故障原因与排查清单
结合实际运维经验,回源握手失败的原因相对集中,可以按下面的清单逐项排除:
- 证书链不完整:只配置了域名证书没有拼接中间证书,CDN严格校验时握手直接失败,日志表现为certificate verify failed。
- TLS版本不匹配:CDN要求TLS1.3回源但Nginx只允许TLS1.2,或者反之。检查
ssl_protocols配置是否覆盖了双端需求。 - SNI缺失或不匹配:部分CDN回源时不携带SNI,多域名虚拟主机场景下Nginx会命中默认证书导致校验失败,可考虑配置默认server块。
- 加密套件协商失败:TLS1.3的套件由
ssl_conf_command Ciphersuites控制,与TLS1.2的ssl_ciphers是两套独立配置,改错了地方不会生效。 - QUIC端口未放行:HTTP/3依赖UDP 443,云服务器安全组只放行TCP是常见疏漏。
- 会话票据跨节点问题:多台Nginx负载均衡时,TLS session ticket密钥不一致会导致会话复用失败,虽然会重新握手不算故障,但高频出现时建议统一ticket key。
排查时建议的顺序是:先看error日志确认握手失败的具体错误码,再用openssl模拟回源握手复现问题,最后对照上面的清单定位配置层面原因。把这三个步骤固化成脚本,可以在故障发生时快速拿到第一手信息,避免盲目重启服务掩盖真实问题。