HTTP/3基于QUIC协议运行在UDP之上,与传统的HTTP/1.1、HTTP/2在TCP上的传输方式完全不同。当Nginx同时对外提供多协议服务、对内通过proxy_pass回源到上游服务器时,日志里往往只记录了请求的URL和状态码,看不到客户端到底用的是不是QUIC,也看不到Nginx回源时用的是什么协议。缺少这些信息,一旦出现访问缓慢、握手失败或者回源异常,排查起来就只能靠猜。本文将从变量、日志格式到回源排查,完整讲清楚如何在Nginx日志中记录HTTP/3 QUIC传输的细节。

一、先搞清楚Nginx中与协议相关的内置变量
Nginx在识别客户端协议这件事上提供了几个非常实用的内置变量。$server_protocol记录的是请求使用的HTTP协议版本,取值通常是HTTP/1.0、HTTP/1.1、HTTP/2.0或者HTTP/3.0。当客户端通过QUIC成功完成握手并发起请求时,这个变量的值就是HTTP/3.0,这是判断客户端是否走HTTP/3最直接的方式。
除了协议版本,$http3变量对应请求头中的Alt-Used或HTTP/3相关的标记头,部分客户端会携带它表明自己正在使用HTTP/3。另外,监听QUIC的端口需要开启quic和reuseport参数,此时$scheme的值会是https,所以不能只看scheme来判断协议,必须结合$server_protocol才能得到准确结论。
还需要注意,QUIC握手相关的错误码可以通过$quic_errno一类的变量(在部分编译版本中提供)或者error日志中的关键字来观察。如果Nginx编译时没有包含ngx_http_v3_module模块,listen ... quic指令会直接报错,可以通过nginx -V查看编译参数确认是否带有--with-http_v3_module。
二、定义能完整记录QUIC传输的日志格式
默认的combined日志格式对HTTP/3观测毫无帮助,需要自定义log_format。下面给出一个生产可用的示例,把协议版本、ALPN协商结果、请求耗时、回源地址全部记录下来:
log_format quic_trace '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'proto=$server_protocol alpn=$ssl_alpn_protocol '
'upstream=$upstream_addr ups_proto=$upstream_http_server_protocol '
'rtt=$request_time urtt=$upstream_response_time '
'cipher=$ssl_cipher version=$ssl_protocol';
access_log /var/log/nginx/quic_access.log quic_trace;
# 访问QUIC监听端口的请求会进入这个日志其中$ssl_alpn_protocol是关键字段,QUIC握手时ALPN协商结果为h3,说明客户端确实通过HTTP/3接入;如果显示http/1.1则说明这次请求实际降级到了旧的TLS连接。$upstream_addr和$upstream_response_time则刻画了回源侧的情况,如果Nginx回源走的是HTTP/2或HTTP/1.1,这里的协议信息要靠上游响应头来间接判断。
如果你想按协议分流到不同日志文件,可以在server块内通过map来做条件判断,示例配置如下:
map $server_protocol $is_quic {
default 0;
"HTTP/3.0" 1;
}
server {
listen 443 quic reuseport;
listen 443 ssl;
http2 on;
ssl_protocols TLSv1.3;
access_log /var/log/nginx/h3_access.log quic_trace if=$is_quic;
access_log /var/log/nginx/other_access.log quic_trace if=!$is_quic;
}这种写法把QUIC流量和普通流量分开落盘,统计HTTP/3的请求占比时直接对两个文件做行数比对即可,不需要再写复杂的awk脚本去过滤。
三、回源场景下如何观测和排查QUIC链路问题
回到回源这个话题,目前的典型架构是客户端到Nginx这一段走QUIC,而Nginx到上游之间仍然通过TCP承载的HTTP/1.1或HTTP/2传输。Nginx官方的proxy模块目前不支持以QUIC作为回源协议,因此不能指望在回源侧直接跑HTTP/3。这种情况下,日志观测的重点就变成了两端各自的耗时:客户端侧的$request_time和上游侧的$upstream_response_time之差,大致就是Nginx自身处理加网络转发的开销。
排查QUIC握手失败有一个容易被忽略的点:客户端可能先尝试HTTP/3,握手失败后静默回退到TCP上的HTTP/2,表现就是页面能打开但日志里全是HTTP/2.0记录。要捕捉这类回退,可以开启error日志的info级别观察QUIC相关的报错,常见原因包括UDP 443端口被防火墙拦截、中间设备丢弃UDP长包、证书没有配置TLSv1.3等。示例配置:
error_log /var/log/nginx/error_quic.log info;
server {
listen 443 quic reuseport;
listen 443 ssl;
ssl_certificate /etc/nginx/certs/server.pem;
ssl_certificate_key /etc/nginx/certs/server.key;
ssl_protocols TLSv1.3;
add_header Alt-Svc 'h3=":443"; ma=86400';
quic_retry on;
}quic_retry on会让Nginx响应地址验证的Retry包,配合Alt-Svc头告知客户端后续请求可以切换到HTTP/3。日志方面,握手阶段发生的问题一般不会出现在access日志里,因为请求还没成型就失败了,只能依赖error日志,这也是很多人明明开了QUIC却查不到失败原因的原因所在。
最后建议对日志做聚合分析时,把proto、rtt、upstream三个维度组合起来看:proto为HTTP/3.0但rtt明显偏高的请求,可能是QUIC在弱网下的重传或拥塞控制问题;upstream耗时高而整体rtt正常的,瓶颈在上游服务而不是传输协议。通过一套完整的日志格式加上按协议分流的策略,HTTP/3上线后的流量画像和问题定位都能有据可查。