Guide

HTTP/2 and HTTP/3 explained: multiplexing, QUIC, and modern web performance

Every page load is a conversation between browser and server — and the rules of that conversation changed twice in the last decade. HTTP/1.1 (1999) assumed one request at a time per connection. Developers worked around its limits with domain sharding, sprite sheets, and inlining CSS. HTTP/2 (2015) multiplexes many requests on a single TCP connection with binary framing and compressed headers. HTTP/3 (2022, RFC 9114) keeps HTTP/2 semantics but replaces TCP with QUIC over UDP, attacking head-of-line blocking at the transport layer. If you tune HTTP caching and ship assets through a CDN but never check which HTTP version clients actually use, you are optimizing half the stack. This guide explains what each version changed, how TLS negotiation picks the version, and what to verify before and after enabling HTTP/2 or HTTP/3 in production.

Why HTTP/1.1 became a bottleneck

HTTP/1.1 is a text protocol: request lines and headers are human-readable, terminated by CRLF. That simplicity hid two performance problems at scale.

Head-of-line blocking at the connection level

On a single HTTP/1.1 connection, responses must arrive in order. If the browser requests a large JavaScript bundle first and a tiny icon second, the icon waits behind the bundle even though they are independent. Browsers opened 6 parallel connections per host (sometimes more) to work around this — which multiplied TCP handshakes and TLS round trips.

Verbose, repetitive headers

Every request resent cookies, User-Agent, Accept, and dozens of other headers. On mobile networks with high latency, header bytes and round trips dominated time-to-first-byte for API-heavy pages.

Frontend engineers responded with bundling, spriting, and concatenation — optimizations that made sense for HTTP/1.1 but fight against granular caching and code splitting today. HTTP/2 and HTTP/3 remove the need for many of those hacks.

HTTP/2: one connection, many streams

HTTP/2 keeps the same methods, status codes, and URL semantics as HTTP/1.1. What changed is how bytes move on the wire.

Binary framing and multiplexing

HTTP/2 splits communication into frames carried on streams (numbered, bidirectional channels). A browser can interleave frames from dozens of parallel requests on one TCP connection. The server can push response data for stream 7 between chunks of stream 3 without waiting for stream 3 to finish.

HPACK header compression

HPACK maintains a dynamic table of previously sent header fields. Repeated headers like :authority and cookies are encoded as small integer references instead of full strings. For SPAs that poll APIs with identical header shapes, HPACK cuts overhead dramatically.

Server push (largely deprecated in practice)

HTTP/2 allowed servers to push assets the client had not yet requested. In theory this eliminated round trips; in practice browsers struggled to match pushed resources with pending requests, wasting bandwidth. Chrome removed push support; most teams rely on preload link hints and smart caching instead. Know it exists in the spec, but do not build new architectures around server push.

TLS is effectively required

Browsers only negotiate HTTP/2 over HTTPS using ALPN (Application-Layer Protocol Negotiation) during the TLS handshake. The client advertises h2; the server picks HTTP/2 or falls back to HTTP/1.1. Cleartext HTTP/2 (h2c) exists for internal networks but is rare on the public internet.

HTTP/2 still blocks on TCP packet loss

Multiplexing fixes application-level head-of-line blocking, but all streams share one TCP connection. If a packet is lost, TCP retransmits and every stream on that connection pauses until recovery. On lossy Wi-Fi or congested mobile links, HTTP/2 can underperform expectations — which motivated HTTP/3.

HTTP/3: QUIC replaces TCP

HTTP/3 is not a rewrite of HTTP semantics — it is HTTP/2-style framing carried over QUIC, a transport protocol built on UDP and standardized as RFC 9000.

Independent streams without TCP head-of-line blocking

QUIC multiplexes streams at the transport layer. Packet loss on one stream does not stall unrelated streams the way TCP does. For pages that fetch many small assets and API responses in parallel, HTTP/3 often wins on high-latency or lossy networks.

Integrated encryption and faster handshakes

QUIC encrypts nearly everything by design — including connection setup metadata. A QUIC connection can complete a 1-RTT handshake (or 0-RTT resumption for repeat visitors, with replay caveats). That collapses the historical sequence of TCP handshake, then TLS handshake, then first HTTP request.

Connection migration

QUIC connections are identified by connection IDs, not just the quad of source IP, source port, destination IP, destination port. When a phone switches from Wi-Fi to cellular, QUIC can migrate the connection without a full reconnect — useful for long-lived sessions and WebSocket or SSE adjacency, though pure WebSockets still commonly run over TCP today.

UDP deployment friction

