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:
- DNS resolves to a CDN or origin IP.
- Client opens TCP to port 443 and starts TLS 1.2 or 1.3.
- During TLS, ALPN lists
h3,h2, andhttp/1.1in preference order. - Server picks the highest mutually supported protocol.
- For HTTP/3, the server may also advertise an
Alt-Svcheader 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-Controlheaders. - Keep domain consolidation. Sharding assets across
static1.example.comandstatic2.example.comwas 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_pushconfigs — 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 --http2and browser Protocol column. - Enable HTTP/3 at CDN if available; confirm UDP 443 and
Alt-Svcadvertisement. - Audit asset bundling strategy — split caches, fingerprint static files, set long
max-ageon 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
- HTTP caching explained — Cache-Control, ETags, and CDN edge behavior that pairs with HTTP/2 asset splitting
- TLS and HTTPS explained — certificates, handshakes, and ALPN negotiation prerequisites
- CDN explained — edge PoPs, HTTP/3 termination, and origin pull
- Load balancing explained — distributing connections across backends behind HTTP/2 terminators