Tags: web-dev concept

HTTP Versions

Date: 2026-08-17


Three wire formats for the same semantics. Each version fixed the bottleneck the previous one created, and each fix invalidated an optimisation that was best practice under the old one — which is why so much performance advice is quietly out of date.


HTTP/1.1, HTTP/2 and HTTP/3 change how messages are transmitted, not what they mean. A GET returning 200 means the same thing in all three — HTTP Semantics.

What each changed

HTTP/1.1   1997
  text protocol, one request at a time
  per connection
  → browsers opened ~6 connections
    per host to fake parallelism

HTTP/2     2015
  binary, MULTIPLEXED over one connection
  header compression, server push
  → many requests share one connection
  ← still TCP, so still head-of-line
    blocking

HTTP/3     2022
  same multiplexing, over QUIC (UDP)
  → per-stream loss recovery
  → handshake folded into connection
    setup

The problem each was solving

HTTP/1.1’s bottleneck was request count. One request at a time per connection, six connections per host, so the seventh resource waits.

6 connections, 30 resources
→ five waves of requests

HTTP/2 multiplexed them — all thirty in flight on one connection, interleaved.

But TCP guarantees order, so a single lost packet blocks every stream sharing the connection until it’s retransmitted — TCP and UDP:

HTTP/1.1  6 connections, loss affects 1
HTTP/2    1 connection, loss affects ALL

On a clean network HTTP/2 wins comfortably. On a lossy one it could be worse than HTTP/1.1 — which is the problem HTTP/3 solved by moving to QUIC and recovering losses per stream.

The optimisations that HTTP/2 made obsolete

This is the practically important part, because the advice persists:

HTTP/1.1 BEST PRACTICE   UNDER HTTP/2+

concatenate all JS       harmful — one
into one bundle          change invalidates
                         the whole cache

CSS sprites              unnecessary, and
                         they break caching
                         granularity

domain sharding          actively harmful —
(assets on               splits one connection
static1., static2.)      into several, each
                         paying handshake and
                         slow start

inline small assets      usually harmful —
as data URIs             uncacheable, and
                         inflates the HTML

All four were correct in 2014 and are wrong now. If you inherit a build doing them, undoing them is often a free performance win — Code Splitting.

Server push, which failed

HTTP/2 could push resources the client hadn’t asked for. In practice it pushed things the client already had cached, wasting bandwidth, and it was hard to use correctly.

It has been removed from Chrome and is effectively dead. 103 Early Hints is the replacement — the server sends hints about what to preload while it’s still generating the response, and the client decides. Same benefit, client keeps control.

[CHECK: current browser and CDN support for 103 Early Hints before relying on it.]

What you actually control

You don’t choose the version — the client and server negotiate the best both support. What you control:

  • Enabling HTTP/2 and /3 on your server or CDN. Usually a toggle, and usually already on with any modern CDN
  • HTTPS, which is a hard prerequisite for both — TLS and HTTPS
  • Not fighting the protocol with obsolete 1.1-era optimisations
  • Reducing distinct hostnames, which is now a straightforward win rather than a tradeoff — Third-Party Scripts

Honest expectations

HTTP/3’s gains are concentrated on poor connections — mobile, congested, high-loss. On a fast wired link the difference is small, which is why synthetic benchmarks underwhelm while real-user metrics improve.

That distribution is the point. The users on bad connections are the ones whose experience has most room to improve, and they’re systematically underrepresented in whatever you test on — Percentiles in Performance.