HTTP/3 needs UDP port 443 open end-to-end. Some corporate firewalls and middleboxes block or throttle UDP. Mature stacks implement HTTP/3 fallback: try QUIC, fall back to HTTP/2 on TCP if UDP fails. CDNs like Cloudflare and Fastly handle this automatically; self-hosted nginx needs explicit listen ... quic directives and compatible OpenSSL or BoringSSL builds.

How browsers and servers negotiate versions

The typical HTTPS flow on a modern edge:

  1. DNS resolves to a CDN or origin IP.
  2. Client opens TCP to port 443 and starts TLS 1.2 or 1.3.
  3. During TLS, ALPN lists h3, h2, and http/1.1 in preference order.
  4. Server picks the highest mutually supported protocol.
  5. For HTTP/3, the server may also advertise an Alt-Svc header on HTTP/2 responses pointing to a QUIC endpoint for subsequent requests.

Your reverse proxy or CDN terminates TLS and speaks HTTP/2 or HTTP/3 to clients, then often HTTP/1.1 or HTTP/2 to your origin. Version mismatches between hops are normal — what matters is the client-to-edge path.

Quick verification commands

# HTTP/2 response headers (look for HTTP/2 200)
curl -sI --http2 https://example.com/

# Verbose ALPN negotiation
curl -v --http2 https://example.com/ 2>&1 | grep ALPN

# Chrome DevTools → Network → Protocol column (h2, h3, http/1.1)

In Chrome, enable the Protocol column in the Network panel. Safari and Firefox expose similar details. If every request shows http/1.1 on a site you believe upgraded, check certificate chain issues, ALPN config, or an intermediate proxy stripping HTTP/2.

What changes in your application code

HTTP/2 and HTTP/3 are mostly transparent to application logic — your Express, Django, or static file server still sees normal requests. What should change is asset strategy:

  • Stop over-bundling. Granular files cache independently when you pair HTTP/2+ with fingerprinted filenames and long Cache-Control headers.
  • Keep domain consolidation. Sharding assets across static1.example.com and static2.example.com was an HTTP/1.1 trick; one host with multiplexing is usually faster now.
  • Prioritize critical bytes. Use fetchpriority="high" on LCP images; HTTP/2 stream priorities exist but browser heuristics and preload hints matter more in practice.
  • Watch server push removal. Audit old nginx http2_push configs — they are dead weight on modern Chrome.

API designers gain less from versioning upgrades than static-heavy publishers, but smaller header overhead still helps chatty mobile clients hitting JSON endpoints hundreds of times per session.

HTTP/2 vs HTTP/3: when each wins

Scenario Likely winner Why
Low-latency fiber, few parallel requests HTTP/2 or HTTP/3 (tie) Handshake savings matter; loss is rare
Lossy mobile, many parallel assets HTTP/3 QUIC isolates stream loss
Corporate network blocking UDP HTTP/2 fallback QUIC never establishes; ALPN falls back
Self-hosted origin without QUIC support HTTP/2 via CDN edge CDN terminates h3; origin can stay h1/h2

Enabling HTTP/3 at a CDN edge is low risk when fallback works. Enabling it on a bespoke origin requires testing UDP path, CPU overhead of QUIC user-space stacks, and observability tooling — many log parsers still assume TCP-centric metrics.

Common mistakes and pitfalls

  • Assuming HTTP/2 fixes slow backends. Multiplexing hides network inefficiency; a 2-second database query is still 2 seconds.
  • Disabling HTTP/1.1 too aggressively on old clients. Ancient crawlers and embedded devices may not speak h2; keep graceful downgrade.
  • 0-RTT replay on mutating endpoints. QUIC 0-RTT resumption can replay idempotent GETs safely; POST without idempotency keys is dangerous.
  • Ignoring TLS certificate renewal on ALPN. Expired certs force fallback or hard failures before any HTTP version negotiation happens.
  • Measuring only synthetic lab tests. Real-user monitoring (CrUX, RUM beacons) on cellular networks reveals HTTP/3 gains lab fiber tests miss.

Production checklist

  • Confirm HTTPS with valid certs and modern TLS (1.2+; prefer 1.3).
  • Enable HTTP/2 on edge and origin; verify with curl --http2 and browser Protocol column.
  • Enable HTTP/3 at CDN if available; confirm UDP 443 and Alt-Svc advertisement.
  • Audit asset bundling strategy — split caches, fingerprint static files, set long max-age on immutable assets.
  • Remove domain sharding and deprecated server-push configs.
  • Monitor p75 LCP and TTFB by protocol version in RUM if your analytics support it.
  • Document fallback behavior when UDP is blocked for enterprise users.

HTTP versioning is infrastructure you enable once and benefit from for years. The highest-leverage move for most publishers is HTTP/2 everywhere today, HTTP/3 at the CDN edge when your provider supports it, and asset architecture that assumes multiplexing instead of fighting it.

Related reading