39. HTTP/1.1 vs HTTP/2 vs HTTP/3 (QUIC)
39.1 The problem each version solves
| HTTP/1.1 | HTTP/2 | HTTP/3 | |
|---|---|---|---|
| Year | 1997 | 2015 (RFC 7540 / 9113) | 2022 (RFC 9114) |
| Transport | TCP (usually + TLS) | one TCP + TLS connection | QUIC over UDP (+ TLS 1.3 built in) |
| Concurrency | 1 request per connection at a time; browsers open ~6 per origin; pipelining exists but is broken by intermediaries | streams multiplexed on one connection; interleaved frames | streams multiplexed without TCP HOL blocking |
| Head-of-line blocking | at HTTP layer (request N blocks N+1 on that connection) | at TCP layer (one lost segment stalls all streams) | eliminated — each QUIC stream is independently reliable; a loss only stalls its own stream |
| Header compression | none (verbose, repeated) | HPACK | QPACK |
| Server push | no | yes (largely deprecated in practice) | yes (rare) |
| Connection setup | TCP + TLS = 2–3 RTT | same | 1-RTT, or 0-RTT resumption; combines transport+crypto handshake |
| Connection migration | no (new IP = new connection) | no | yes — a Connection ID survives IP/port change (Wi-Fi↔cellular) |
| Prioritization | via connection count | stream priority tree (H2) / scheme (RFC 9218) | RFC 9218 |
39.2 HTTP/1.1 mechanics you still deal with
- Persistent connections (
Connection: keep-alive, default in 1.1). Reuse avoids repeated TCP+TLS handshakes — huge. - Chunked transfer encoding (
Transfer-Encoding: chunked) streams a body of unknown length; each chunk is<hex size>\r\n<data>\r\n, terminated by a0\r\n\r\n. Content-Lengthvs chunked mismatch, or twoContent-Lengthheaders, orCL+TEtogether → request smuggling when a front proxy and back server disagree on where the request ends (Ch. 52 relevance). Normalize at the edge.- Pipelining (send request 2 before response 1 returns) is allowed but disabled in every major browser because buggy proxies mis-associate responses.
- Connection limits: ~6 per origin per browser → domain sharding was a common (now counterproductive with H2) hack.
39.3 HTTP/2 mechanics
- Everything is a frame (
HEADERS,DATA,SETTINGS,WINDOW_UPDATE,RST_STREAM,PING,GOAWAY) tagged with a stream ID (odd = client- initiated). Frames from many streams interleave on the one TCP connection. - Flow control exists again at the HTTP/2 layer (per-stream and
per-connection
WINDOW_UPDATE) — independent of TCP's window. A stuck reader on one stream can stall via HTTP/2 flow control even if TCP is fine. - HPACK keeps a synchronized header table both sides; repeated headers cost ~1 byte. Dynamic-table desync or a hostile peer → the HPACK bomb DoS.
- The TCP HOL problem: all streams ride one TCP connection, so a single lost TCP segment blocks delivery of every stream's bytes behind it. On a clean network H2 crushes H1; on a lossy network (mobile) H2 can be worse than H1's 6 connections (which fail independently). This is the entire motivation for HTTP/3.
GOAWAY: graceful connection shutdown telling the client the highest stream it will process — clients replay higher streams on a new connection. Load balancers use this for draining.
39.4 HTTP/3 / QUIC mechanics
QUIC (RFC 9000) is a full reliable, multiplexed, encrypted transport in userspace, over UDP:
- Streams are independent: loss on stream 3 doesn't delay stream 7. Solves TCP HOL blocking.
- Crypto is integral: the transport and TLS 1.3 handshakes are one; 1-RTT setup, 0-RTT resumption.
- Connection ID: the connection is identified by a CID in the QUIC header, not the 4-tuple. Change networks (new IP/port) and the connection migrates seamlessly — great for phones. Load balancers must route by CID, not 5-tuple.
- Always encrypted, including most of the transport header — middleboxes can't inspect/mangle sequence numbers, so QUIC can evolve ("ossification" resistance). Only a few bits are visible.
- Its own congestion control (NewReno/CUBIC/BBR reimplemented in userspace), ACK ranges (like SACK but richer, no separate "cumulative" limitation), PLPMTUD (no ICMP dependency).
- Costs: UDP is more expensive per-packet in many kernels/NICs (less offload — improving with UDP GSO/GRO and hardware QUIC offload); some networks rate-limit or block UDP/443 → clients fall back to HTTP/2. QUIC amplification limits: a server may send at most ~3× the bytes it received from an unvalidated client address.
39.5 How a browser picks a version
- Connects via TCP+TLS, ALPN offers
h2, http/1.1→ gets HTTP/2 or 1.1. - Server response includes
Alt-Svc: h3=":443"; ma=86400or the DNSHTTPSrecord advertisesalpn=h3→ browser tries QUIC/HTTP/3 on the next navigation and caches that. - If UDP/443 is blocked/broken → falls back to h2 (Happy-Eyeballs-style racing in modern browsers).
39.6 War story
A news site enabled HTTP/2 and saw p75 page-load improve but p95 (mobile users)
regress by 20%. On lossy cellular, all 30 sub-resources shared one TCP
connection, and each packet loss stalled the whole pipe (TCP HOL). Rolling out
HTTP/3 (via Alt-Svc) recovered p95 because QUIC streams fail independently,
and connection migration cut reconnects during cell handovers. Lesson: HTTP/2
trades many-connection resilience for single-connection efficiency; on lossy
links that trade can lose. HTTP/3 is the fix.
Try it yourself
curl -sI --http2 https://example.com | grep -i '^HTTP\|alt-svc'
curl -sI --http3 https://cloudflare.com | head -1 # needs curl built with HTTP/3
dig example.com HTTPS +short # alpn=h3 advertised?
nghttp -nv https://example.com # HTTP/2 frame-level trace
# See which protocol Chrome used: DevTools > Network > Protocol column (h2 / h3 / http/1.1)