QUIC and HTTP/3 as a Transport

Topics Covered

Why QUIC Exists

Head-of-line blocking, which HTTP/2 could not escape

The handshake cost that compounds

Ossification, which made fixing TCP impossible

UDP as a substrate, not as a transport

Encrypting the transport header, deliberately

Version negotiation, and keeping the protocol changeable

Where QUIC does not help

What you get, concretely

QUIC is the most significant change to internet transport in thirty years, and it was not motivated by a desire for novelty. Every design decision in it is a response to a specific limitation that could not be fixed in place, and understanding those limitations is what makes the rest of the protocol follow logically rather than appearing as a pile of features.

Head-of-line blocking, which HTTP/2 could not escape

HTTP/2 multiplexes many concurrent requests onto one TCP connection, which solved the problem of browsers opening six connections per origin and paying six handshakes. It did not solve the underlying problem, because TCP delivers a single ordered byte stream.

When one TCP segment is lost, every byte after it waits in the receiver's kernel buffer until the retransmission arrives, regardless of which HTTP/2 stream those bytes belong to. Ten requests multiplexed on one connection, one lost packet, and all ten stall for a round trip. HTTP/2 moved the blocking from the application layer, where it had six connections to spread it across, down to the transport layer, where there is one stream and no escape.

The arithmetic is unkind. On a path with 1 percent loss and a 100 millisecond round trip, a page needing 100 packets will experience about one stall per page load, costing at least 100 milliseconds of dead time for every stream, and the loss rate on mobile networks is routinely higher than that. This is why HTTP/2 measured worse than HTTP/1.1 with six connections on poor networks, which was an uncomfortable result for a protocol designed to be faster.

The handshake cost that compounds

The second motivation is less discussed than head-of-line blocking and was equally important to the parties who built QUIC. Establishing a secure connection over TCP requires the three-way handshake, then the TLS handshake, in series, because TLS cannot begin until TCP's connection exists.

With TLS 1.3 that is two round trips before the first request byte; with TLS 1.2 it was three. On a 150 millisecond mobile path, three round trips is 450 milliseconds of latency before anything useful happens, repeated for every origin a page touches, and a typical page touches several. For a content provider measuring revenue against page load time, this was a concrete and expensive number rather than an aesthetic complaint.

The serialisation is the problem rather than the work. Both handshakes are necessary; doing them one after another because of a layering boundary is not. Merging them, which is what QUIC does by carrying TLS messages inside its own Initial packets, removes a round trip without removing any security property, and session resumption removes the remaining one for returning clients.

Ossification, which made fixing TCP impossible

The obvious response is to fix TCP by adding independent streams. This has been attempted, as Multipath TCP and as various extensions, and it fails for two structural reasons.

The first is that TCP lives in the kernel. A change requires operating system updates on both endpoints, and reaching the installed base of clients takes somewhere between five and fifteen years. The second is worse: middleboxes inspect and modify TCP headers. Firewalls, NAT devices, traffic shapers and load balancers parse TCP options, and many of them reject or strip options they do not recognise, or rewrite sequence numbers, or enforce assumptions about the state machine. A new TCP option is therefore not merely slow to deploy but actively unreliable on the paths it does reach, which is what ossification means: the protocol's observable surface has been frozen by the devices that look at it.

The same argument rules out a new transport protocol with its own IP protocol number. Anything that is not TCP or UDP is dropped by a substantial fraction of the internet's middleboxes, so even a perfect protocol would be unreachable.

One lost packet, ten stalled streams over TCP. QUIC's streams are independent at the transport layer, so loss affects only the stream whose bytes were lost.

UDP as a substrate, not as a transport

QUIC's answer is to build a complete transport on top of UDP, using UDP for exactly one property: it traverses the internet. Nothing in QUIC relies on any UDP semantic beyond demultiplexing by port, and QUIC provides reliability, ordering within streams, flow control, congestion control and encryption itself.

