<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Quic · XB 现场笔记</title><link>https://www.xbcatm.com/zh/tags/quic/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/tags/quic/index.xml" rel="self" type="application/rss+xml"/><item><title>HTTP3 走 UDP，HTTP2 仍走 TCP 兜底</title><link>https://www.xbcatm.com/zh/posts/nginx-http3-http2-fallback/</link><pubDate>Sat, 12 Feb 2028 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/posts/nginx-http3-http2-fallback/</guid><description>HTTP/3 用基于 UDP 的 QUIC 换掉 TCP 头几行阻塞，HTTP/2 仍是老客户端的可靠兜底。</description><content:encoded>&lt;p&gt;HTTP/3 把 QUIC 跑在 UDP:443 上，值得在意的理由是丢一个包的代价。TCP 连接上所有流共用一个字节流，丢一个段就会挡住它后面所有数据的交付，直到重传到达。QUIC 让每条流各自拥有丢失恢复，同一个丢包只挡住数据真的在那个包里的流。&lt;/p&gt;
&lt;p&gt;别扭的地方在于这不是一个开关。客户端必须先知道服务器会说 HTTP/3 才可能去试，而它得知这件事所走的那条路径，恰恰是你想优化的那条。&lt;/p&gt;
&lt;h2 id="两种传输一个端口号"&gt;两种传输，一个端口号&lt;/h2&gt;
&lt;p&gt;UDP:443 和 TCP:443 属于彼此独立的命名空间，所以一个监听可以同时占住两者。这就是为什么 nginx 的 server 块可以把 &lt;code&gt;listen 443 quic reuseport;&lt;/code&gt; 和 &lt;code&gt;listen 443 ssl; http2 on;&lt;/code&gt; 并排放着，用同一个端口号同时服务 QUIC 和 HTTP/2。&lt;code&gt;reuseport&lt;/code&gt; 比看起来更重要：没有它，第二次绑定 UDP:443 会拿到 EADDRINUSE，而这正是两个 server 块都声称要占用 QUIC 监听时看到的报错。&lt;/p&gt;
&lt;p&gt;QUIC 不是「HTTP/2 搬到 UDP 上」。它把传输握手和 TLS 握手合成一趟——一个往返，复用会话时是零个——并且携带一个与地址和端口无关的连接 ID。这个 ID 让连接迁移成为可能，同时也让那些假定一条流永远待在一个五元组上的负载均衡器当场失效。还要记住 QUIC 要求整条路径都能过 UDP：企业网络、机场 Wi-Fi 和部分移动运营商至今仍在整段丢弃 443 端口的 UDP，这正是兜底路径必须一直可用的原因。&lt;/p&gt;
&lt;h2 id="alt-svc-是客户端得知的方式"&gt;Alt-Svc 是客户端得知的方式&lt;/h2&gt;
&lt;p&gt;对某个源的第一次连接永远是走 TCP 的 HTTP/2，因为客户端此时一无所知。响应里带着一条通告：&lt;code&gt;Alt-Svc: h3=&amp;quot;:443&amp;quot;; ma=86400&lt;/code&gt;。解析开来就是协议 &lt;code&gt;h3&lt;/code&gt;、authority &lt;code&gt;:443&lt;/code&gt;、新鲜期 86400 秒，也就是 24 小时。下一次连接该源时客户端会先试 QUIC，所以升级发生在后续某次访问，永远不会发生在第一次。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ma&lt;/code&gt; 才是咬人的地方。只要通告还在新鲜期内，客户端每建立一条新连接都会先试 HTTP/3——哪怕 HTTP/3 已经反复失败。把 UDP:443 被中间盒静默丢弃的情形建个模型：客户端在 QUIC 探测超时退避里烧掉 15 秒（1、2、4 再到 8 秒）才回退到 HTTP/2。按每天 20 次访问算，就是每天白白丢掉 300 秒，一直持续到一天后缓存过期。反过来，不等 QUIC 放弃就并行发起 TCP 连接，同一条被阻断的路径 0.1 秒就能出结果。回退的代价是设计选择，不是协议开销。&lt;/p&gt;
&lt;h2 id="0-rtt-与连接迁移是两处锋利的边缘"&gt;0-RTT 与连接迁移是两处锋利的边缘&lt;/h2&gt;
&lt;p&gt;0-RTT 允许回访客户端在第一个飞行包里就发出应用数据，用的是上一会话留下的票据。这些数据可以被重放——抓到那个飞行包的人可以再发一次——所以只有可以安全重复的请求才该放进去。把「创建订单」放在 0-RTT 里，等于预订了一笔重复订单。&lt;/p&gt;
&lt;p&gt;连接迁移是另一处。QUIC 用连接 ID 而不是地址来标识连接，所以从 Wi-Fi 漫游到蜂窝网的客户端能保住同一条连接。把一台按五元组做哈希的负载均衡器放在前面，这条继续存在的连接就会落到另一个后端：八个后端的情况下，把同一客户端漫游前后的键算出来，先落到 7 号，再落到 4 号。协议的移动性变成了负载均衡器的问题。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;第一种失败，是客户端缓存着 Alt-Svc，而网络把 UDP 丢了。它的表象是整整一天里每次导航首字节都很慢，而服务器日志里没有任何异常——QUIC 的 Initial 根本没到，也就没有东西可记。把服务端的 HTTP/3 连接数和客户端感知到的延迟放在一起比；h2 路径健康却出现巨大落差，就是它的特征。缓解办法是缩短 &lt;code&gt;ma&lt;/code&gt;、并行竞速 TCP 连接，以及不要在你验证不了的路径上通告 h3。&lt;/p&gt;
&lt;p&gt;第二种失败，是连接迁移打坏了会话亲和性。移动客户端在会话中途换地址，HTTP/3 把连接保住了，而此刻接管它的后端没有任何原先那台持有的缓存或会话状态。它的表象是间歇性的认证失败、缓存不命中、上传做了一半，而且只出现在移动端客户端上——这也是它难以复现的原因。按连接 ID 路由，或者把亲和性钉在一个能扛住地址变化的东西上。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;确认两条路径都真的活着：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;curl -sI https://example.com/ | grep -i alt-svc
curl -s --http3-only -o /dev/null -w &amp;#39;%{http_version} %{time_appconnect}\n&amp;#39; https://example.com/
nginx -T 2&amp;gt;/dev/null | grep -nE &amp;#39;listen .*quic|http3|Alt-Svc&amp;#39;
ss -lun | grep -E &amp;#39;:443\b&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;第一条应当打出一条带有限 &lt;code&gt;ma&lt;/code&gt; 的 &lt;code&gt;h3&lt;/code&gt; 通告。第二条在 UDP 通的时候打出 &lt;code&gt;3&lt;/code&gt; 和很小的连接耗时，路径被挡时则直接失败——第一条成功而这里失败，就是典型的中间盒情形。第三条看配置是否真把 QUIC 和响应头接在了一起，第四条证明 443 上确实有一个 UDP 套接字被绑定。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9114.html"&gt;RFC 9114 HTTP/3&lt;/a&gt;，HTTP/3 在 QUIC 之上的语义，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc7838.html"&gt;RFC 7838 HTTP 替代服务&lt;/a&gt;，Alt-Svc 头及其新鲜期，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nginx.org/en/docs/http/ngx_http_v3_module.html"&gt;ngx_http_v3 模块&lt;/a&gt;，nginx 的 QUIC 与 HTTP/3 指令，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9000.html"&gt;RFC 9000 QUIC&lt;/a&gt;，流级丢失恢复、连接 ID 与 0-RTT，已核验 2026-09-16。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>