HTTP is the language a browser uses to talk to a server, and services use to talk to each other. The meaning of a request has barely changed in decades: GET /orders/42, headers, body, response code. But how those requests are packed and shipped across the network has been rewritten three times. That's where HTTP/1.1, HTTP/2, and HTTP/3 come from — three versions of one protocol, each faster than the last.
The good news: your code almost always stays the same. You learn the versions not to rewrite logic, but to understand why one page loads in jerks and another smoothly, and what that "HTTP/2" checkbox you flip on the balancer actually does. Let's go in order — from the oldest to the newest.
HTTP/1.1: simple, but one at a time
HTTP/1.1 is the version the internet lived on for most of its history. It's text-based: a request is literally lines you can read with your own eyes. Open a connection, send GET /page, get a response. Convenient for debugging, easy to grasp.
The early annoyance of HTTP/1.0 — a new connection for every request — is already solved here by keep-alive: the connection stays open, and you can send several requests over it in a row without re-establishing the link each time. That saves time, because setting up a connection isn't free (more on that in the article on connections).
But there's a fundamental limit. Over a single connection, requests go strictly in turn: you send the first, wait for its response, and only then can you send the next. If the first response is large or the server stalls, everything else queues up behind it. This is head-of-line blocking: one slow item at the front holds up everyone behind it, the way a single stalled car holds up a whole lane.
How did people live with this? Browsers opened several parallel connections to a single site — usually six. While an image downloads over one, a stylesheet flies over another, a script over a third. The workaround works, but it's expensive: each connection is a separate setup, separate memory on the server, separate overhead. Six is the ceiling, and a heavy page has hundreds of resources.
HTTP/2: many streams in one pipe
HTTP/2 was born precisely to kill that queue. The core idea is multiplexing: over a single connection, many independent requests and responses travel at the same time without getting in each other's way.
To make that possible, the protocol was made binary. Instead of text lines, data is chopped into small numbered pieces — frames. Each frame is tagged with the stream it belongs to. The server and client send frames interleaved, and the other side reassembles them by their numbers. Think not of a column of cars on a one-lane road, but of parcels on a shared conveyor belt: they ride mixed together, but each has its own address, and at the exit they're sorted back to their recipients.
What this buys you in practice:
- One connection instead of six. Fewer setups, less load on the server — while the parallelism is higher than it was over six connections.
- Header compression. Every request carries a pile of repeating headers (the same cookies, user-agent, host). HTTP/2 compresses them and doesn't ship the same thing a hundred times over — a noticeable saving where requests are frequent.
- Server push — the server could, without waiting for a request, proactively send the client what it would surely need next (say, a stylesheet for a page). In practice the idea didn't catch on and was gradually retired, but you'll still meet it in descriptions of HTTP/2.
Important: the meaning of a request didn't change. The same methods, headers, response codes — only the "packaging" changed. That's why moving to HTTP/2 usually doesn't require touching the service code.
What HTTP/2 left unsolved
HTTP/2 removed the queue at the level of HTTP itself. But it still lives on top of TCP — the transport that guarantees bytes arrive whole and strictly in order (TCP is covered in the article on TCP and UDP).
And here a trap hides. TCP hands data upward strictly in order. If one packet is lost on the way, TCP stops delivering everything that arrived after it and waits for the missing piece to come again. And in that "everything after" sit frames from different streams. The result: logically the streams are independent, but one lost packet stalls them all — because they all share one TCP connection.
This is head-of-line blocking again, only dropped down to the TCP level. HTTP/2 honestly removed the blocking in its own domain, but couldn't reach into the transport beneath it. On a good network the difference is invisible; on a mobile link with packet loss, HTTP/2 sometimes worked no better than several separate HTTP/1.1 connections.
HTTP/3: swap the foundation under the protocol
To remove the blocking for good, you had to replace the transport itself. That's how HTTP/3 came about — the same binary, multiplexed HTTP, but over QUIC instead of TCP.
QUIC is a transport built on UDP (the fast "send and forget" protocol, with no ordering guarantee) with reliability layered back on top. The key difference: QUIC knows about streams. If a packet of one stream is lost, only that stream suffers — the rest keep going, nobody holds them up. That TCP-level head-of-line blocking disappears, because there's no TCP under the protocol anymore.
As a bonus, QUIC sets up a connection faster. In TCP, first comes the transport handshake, then separately the encryption handshake — two round trips of latency. QUIC folds them into one: encryption is built into it from the start, and the link comes up in fewer exchanges. On a mobile network, where every round trip to the server is felt, this noticeably speeds up the first response.
The price: QUIC rides over UDP, and some older networks and firewalls treat UDP with suspicion. So HTTP/3 usually acts as an acceleration on top: client and server both speak HTTP/2, and switch to HTTP/3 when they can, falling back if QUIC doesn't get through.
Where this applies
For a backend service, the HTTP version isn't an abstraction — it's a set of concrete places where it surfaces.
First and foremost — gRPC runs on top of HTTP/2. Multiplexing and binary frames aren't a bonus for gRPC but a requirement: the many parallel calls and streaming rely on exactly this. If you stand up a gRPC service, HTTP/2 is already on, whether you want it or not.
Second — where the version is turned on. Usually not in the application code. Facing outward is a proxy or a balancer (nginx, Envoy, a cloud load balancer), and HTTP/2 or HTTP/3 is terminated there: the browser talks to the balancer over HTTP/2, and from there to your service it may go over plain HTTP/1.1 — on the short, fast hop inside the data center the difference matters less. Exactly where the connection is broken and reassembled is the topic of the article on load balancers. One more detail: browsers enable HTTP/2 only over encryption, so in practice it comes paired with HTTPS and TLS.
Where beginners stumble:
- They think HTTP/2 changes the meaning of requests. It doesn't. Methods, headers, response codes are the same as in HTTP. Only the on-the-wire packaging changes, and your request handler stays as it was.
- They believe HTTP/2 fully killed head-of-line blocking. It removed it at the HTTP level, but at the TCP level, on packet loss, the blocking remains — only HTTP/3 over QUIC removes it for good.
- They try to "enable HTTP/3 in the application." The version usually lives on the proxy/balancer, not in the service code. That's where to look for the switch.
- They confuse the protocol version with encryption. HTTP/2 and TLS are different things; browsers just require them together. The version governs how data is shipped, TLS governs that it can't be read along the way.
What to learn next
HTTP versions sit on the transport beneath them, so it makes sense to unpack the transport itself: TCP and UDP — which is also where QUIC lives, the thing HTTP/3 rests on. It's useful to understand what the versions actually optimize — setting up and reusing connections: keep-alive, handshakes, the cost of a new connection. Then the application layer itself, HTTP (methods, codes, headers), and the encryption on top of it, HTTPS and TLS, the pair HTTP/2 and HTTP/3 usually work with.