This places the entire transport in user space, which changes the deployment economics completely. A new loss recovery algorithm, a congestion control change or a security fix ships with the application, so the time from standardisation to wide deployment is months rather than a decade. The browsers and the large content providers that drove QUIC's design are precisely the parties who can deploy both ends of a connection on their own schedule, which is why it came from there.

The cost is that UDP is sometimes blocked or throttled, typically on enterprise networks, so every QUIC client needs a TCP fallback. In practice clients race the two and use whichever succeeds first, which makes deployment safe but means QUIC's benefits reach only the paths that allow it.

Encrypting the transport header, deliberately

QUIC does something unusual to prevent its own ossification: almost everything is encrypted, including the parts of the header that a middlebox would want to read. Packet numbers are protected, the stream structure is invisible, and acknowledgement information is inside the encrypted payload rather than in a header field.

This is not only a privacy measure, though it is that too. It is a deliberate decision that middleboxes must not be able to parse the transport, because anything they can parse they will eventually depend on, and anything they depend on cannot be changed. The small amount left visible, the connection identifier and a few bits of header form, is explicitly specified as the only invariant surface, so future QUIC versions remain free to change everything else.

The operational consequence lands immediately on anyone debugging it: a packet capture of QUIC traffic shows almost nothing useful without the connection's keys, which the next sections return to.

Version negotiation, and keeping the protocol changeable

Having gone to the trouble of making the protocol opaque to middleboxes, QUIC also needed a way to evolve without breaking clients, and the mechanism is more deliberate than TCP's option negotiation.

Every long-header QUIC packet carries a version field. A server that does not support the version a client requested replies with a Version Negotiation packet listing what it does support, and the client retries. Because the version is one of the few invariant fields, this works even between endpoints that otherwise share no understanding of each other's packet format, which is the property that makes a future version free to change everything else.

The specification goes further and actively defends against accidental ossification. Greasing means deliberately sending values that have no meaning, reserved version numbers, unknown frame types and unknown transport parameters, specifically so that an implementation which cannot tolerate them is discovered immediately rather than years later when a real extension needs that space. This is a direct lesson from TLS, where middleboxes that assumed a fixed set of values forced TLS 1.3 to disguise itself as TLS 1.2 to be deployable at all.

Where QUIC does not help

It is worth being explicit about the cases where none of this matters, because the enthusiasm around the protocol tends to obscure them.

A single large transfer on a path with no loss gains nothing from stream independence, because there is one stream and nothing to block. A warm pooled connection gains nothing from the handshake saving, because it has no handshake. A service inside one data centre gains nothing from migration, because its addresses do not change. In all three cases QUIC's measurable effect is the CPU it costs rather than the latency it saves.

The corollary is that performance claims about QUIC are claims about a traffic pattern and a network, not about the protocol. Cold connections, concurrent requests, lossy or long paths and mobile clients are where the gains are; warm connections, single streams and clean short paths are where they are not.

What you get, concretely

Stripped of the history, QUIC's deliverables are five. Independent streams, so loss in one does not block another. A combined cryptographic and transport handshake, so a connection is established and secured in one round trip, or zero for a resumed one. Connection migration, so a client changing network keeps its session. Modern loss detection with unambiguous packet numbering, which removes a real source of imprecision in TCP's recovery. And pluggable, updatable congestion control.

HTTP/3 is then simply HTTP mapped onto those streams: one request and response per stream, header compression adapted to tolerate out-of-order delivery, and no further need for the prioritisation and flow control machinery HTTP/2 had to invent on top of a single ordered stream.

Key Insight

The question worth asking about QUIC is not whether it is faster, which depends entirely on the path, but what it made possible that was previously blocked. Independent streams over a single connection, a one-round-trip secure handshake and connection migration are all things TCP cannot be extended to provide, not because of any technical barrier in the protocol but because the deployment path does not exist. QUIC is as much an answer to ossification as it is a transport design.