HTTP/3 and QUIC: routing around TCP
HTTP/3 is the first HTTP that does not run on TCP. This covers where TCP head-of-line blocking actually hurts, what QUIC moved into user space, how connection migration works, and what the switch costs.
From HTTP/1.0 in 1996 to HTTP/2 in 2015, the protocol changed twice in twenty years while the layer underneath stayed TCP. HTTP/3 is the first version to break that: it runs on QUIC, and QUIC runs on UDP.
Head-of-line blocking: the problem is ordering, not loss
TCP provides one reliable, ordered byte stream. That second word is the whole story: if byte 3 is lost, the kernel cannot hand bytes 4 through 100 to the application even though they arrived. It waits for the retransmission.

Image: Daniele Giacomini, CC BY-SA 2.5, via Wikimedia Commons.
HTTP/2 added multiplexing: many streams over one TCP connection, fixing the HTTP/1.1 problem of one request per connection. But it did not touch the ordering constraint — every stream shares one byte stream, so a single lost packet stalls all of them. That is TCP-level head-of-line blocking.
Browsers work around it by opening more connections, but each connection runs its own congestion control, which tends to make things worse on a shared link.
What QUIC does: reliability in user space
QUIC starts from a blunt premise: if TCP semantics cannot be changed, implement a new transport on top of UDP. UDP only offers best-effort datagrams; everything else is built in user space.
| Capability | On TCP | With QUIC |
|---|---|---|
| Reliability | kernel retransmission | user-space retransmission, per-stream numbering |
| Ordering | global; one loss blocks every stream | ordered within a stream, streams independent |
| Encryption | TLS on top, two handshakes | TLS 1.3 built in, merged into one |
| Connection identity | 4-tuple (src/dst IP + port) | Connection ID, decoupled from addresses |
| Handshake | TCP 1-RTT + TLS 1-2 RTT | 1-RTT, 0-RTT on resumption |

Image: Jigs35, CC BY-SA 2.5, via Wikimedia Commons.
Three changes matter most:
Streams are genuinely independent. A lost packet only affects its own stream; the rest keep delivering. This is the most concrete win over HTTP/2, and it shows up hardest on lossy mobile networks.
The handshakes merge. QUIC folds the transport handshake into TLS 1.3, so one round trip is enough to start sending data. On resumption, 0-RTT puts application data in the first packet — at the cost of that packet being replayable, so it is only safe for idempotent requests.

Image: Tortue rapide, CC BY-SA 4.0, via Wikimedia Commons.
Connections follow the device, not the IP. My favourite part. A TCP connection is identified by its 4-tuple, so when a phone moves from Wi-Fi to 5G the connection dies. QUIC identifies it with a Connection ID; changing networks means sending a migration frame, and the connection slides to the new address — an in-flight download survives.
Why route around TCP instead of fixing it
The obvious question: why not put these improvements into TCP?
Because TCP lives in the kernel and in middleboxes. Kernel changes wait on OS upgrade cycles, and the harder problem is middleboxes: NATs, firewalls, load balancers and carrier policy boxes have made assumptions about what TCP looks like and how ports are used for decades. A new TCP option is likely to be dropped by some box along the path.
UDP gets completely different treatment: it is forwarded as opaque datagrams and middleboxes mostly leave the payload alone. Putting a new protocol inside UDP is therefore the only path that both iterates fast and traverses the existing network. It is also why QUIC is encrypted so heavily that middleboxes cannot inspect it — that is a design goal, not a side effect.
The costs
QUIC is not free:
- UDP is often rate-limited or blocked. UDP 443 being blocked on enterprise networks is normal, so browsers all implement “fall back to HTTP/2 when HTTP/3 fails” and remember the failure;
- Higher CPU cost. A user-space stack plus blanket encryption costs more CPU per unit of throughput than kernel TCP. Hence the push to offload QUIC to the NIC;
- 0-RTT can be replayed. Idempotent requests only;
- New operational tooling. Packet captures show encrypted UDP, and debugging looks nothing like before.
My take
The benefit of HTTP/3 depends heavily on network quality. On a clean internal network the difference from HTTP/2 is unmeasurable; on a mobile network with more than 1% loss it is obvious.
So my policy for my own sites: enable HTTP/3, keep HTTP/2 fallback, and do not optimise specifically for it. On a CDN it is usually one config line and the win is free; hand-tuning QUIC parameters and UDP stacks is a much worse trade.
What is worth remembering is the underlying move: when a protocol layer cannot be changed, reimplement it a layer up, where you can iterate freely. That idea is not specific to transports.

Comments
…