awesome-everything RU
↑ Back to the climb

Networking & Protocols

HTTP/2: streams, frames, and HPACK

Crux HTTP/2 replaced HTTP/1.1''''s parallel-connections workaround with multiplexed streams on one TCP connection — HPACK compresses headers, and server push proved a dead end.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at middle altitude — in the sky
◷ 14 min

A browser loading a page with 50 sub-resources opens 6 TCP connections to the same origin, fills them with requests, and queues the rest. Each connection pays its own handshake cost. HTTP/2 collapses all that into one connection with 50 concurrent streams — but the fix comes with its own failure mode.

HTTP/1.1: pipelining and six connections

HTTP/1.1 (RFC 9112) sends one request per TCP connection and waits for the complete response before sending the next on that connection. To parallelise, browsers open 6–8 connections to the same origin. A browser loading 10 critical JavaScript files opens 6 connections, sends one request on each, then queues the remaining 4. Once a response completes, the connection is freed and fetches the next file.

Pipelining (RFC 7230) allowed clients to send multiple requests without waiting for responses — but it rarely worked. Proxies broke it, and if request #1 stalled waiting for a slow server response, request #2 would block behind it on the same connection anyway — head-of-line (HOL) blocking on the response side. Nearly all browsers disabled pipelining by default.

HTTP/2: the framing layer

HTTP/2 (RFC 9113) abandons the one-request-per-connection model. A single TCP connection carries many concurrent streams — each stream is an independent request-response pair.

Frames are the unit of transmission:

  • HEADERS frame — opens a stream and carries request or response metadata (status, headers). A stream is created when the client sends a HEADERS frame with a new stream ID.
  • DATA frames — carry body content for the stream.
  • RST_STREAM — aborts a stream without closing the connection.
  • SETTINGS — negotiates connection parameters (max concurrent streams, initial window size, header table size).
  • WINDOW_UPDATE — flow control: tells the sender how many more bytes the receiver is willing to buffer.

The server can interleave responses from different streams: 100 bytes of stream #1’s response, then 100 bytes of stream #3’s, then more of stream #1 — all on the same underlying TCP connection. Order within a single stream is preserved; order between streams is up to the server.

HTTP/2 framing facts
HTTP/2 frame header size
9 bytes (type, flags, length, stream ID)
Stream ID space
31 bits (odd = client-initiated, even = server push)
Default flow-control window
65,535 bytes per stream
Common server setting: max concurrent streams
100 (RFC 9113 recommendation)
SETTINGS frame negotiated
at connection start, before first request
RST_STREAM cost
one frame — no TCP teardown needed

HPACK header compression

HTTP/1.1 sends full headers on every request — typically 400–800 bytes per request even if only the path changes. HTTP/2 uses HPACK (RFC 7541) to compress HEADERS frames:

  1. Static table — 61 pre-defined (name, value) pairs with common headers (":method": "GET", ":status": "200", "content-type": "text/html", etc.). A single byte index replaces the full header.
  2. Dynamic table — entries added per connection as new headers appear. If the first request sends user-agent: Mozilla/5.0, the second request can reference it with a 1–2 byte index, sending only the delta.

Result: after the first request to a domain, subsequent requests reduce header overhead from ~400 bytes to ~20–50 bytes. On a page with 50 sub-resources, that saves ~18 KB of header data.

The catch: the dynamic table is ordered and shared across all streams. If index 60 is added to the table and a HEADERS frame referencing index 60 is lost, subsequent HEADERS frames cannot decode index 60 until retransmission arrives. On TCP this is invisible (TCP delivers in order anyway). On QUIC-based HTTP/3, this ordering dependency breaks stream independence — which is why HTTP/3 replaced HPACK with QPACK (covered in the next lesson).

HTTP/2 flow control

Each HTTP/2 connection has connection-level and stream-level flow control via WINDOW_UPDATE frames — the receiver tells the sender how many bytes it is willing to buffer. Defaults: 65,535 bytes per stream, sometimes raised to 16 MiB by modern servers for high-throughput APIs.

SETTINGS_MAX_CONCURRENT_STREAMS bounds how many streams a client can open simultaneously (typical: 100). Hitting the limit forces the client to queue requests, undoing some of the multiplexing benefit. Tuning: raise the window size on long-lived streams; raise max-concurrent-streams for chatty APIs.

Trace it
1/5

Trace a modern browser loading a page over HTTP/2 from clean state.

1
Step 1 of 5
Browser completes DNS, TCP, TLS. What does it advertise in TLS ALPN?
2
Locked
TLS handshake completes. What does the browser send first?
3
Locked
Server responds. What does the response look like at the framing level?
4
Locked
Browser parses the HTML and discovers 50 sub-resources. What does it do?
5
Locked
One TCP packet is lost mid-page. What happens to all 50 streams?

Server Push: the feature that failed

HTTP/2 introduced Server Push — the server could send resources the browser had not yet requested. On receiving a GET /index.html, the server could send a PUSH_PROMISE for /style.css, betting the browser would need it.

In theory, this saves a request-response round-trip. In practice it failed on three counts:

  1. The server has no visibility into what the browser already cached. It blindly pushes /style.css even if the browser has a fresh cached copy, wasting bandwidth.
  2. Push only helps on the first navigation — on warm loads, the browser is already caching aggressively and pushed resources arrive unrequested.
  3. Middleboxes and CDNs didn’t handle push correctly, leading to duplicate transfers.

Chrome removed Server Push in 2022. RFC 9113 deprecates PUSH_PROMISE entirely. The modern replacement: 103 Early Hints + <link rel="preload"> — the server sends a 103 informational response pointing to critical sub-resources, letting the browser decide whether to fetch them. This gives the browser agency to check its cache first.

Quiz

Why is Server Push effectively dead in 2026?

Quiz

HTTP/2 multiplexing eliminates HOL blocking — true or false?

Order the steps

Order the events a modern browser does to fetch https://example.com over HTTP/2:

  1. 1 DNS resolution for example.com
  2. 2 TCP handshake
  3. 3 TLS 1.3 handshake (ALPN selects h2)
  4. 4 HTTP/2 SETTINGS frame exchanged
  5. 5 HEADERS frame sent for page request
  6. 6 DATA frames received with HTML body
  7. 7 50 more HEADERS frames sent for sub-resources
  8. 8 DATA frames interleaved from all 50 streams
Trace it
1/4

Diagnose: why does this HTTP/2 API service have higher latency on mobile networks than HTTP/1.1?

1
Step 1 of 4
Step 1: what network metric tells you immediately if packet loss is the problem?
2
Locked
Step 2: you measure 1.2% loss on the mobile path. The API does 50 concurrent requests per page. What happens in HTTP/2?
3
Locked
Step 3: same scenario over HTTP/1.1 with 6 parallel connections. What happens?
4
Locked
Step 4: should this API service switch back to HTTP/1.1 on mobile?
Recall before you leave
  1. 01
    Explain why HTTP/2 multiplexing does NOT fully solve head-of-line blocking.
  2. 02
    What is HPACK and what is its main limitation?
  3. 03
    What replaced HTTP/2 Server Push and why does it work better?
Recap

HTTP/2 introduced stream multiplexing over a single TCP connection, replacing HTTP/1.1’s six-connection workaround. Frames (HEADERS, DATA, RST_STREAM, SETTINGS, WINDOW_UPDATE) are the unit of transfer; streams interleave freely on one connection. HPACK cuts header size by 80–90% using a static table and per-connection dynamic table. Flow control via WINDOW_UPDATE prevents buffer exhaustion. Server Push — the server proactively sending uncached resources — was removed from Chrome in 2022 because the server cannot know what the browser has cached; 103 Early Hints replaced it. The remaining open problem: a single lost TCP packet stalls every stream — TCP-layer HOL blocking that HTTP/3 solves with QUIC.

Connected lessons
appears again in5
Continue the climb ↑HTTP/3 and QUIC: stream-level loss isolation
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.