sourav khan
githublinkedinrésuméemailrss
© 2026 sourav khan
Writing5

2026

  • Making images and video fast
  • Making web fonts fast
  • What a coding-agent harness actually does
  • HTTP 1 vs HTTP 2 vs HTTP 3
  • How browser rendering works
writing5

2026

  • Making images and video fast
  • Making web fonts fast
  • What a coding-agent harness actually does
  • HTTP 1 vs HTTP 2 vs HTTP 3
  • How browser rendering works
all writing
httpnetworkingperformancequic

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.

July 31, 202611 min read

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.

About the underlined words

This post uses plain words first, with the real name in brackets after — a

round trip

(RTT)
Round-trip time. One full trip from the browser to the server and back. On office wifi that's about 20ms; on a mobile connection it's often 100–200ms.

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.

thirty years, five protocols

http/0.9 · 1991

One line. One file. Goodbye.

  • +GET, and nothing else
  • +no headers
  • +no status codes
what it broke

One connection per file, closed after every reply. A page with ten images paid to open ten connections.

on the wire
→ request
GET /index.html
← response
<html>…</html>
connection closed
1 / 5

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 Host header. Sounds boring. It's what lets many sites share one address, which is what made shared hosting and content delivery networks(CDN)Content delivery network. Copies of your files kept in data centres around the world, so a user in Delhi downloads from Delhi and not from Virginia. 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 blockingA queue where one stuck item at the front holds up everything behind it, even though those items are ready to go. Often written as HOL 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.

eight files, one page load
index.html
main.css
app.js
logo.svg
hero.jpg
font.woff2
chart.js
avatar.png
transferringstuck, waiting for the lost packetpage finishes at 44 units
Six connections, each handling one file at a time. Files seven and eight wait for one to free up. That staircase is the connection limit, not slow internet.

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 shardingSplitting your files across several subdomains purely to get past the browser's six-connections-per-site limit. Standard practice in 2013, a mistake today.. 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 multiplexingSending many requests and replies over one connection at the same time, mixed together, instead of one after another., 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.

Two HTTP/2 features you can ignore

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)Transmission Control Protocol. The layer under HTTP that turns unreliable packets into a reliable, in-order stream of bytes. That in-order promise is exactly what causes the problem here., 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)User Datagram Protocol. The bare-bones alternative to TCP: it fires packets off and promises nothing about order or delivery. QUIC adds those guarantees back itself, which is the point. — 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 handshakeThe back-and-forth two computers do before any real data moves: agreeing to talk, then agreeing on encryption keys. 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)Transport Layer Security. The encryption behind the padlock in your address bar — the S in HTTPS. Its old name was SSL, which people still say., same as everywhere else.
round trips (rtt) before anything loads
HTTP/1.1 + TLS 1.2the 2015 default4 round trips
connect
encrypt
request
HTTP/2 + TLS 1.3most of the web today3 round trips
connect
encrypt
request
HTTP/3 (QUIC)connect and encrypt in one step2 round trips
connect + encrypt
request
HTTP/3, returning visitornothing to set up, just ask1 round trip
request, with data attached
On a fast 20ms link, none of this is worth a paragraph. On a 200ms mobile link it's 800ms of setup before the server has even read your request — against 200ms for HTTP/3.

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.

side by side
 HTTP/1.1HTTP/2HTTP/3
shipped199720152022
runs onTCPTCPQUIC, over UDP
formatplain textbinarybinary
requests at onceone per connection, ~6 openall, on one connectionall, on one connection
one lost packetstalls one connectionstalls everythingstalls one request
head-of-line blockingin HTTPin TCP, underneathgone
header compressionnoneyesyes
setup before you can ask3 round trips2 round trips1, or 0 if you've been before
encryptionoptionaloptional, but always usedbuilt in
survives wifi → mobilenonoyes
best onpatchy links, by accidentfast, stable linkspatchy 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.

status codes, quickly
101Switching Protocolswhat you see when a page opens a WebSocket
103Early Hintsstart downloading these while I build the page — the replacement for server push
200OKthe default success, body included
201Createda POST made something new — send back its URL
204No Contentdone, and there's deliberately nothing to send back — the right answer for a delete
206Partial Contenthere's the chunk you asked for — video scrubbing, resumed downloads
301Moved Permanentlypermanent — cached hard by browsers, so be sure before you ship it
302Foundtemporary; method may change on the retry, which is why 307 exists
304Not Modifiedyour cached copy is still good — nothing sent back, the cheapest reply there is
307 / 308Temporary / Permanent Redirectsame as 302 / 301, but a POST stays a POST
400Bad Requestmalformed — the catch-all when nothing more specific fits
401Unauthorizedactually unauthenticated: who are you? log in and try again
403Forbiddenwe know who you are and you still can't — re-authenticating won't help
404Not Foundno such resource here
405Method Not Allowedthe address exists, but you can't POST to it — say which ones work
409Conflictclashes with what's already there — a duplicate, or an edit based on an old copy
422Unprocessable Contentwe read it fine, we just can't do it — the usual answer to a failed validation
429Too Many Requestsslow down — tell them how long to wait, don't make them guess
500Internal Server Errorsomething threw and nobody caught it — the default for any crash
502Bad Gatewaythe proxy in front reached your real server and got nonsense back
503Service Unavailableoverloaded, or down for maintenance — temporary, worth retrying
504Gateway Timeoutthe proxy waited, your real server never answered in time
The two everyone gets backwards: 401 means we don't know who you are, 403 means we do and the answer is still no. And reach for 422 over a plain 400 when a form fails validation — the request made sense, it just asked for something you can't do.

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-Svc lets 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.

share this post

on this page

share

keep reading

all posts →

August 26, 2026 · 13 min read

Making images and video fast

Images are the heaviest thing on most pages, and the standard advice can make them slower. The format rules, the srcset maths, and the lazy-loading trap.

read

August 26, 2026 · 12 min read

Making web fonts fast

A web font can hide your text for three seconds before a single word renders. What font-display really controls, and why preload is the second fix, not the first.

read