HTTP 1 vs HTTP 2 vs HTTP 3
Switching to HTTP/2 can make mobile slower. The reason sits one layer below HTTP, in a place your server config can't see — and it's also why HTTP/3 isn't the free upgrade everyone assumes.
Turning on HTTP/2 is meant to be a free win. On a patchy mobile connection it can make things slower, with nothing misconfigured and HTTP/2 doing exactly what it says it does.
Each version of HTTP fixed the last one's slow part and hit a new one underneath. That's the whole story, and it's why "which version is fastest" has no single answer.
The short answer
Serve both. Let the browser pick.
# Alt-Svc is the part people forget. Without it the browser
# never learns HTTP/3 is on offer.
listen 443 ssl;
listen 443 quic reuseport;
http2 on;
http3 on;
add_header Alt-Svc 'h3=":443"; ma=86400' always;Which one is faster depends on the network your user is standing in, and you don't get to choose that.
This post uses plain words first, with the real name in brackets after — a
round trip
(RTT)and so on. Hover or tap any underlined word to see what it means. You'll meet the short forms in everyone else's writing, so it's worth knowing both.
Why it happens
Every version of HTTP fixed the last one's slow part and immediately hit a new one underneath. That's the whole story, five times over.
http/0.9 · 1991
One line. One file. Goodbye.
- +GET, and nothing else
- +no headers
- +no status codes
One connection per file, closed after every reply. A page with ten images paid to open ten connections.
The early versions: one file, one connection
The first version was so small it didn't get a version number until later. The browser opened a connection, sent one line, and got a file back:
GET /index.html
No headers. No status codes. No POST. The reply was always HTML, and then the server closed the connection.
So every file on a page paid for its own connection — open it, transfer, close it. A page with ten images did that eleven times.
HTTP/1.0 in 1996 added most of the words we still use: POST, status codes, headers, and Content-Type — the moment HTTP stopped being an HTML delivery system and became a way to move anything. But the connection habit didn't change. Still one file, one connection. And now every request carried a block of headers, sent again in full each time, uncompressed.
HTTP/1.1: keep the connection open
HTTP/1.1 arrived in 1997 and still carries a big share of traffic today. It brought:
- Connections that stay open. A page's files share one connection instead of each paying to set up its own.
- Sending before you know the size. The server can start streaming a page it's still building.
- Real caching.
Cache-Control,ETag, and asking "has this changed?" before downloading again. - The
Hostheader. Sounds boring. It's what lets many sites share one address, which is what made shared hosting and content delivery networks(CDN) possible. - Pipelining. Send the second request without waiting for the first reply.
That last one was meant to be the big win. It wasn't.
The queue problem, or head-of-line blocking
The proper name for what happens next is head-of-line blocking, and it's worth knowing, because it comes back twice more in this post.
Pipelining lets the browser send several requests back to back — but the server has to reply in the order they were asked for. There's no request number on the wire, so order is the only way the browser can tell which reply belongs to which request.
So if the first request is a slow page and the next two are tiny files that finished instantly, those two sit and wait. The line is stuck behind whatever is at the front of it. That's the blocking.
This went badly enough in practice that browsers shipped with pipelining switched off. It was specified, built, then disabled.
What browsers did instead was brute force: open about six connections per site and spread requests across them. Six lanes, each one strictly one-at-a-time.
The workarounds became an industry
Six connections per site meant the obvious hack was more sites. That's why people served images from img1.example.com and img2.example.com — six more connections each. The name for this was domain sharding. The rest of the 2010s toolkit came from the same place: one big sprite image instead of twenty icons, every script glued into one file, CSS pasted inline.
All of it exists to dodge a limit on connections. Remember that — most of it turned harmful later.
HTTP/2: stop making requests wait in line
HTTP/2 landed in 2015, built from Google's SPDY, and changed the format on the wire completely.
It's binary now. HTTP/1.1 is plain text you can type by hand. HTTP/2 sends small packets of data, each one tagged with the request it belongs to. Harder for humans to read, far easier for machines to read without guessing — and that tag is what makes everything else possible.
Replies can arrive in any order. Because every packet says which request it belongs to, replies can come back mixed together over a single connection. Reply three can finish while reply one is still being made. This is multiplexing, and it's the reason HTTP/2 exists. It fixes head-of-line blocking inside HTTP, and it makes the six-connection hack pointless.
Headers get compressed. HTTP/1.1 could compress the page but never the headers, so a hundred requests to the same site sent near-identical cookies a hundred times. HTTP/2 remembers what it already sent and points back at it.
Server push is dead. It let the server send files before the browser asked
for them, but it couldn't tell what the browser already had cached, so it
mostly wasted bandwidth. Chrome switched it off in 2022, Firefox removed it in
2024. Use 103 Early Hints instead. Stream priorities went much the same
way — the original design was built so differently across servers that it was
replaced with a simpler one.
What HTTP/2 didn't fix
Here's the mobile problem.
HTTP/2 stopped requests queueing inside HTTP. But it still runs on TCP(TCP), and TCP hands over data as one strict, in-order stream. TCP has no idea HTTP/2 split that stream into eight separate conversations. It only knows some bytes in the middle went missing, so it holds back everything after them until the missing part is sent again.
One lost packet stalls every request on that connection — including the seven that arrived perfectly and are sitting there, ready. Head-of-line blocking again, one floor down.
And HTTP/1.1's clumsy six-connection approach protects against this by accident. A lost packet stalls one connection and the other five keep working. Flip the packet-loss toggle in the diagram above and switch between the two.
That is the whole regression. On stable office wifi, HTTP/2 wins comfortably. On a patchy mobile connection dropping a couple of percent of packets, one connection carrying everything can be worse than six.
HTTP/3: replace the layer underneath
You can't fix this inside TCP. The in-order guarantee is the problem, and it's baked into every operating system and every piece of network equipment in the world. It isn't going to change.
So HTTP/3 doesn't use TCP. It uses QUIC, which is built on UDP(UDP) — not because UDP is good, but because it's the one thing that reliably gets through the internet's equipment while leaving the hard parts to code that can actually be updated.
QUIC rebuilds what TCP gave you, but per request:
- Requests are independent. A lost packet only stalls the request that lost it. No head-of-line blocking across requests.
- One handshake instead of two. Encryption is part of setting up the connection, not a separate step on top. One round trip, or none at all for a returning visitor.
- Connections survive a network change. A connection is identified by an ID, not by your address — so walking out of wifi onto mobile data doesn't kill your download.
- Always encrypted. Not optional. The encryption itself is TLS(TLS), same as everywhere else.
The twist
This is where "newer is better" falls apart.
HTTP/3 adoption has stalled, and slightly gone backwards. Cloudflare's own data showed HTTP/3 at 28% of traffic in May 2023; their public Radar data showed 21.11% and declining by April 2026, while HTTP/2 climbed to over 51%.
The reason is physics, not politics. Moving this work out of the operating system means your computer's processor now does per-packet work that used to happen in a heavily optimised place. A peer-reviewed paper measured it: the QUIC/HTTP-3 stack lost up to 45.2% of its data rate compared with TCP + TLS + HTTP/2 over fast connections, and the gap widened as bandwidth increased. The cause was traced to receiver-side processing overhead — too many small packets and QUIC's userspace acknowledgements — and it showed up across Chrome, Edge, Firefox and Opera, on desktop and mobile.
So the protocol built to survive bad networks can be the slower choice on a very good one. Which is the same trade every version made: HTTP/2 is tuned for one clean connection and loses when packets drop; HTTP/3 is tuned for dropped packets and pays for it when they don't.
| HTTP/1.1 | HTTP/2 | HTTP/3 | |
|---|---|---|---|
| shipped | 1997 | 2015 | 2022 |
| runs on | TCP | TCP | QUIC, over UDP |
| format | plain text | binary | binary |
| requests at once | one per connection, ~6 open | all, on one connection | all, on one connection |
| one lost packet | stalls one connection | stalls everything | stalls one request |
| head-of-line blocking | in HTTP | in TCP, underneath | gone |
| header compression | none | yes | yes |
| setup before you can ask | 3 round trips | 2 round trips | 1, or 0 if you've been before |
| encryption | optional | optional, but always used | built in |
| survives wifi → mobile | no | no | yes |
| best on | patchy links, by accident | fast, stable links | patchy or mobile links |
Status codes, in one pass
These barely changed across versions. They arrived in HTTP/1.0 and every version since carries the same set. The first digit tells you almost everything:
- 1xx — informational. The server got your request and is still working. The real reply is coming.
- 2xx — success. Received, understood, done.
- 3xx — redirection. It moved, or your cached copy is still fine.
- 4xx — client error. The client asked for something wrong. Sending the same request again gets the same answer.
- 5xx — server error. The request was fine; the server broke. Trying again might work.
That last split is the one that matters day to day: 4xx means the client has to change something, 5xx means the server does. It's what alerts and retry logic are built on.
What people get wrong
- "HTTP/2 makes splitting across domains better." It makes it worse. Splitting exists to get more connections; HTTP/2 wants exactly one, because header compression and priority only work within a single connection. Splitting across domains on HTTP/2 makes a site slower.
- "A slowdown after switching must be a server config problem." Usually nothing in nginx or the load balancer is wrong. The stall is in TCP, one layer below anything HTTP shows you — which is exactly why it hides for weeks.
- "Bundle everything into one file." Right for HTTP/1.1, harmful now. One 400KB bundle is thrown away entirely when one line changes; thirty small files download together fine and cache separately.
- "Turn on HTTP/3 and turn HTTP/2 off." Offer both.
Alt-Svclets the browser move up when it can and fall back when the network blocks UDP — which plenty of office firewalls still do.
Takeaway
Run HTTP/2 and HTTP/3 side by side and let the browser choose, because the honest answer to "which is faster" is that it depends on the network your user is standing in — and if you switched to HTTP/2 and mobile got slower, go look at packet loss, not at your HTTP config.