Guide

HTTP/1.1 and HTTP/2 use the same meaning for requests and responses. What differs is how they share a connection among many requests. That sharing decides how fast a page with 90 images loads.

Concept

Before you start: a connection is one TCP socket between a client and a server. A stream is one request and its response inside a connection. Each TCP connection pays a handshake and slow start before it is fast (see networking basics).

HTTP/1.1: one request at a time per connection

HTTP/1.0 opened a new TCP connection for every request, so each one paid a handshake and slow start. HTTP/1.1, from 1997, made persistent connections (keep-alive) the default. The connection stays open for the next request.

But one connection still carries one request at a time. The client sends a request, waits for the full response, then sends the next. HTTP/1.1 defined pipelining, which sends several requests without waiting. Responses still had to come back in order, so one slow response blocked the ones behind it. Buggy proxies corrupted pipelined streams, and browsers never turned it on by default.

Browsers worked around it by opening up to 6 parallel connections per host. A page with 90 assets on one host could only have 6 in flight at once. Developers added hacks. Domain sharding used static1.example.com, static2.example.com and so on, to get 12 or 18 connections. Teams bundled JavaScript and CSS, and inlined images as sprites. Each extra connection paid its own TCP and TLS handshakes and its own slow start.

HTTP/2: many streams on one connection

HTTP/2 (2015, now RFC 9113) keeps the same semantics and changes the wire format:

  • Binary framing. Messages are split into typed frames, such as HEADERS, DATA, SETTINGS and WINDOW_UPDATE. Each frame carries a stream ID.
  • Multiplexing. Every request and response pair is a stream. Frames from hundreds of streams interleave on one TCP connection, so a slow response no longer blocks fast ones. The browser opens one connection per origin instead of six.
  • HPACK header compression. Headers repeat a lot: the same cookies, user agent and accept headers on every request. HPACK keeps a table of earlier headers on both sides and sends small indexes. That often cuts header bytes by 85 to 90% after the first request.
  • Flow control per stream, so one large download cannot starve the others.
  • Server push was meant to send resources before the client asked. In practice it often pushed things the browser already had cached, and it rarely helped. Chrome removed support in 2022. 103 Early Hints replaced it.

HTTP/2 made sharding and bundling counterproductive, because everything now shares one connection and one congestion window.

Walking through it: one lost packet

HTTP/2 removed head-of-line blocking at the HTTP layer and moved it down to TCP. TCP delivers one ordered byte stream. Suppose one packet carrying a frame for stream 3 is lost. The kernel holds back every later byte on the connection, including complete frames for streams 5, 7 and 9. They wait until the retransmission arrives, at least one round trip later.

Put numbers on it. With 2% packet loss and a 100 ms round trip, a page load hits several losses. Each one stalls every in-flight request on the connection for at least 100 ms. On a lossy mobile network, HTTP/2 over one connection can therefore be slower than HTTP/1.1 with six connections. Six connections lose only one-sixth of their progress per lost packet.

That leftover stall is what HTTP/3 was designed to remove. See HTTP/3 and QUIC.

Common mistakes

  • Sharding domains on HTTP/2. It adds handshakes and splits the congestion window. Remove the sharding.
  • Bundling everything into one file on HTTP/2. One change then invalidates the whole bundle. Smaller files cache better.
  • Assuming multiplexing removes all blocking. TCP still stalls every stream on a lost packet.

Check yourself

  • Why did browsers open six connections per host under HTTP/1.1? One connection could carry only one request at a time, so parallel connections were the only way to get more requests in flight.
  • Why can HTTP/2 be slower than HTTP/1.1 on a lossy network? A single lost packet stalls every stream on the one TCP connection, while six connections each lose only part of their progress.

Key takeaways

  • HTTP/1.1 handles one request per connection at a time. Browsers used six connections per host to compensate.
  • HTTP/2 multiplexes streams over one connection, with binary framing and HPACK header compression.
  • HTTP/2 still runs on TCP, so a lost packet stalls all of its streams until retransmission.

Further learning

Next, see how HTTP/3 and QUIC remove that stall, in HTTP/3 and QUIC.

HTTP/3 explained (Daniel Stenberg) covers the same ground with a focus on how the protocol works on the wire.