Skip to main content

HTTP/2 and HTTP/3 (QUIC): Multiplexing, Header Compression

What This Concept Is

HTTP/2 and HTTP/3 are not replacements for HTTP's request/response model. The methods, status codes, and header names are almost unchanged. What changed is how those messages are framed and carried on the wire.

HTTP/2:

  • runs over one TCP connection, but multiplexes many concurrent streams
  • uses a binary framing layer instead of ASCII text
  • compresses headers with HPACK (static + dynamic tables, Huffman coding)
  • supports server push (rarely used in practice now)

HTTP/3:

  • runs over QUIC, which runs over UDP (not TCP)
  • moves reliability, ordering, and congestion control into the user-space QUIC implementation
  • integrates TLS 1.3 into the transport handshake (one or zero RTT)
  • fixes HTTP/2's remaining head-of-line blocking by giving each stream its own independent delivery

Why It Matters Here

HTTP/1.1's head-of-line blocking was painful: one slow response on a keep-alive connection blocked everything behind it. Browsers worked around it by opening many parallel TCP connections (wasteful).

HTTP/2 fixes this at the HTTP layer but a single TCP loss still stalls every HTTP/2 stream riding that TCP connection. HTTP/3 fixes that by putting each stream in its own QUIC-level reliability domain.

This is why QUIC is on UDP -- the kernel's TCP could not be extended fast enough, and putting transport in userspace made experimentation possible.

Concrete Example

An HTTP/2 GET / might be carried in a single HEADERS frame on stream 3:

+-----------------------------------------------+
| Length (24) | length of this frame's payload
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) | which stream
+=+=============================================================+
| Frame Payload ... | HPACK-compressed headers
+---------------------------------------------------------------+

Stream IDs let the client and server interleave frames from different requests on one TCP connection. HPACK avoids re-sending the same User-Agent string on every request by indexing it into a dynamic table.

Under HTTP/3, the same logical request rides a QUIC stream; the wire frames are QUIC packets instead of TCP segments, and the TLS 1.3 handshake is woven into the connection setup.

Common Confusion / Misconception

"HTTP/2 is always faster than HTTP/1.1." Not always. For single-request workloads on a reliable network, HTTP/1.1 with keep-alive is competitive. HTTP/2 wins when many requests share a connection and when header sizes matter.

"HTTP/3 is TCP over UDP." No. QUIC is a new transport that happens to use UDP as a substrate. It is not reimplementing TCP; it makes different tradeoffs (per-stream reliability, built-in encryption, faster handshake).

How To Use It

When choosing or debugging:

  1. If you control client and server and want low-latency APIs, accept either HTTP/2 or HTTP/3 and let ALPN negotiate.
  2. If a request is stalling, check whether a prior TCP loss is blocking it (ss -ti shows retransmits). On HTTP/3 that should not happen across streams.
  3. Do not optimize HTTP/2 by opening multiple connections -- that defeats header-table reuse and congestion pooling.

Check Yourself

  1. What is the specific head-of-line blocking that HTTP/2 fixes, and what head-of-line blocking remains on TCP?
  2. Why does HTTP/3 need to run on UDP rather than TCP?
  3. Why does HPACK help real workloads even though each header is already just a few dozen bytes?
  4. What is ALPN and where in the stack is it negotiated?
  5. If HTTP/2 is already widely deployed, what specific workloads most benefit from moving to HTTP/3?

What Did Not Change

It is worth remembering what these new versions do not change:

  • Methods (GET, POST, etc.) and their semantics are unchanged.
  • Status codes and their classes are unchanged.
  • Header names are unchanged; only the encoding is compressed.
  • URI and caching semantics are unchanged.

Practically, an HTTP/1.1 handler in your application layer usually works unchanged on HTTP/2 or HTTP/3 because the framing happens below the level your code sees. That was a deliberate design choice to make the upgrade path tractable for the web ecosystem.

Mini Drill or Application

  1. Run curl --http2 -sI https://www.google.com/ and curl --http3 -sI https://www.google.com/ (if your curl has HTTP/3). Note the reported ALPN protocol.
  2. Capture both with tcpdump port 443 and compare the number of packets needed for the first response.
  3. Write one paragraph distinguishing what changed in the transport vs what is the same HTTP request semantically.

Read This Only If Stuck