Everything I didn’t know about HTTP2 (Part 2: The Promise, The Delivery, and The Rug Pull)

I started writing this article a long time ago.
I had the whole thing planned out. Part 1 was the setup: here are the hacks, the workarounds, the things we normalized because HTTP/1.1 was never built for what we were doing with it. Part 2 was supposed to be the payoff: here comes HTTP/2, riding in to fix everything, and oh by the way, it has this beautiful feature called Server Push that finally solves the roundtrip problem cleanly and elegantly.
Then I discovered that the whole platform had quietly given up on Server Push.
I closed my draft, walked away, and didn’t touch it for a long time.
But the story is still worth telling, even with the bitter ending. Maybe especially with the bitter ending.
So What Did HTTP/2 Actually Fix?
Let’s go back to the problems from Part 1. We had:
- A 6 connection-per-origin ceiling that forced us to do domain sharding
- Bloated, monolithic asset bundles because each request was expensive
- Uncompressed, repetitive headers on every single request
- No way to proactively send assets the server already knew the client would need
HTTP/2 came with real, structural answers to most of these.
Multiplexing
This is the big one. In HTTP/1.1, each connection could handle only one request at a time. You send a request, you wait for the response, then you send the next one. The 6-connection limit existed because browsers had to open multiple connections in parallel just to make things feel reasonably fast.
HTTP/2 introduced multiplexing. A single TCP connection can now carry multiple requests and responses simultaneously, interleaved freely. The browser does not need to wait. It does not need to open six connections. One connection, everything flowing through it at once.
One connection, everything at once. No queuing, no 6-connection ceiling, no domain sharding tricks needed
This also quietly killed the justification for domain sharding. Remember hosting assets across assets1.example.com and assets2.example.com to bypass the connection limit? With HTTP/2, that trick not only becomes unnecessary, it actually hurts you. Each new origin still requires its own connection, its own TLS handshake, its own DNS lookup. You are paying the costs of sharding with none of the benefits.
Header Compression (HPACK)
HTTP is stateless. Every request carries its full headers regardless of how many times the browser has already sent them. Your cookies, your user agent string, your content-type declarations, all of it, over and over again with every request.
HTTP/2 introduced HPACK compression. Both the client and the server maintain a shared table of headers they have seen before. Instead of sending the same cookie header on every request, you send a reference to an index. The redundancy collapses.
For applications with a lot of API calls, this is not a minor optimization. Headers that used to add hundreds of bytes to every request quietly shrink to almost nothing.
Binary Framing
HTTP/1.1 is a text-based protocol. That is charming in a retro sort of way but it is not efficient. Parsing text requires handling edge cases, whitespace, line endings, ambiguous formatting.
HTTP/2 switched to a binary framing layer. All communication is now split into small binary frames, typed and structured. It is less human-readable but far more efficient to parse and less error-prone. The browser and server can now also assign priorities to different streams, so critical CSS can be marked as higher priority than a tracking pixel buried at the bottom of the page.
The Feature I Was Most Excited About
None of the above is what made me want to write this article.
What made me want to write this article was Server Push.
The roundtrip problem from Part 1 always stuck with me as the most frustrating one because it was the most avoidable. The server has the HTML. The server has the CSS. The server has the JS. The server knows what the browser is going to ask for next. And yet HTTP/1.1 forces the browser to receive the HTML, parse it, discover the linked assets, and then go back to the server and ask for them. The server sits there waiting, holding the files, watching the roundtrip happen for no reason.
Server Push was the solution. With HTTP/2, the server could push resources to the browser’s cache before the browser even knew it needed them. You request page.html, the server sends page.html and also pushes style.css and script.js alongside it. By the time the browser finishes parsing the HTML and finds the link tags, the assets are already sitting in the cache waiting.
It was elegant. It was the right answer. It solved the problem properly instead of working around it.
I was genuinely excited about it.
What Server Push promised: ask once, get everything. What killed it: the server never knew what you already had cached
The Rug Pull
Chrome disabled Server Push by default in 2022 with Chrome 106. Firefox removed it entirely in late 2024. And before any of that, HTTP/3 had already arrived without Push being meaningfully implemented in most servers and clients at all, so for a large chunk of the web it had quietly died before anyone made a formal announcement.
The reasons were real. Push was hard to implement correctly. If the server pushed resources the browser already had cached, it wasted bandwidth. The server had no reliable way to know what was in the browser’s cache. The 103 Early Hints header emerged as a lighter alternative, a way for the server to hint at resources without fully pushing them, leaving the decision to the browser.
All of this is reasonable. The reasoning makes sense. The problems with Push were genuine.
And I still felt like the rug had been pulled out from under me.
I had structured my whole mental model of HTTP/2 around Push being the piece that finally solved the avoidable-roundtrip problem completely. The other features were great. Multiplexing, header compression, binary framing, all genuinely meaningful improvements. But Push was the one that felt like a real rethinking of how client-server communication should work rather than just an optimization of the existing model.
When it was deprecated I had to sit with the uncomfortable realization that the web does not always evolve in straight lines toward obviously better solutions. Sometimes a good idea arrives before the ecosystem is ready for it. Sometimes the right answer in theory runs into the wrong realities in practice.
What HTTP/2 Actually Delivered
Here is where I have landed after some distance from that initial frustration.
HTTP/2 did deliver. Multiplexing alone was a significant shift, and the fact that it made a generation of performance hacks obsolete is genuinely worth appreciating. Domain sharding, asset concatenation as a necessity, the worst costs of head-of-line blocking at the HTTP layer: these problems are either gone or dramatically reduced.
The workarounds we built in the HTTP/1.1 era were clever adaptations to a broken situation. HTTP/2 changed the situation. Some of those old habits are now not just unnecessary but actively counterproductive. That is a real change.
But Server Push’s story is a useful reminder. The web is not a controlled environment. A protocol feature does not live or die on its technical merits alone. It lives or dies in the gap between what a specification imagines and what a browser vendor, a CDN operator, and a server implementation can actually coordinate around reliably at scale.
A Note on HTTP/3
I am going to save HTTP/3 for another time because it deserves its own space. But I will say this: it moves the underlying transport from TCP to QUIC, which addresses a different kind of head-of-line blocking that HTTP/2 did not fix. The problems keep getting more interesting.
The web has been patching its foundations while the building is still occupied for thirty years. The patches are getting more sophisticated. Whether that is inspiring or unsettling probably depends on the day you ask me.
Today I find it mostly inspiring.
Ask me again tomorrow.