dnsprobe v1

HTTP/2 vs HTTP/3 — when each helps (and when neither does)

· ~3 min read · dnsprobe.net/blog

HTTP/2 shipped in 2015. HTTP/3 shipped in 2022. The naming makes it sound like a straightforward upgrade — version up by one, get a faster website. The actual picture is more nuanced. HTTP/3 is a major architectural shift (TCP → QUIC, IETF, RFC 9114) and it helps a specific set of workloads enormously while making no measurable difference to others.

What HTTP/2 actually changed

HTTP/1.1 sent one request, waited for the full response, then sent the next request on the same connection. Pipelining was specified but broken in practice. Sites worked around this by opening 6-8 parallel connections to the same origin. Each connection had a separate TCP handshake and TLS handshake, and TCP slow-start started fresh.

HTTP/2 multiplexed many requests onto a single TCP connection, frame-by-frame. One TCP handshake, one TLS handshake, one slow-start ramp. Header compression (HPACK) cut redundant header bytes. Server push let the server send resources the client hadn't asked for yet (now deprecated as it never delivered the wins).

The win for a typical website was real: hundreds of small assets loading concurrently over one connection, no per-request handshake cost.

The TCP head-of-line blocking problem

HTTP/2's multiplexing happens above TCP. TCP itself still delivers bytes in order. If packet 5 of a 100-packet sequence is lost, TCP holds back the delivery of packets 6-100 until packet 5 is retransmitted and received. From the application's perspective, every HTTP/2 stream is stalled — even streams that have nothing to do with the data in packet 5.

On a clean wired connection, this is invisible. On a mobile network with 1-3% packet loss, it can cost 30-40% of the multiplexing benefit. Browsers vendors measured this and decided the underlying transport had to change.

QUIC and HTTP/3

QUIC is a transport built on top of UDP. It implements its own congestion control, its own reliability layer, and its own stream multiplexing with per-stream loss recovery. A lost packet stalls only the streams whose data was in that packet — other streams keep flowing.

QUIC also integrates TLS 1.3 directly into the handshake. Where HTTP/2 needs TCP-handshake (1 RTT) + TLS-handshake (1 RTT) = 2 RTT before any HTTP data, HTTP/3 needs 1 RTT for a new connection and 0 RTT for a resumed connection.

The wire protocol is HTTP/3 (RFC 9114). It maps HTTP semantics onto QUIC streams. From an application's perspective, it is the same HTTP. From a network's perspective, it is UDP on port 443 with a completely different framing.

When HTTP/3 helps a lot

  • Mobile clients on lossy networks.
  • Long-RTT connections (transcontinental, satellite).
  • Workloads with many parallel small requests.
  • Connection migration: a phone moving from WiFi to LTE keeps the QUIC connection alive. TCP would have torn down and reconnected.

When HTTP/3 helps little or none

  • Single large transfers (video streaming, file download). The bottleneck is bandwidth, not handshakes. HTTP/2 over TCP is fine.
  • Server-to-server traffic on a low-latency wired network. The TCP head-of-line issue does not manifest.
  • Workloads behind corporate firewalls that drop or rate-limit UDP. The browser falls back to HTTP/2 anyway, and the negotiation cost is small but non-zero.

Deployment caveats

  1. HTTP/3 must be advertised via Alt-Svc. Browsers connect over HTTP/2 first, see alt-svc: h3=":443" in the response headers, and try HTTP/3 for the next request. The first visit is therefore always HTTP/2 — you only get HTTP/3 from the second request onward.
  2. UDP must reach your server. If your firewall blocks UDP/443 (some still do) or your load balancer terminates only TCP, HTTP/3 will silently never work.
  3. CPU cost is higher. QUIC moves work that was historically in the kernel (TCP) into user space. For low-volume servers this is a rounding error. For high-volume origins, plan for 5-20% more CPU under load.
  4. Stateful firewalls and DDoS appliances. Many enterprise security devices have HTTP/3 detection as a relatively recent feature. Expect to file at least one ticket per vendor.

Verifying it

From the command line:

curl --http3-only -sI https://cloudflare.com
HTTP/3 200

You need a curl built against an HTTP/3-capable libcurl (nghttp3 + ngtcp2). The Ubuntu default does not have it; you need to install curl from a recent source or use a container.

The HTTP panel on dnscheck tests both protocols and reports each with its own status — if our probe host's libcurl can't speak HTTP/3, the panel marks it "not probeable" rather than incorrectly reporting "not supported". Try cloudflare.com, which is HTTP/3-first.

References: RFC 9114 (HTTP/3), RFC 9000 (QUIC).