Network Protocols From HTTP/1.1 to HTTP/3

Know What Your Bytes Ride On Before You Optimize It

Network Protocols From HTTP/1.1 to HTTP/3

How HTTP/1.1, HTTP/2, and HTTP/3 actually move bytes — keep-alive, multiplexing, head-of-line blocking, the TLS handshake tax, and where gRPC fits.

9 min read Level 3/5 #system-design#http#http2
What you'll learn
  • Compare HTTP/1.1, HTTP/2, and HTTP/3 connection models
  • Explain head-of-line blocking at the TCP and HTTP layers
  • Reason about TLS handshake cost and when gRPC's HTTP/2 transport pays off

Every request in your system rides on a transport protocol, and the protocol quietly decides how many round trips you pay, how requests interleave, and what happens when one packet is lost. You can’t tune what you don’t understand — so before we talk APIs, let’s get concrete about what HTTP actually does on the wire, version by version.

HTTP/1.1: one request at a time per connection

HTTP/1.1 is a text protocol over a single TCP connection, and its defining limitation is that a connection handles one request/response at a time. You send a request, you wait for the full response, then you send the next.

The big win 1.1 added over 1.0 was keep-alive: reuse the same TCP connection for many requests instead of opening a fresh one each time. That matters because opening a connection isn’t free — it costs a TCP handshake (one round trip) plus, for HTTPS, a TLS handshake (one or two more).

But keep-alive doesn’t make a single connection concurrent. Browsers worked around this by opening ~6 parallel connections per origin. Six connections means six handshakes, six congestion-control state machines, and a hard ceiling on parallelism. Worse, it suffers head-of-line (HOL) blocking: a slow response at the front of a connection holds up everything queued behind it.

HTTP/2: multiplexing over one connection

HTTP/2 keeps the same semantics (methods, headers, status codes) but changes the wire format to binary frames. The key feature is multiplexing: many independent streams share one TCP connection, and their frames interleave.

Network Protocols From HTTP/1.1 to HTTP/3 — architecture diagram

Notice /b can come back before /a — streams are independent at the HTTP layer. HTTP/2 also adds header compression (HPACK) to avoid re-sending bulky cookie/header sets on every request, and lets the server prioritize streams.

The catch: HTTP/2 still runs over TCP, and TCP delivers bytes strictly in order. If one packet is lost, the kernel holds all streams’ data until that packet is retransmitted. So HTTP/2 fixed application-layer HOL blocking but inherited transport-layer HOL blocking from TCP.

HTTP/3: HTTP over QUIC

HTTP/3 swaps the transport entirely. Instead of TCP it uses QUIC, a protocol built on UDP that implements its own streams, reliability, and congestion control in user space. Because QUIC understands streams natively, a lost packet only stalls its own stream — the others keep flowing. That kills transport-layer HOL blocking, the one thing HTTP/2 couldn’t fix.

QUIC also folds TLS into the transport handshake, so connection setup is faster (often 1 round trip, and 0-RTT on resumption), and it supports connection migration — a phone moving from Wi-Fi to cellular keeps the same connection instead of starting over.

PropertyHTTP/1.1HTTP/2HTTP/3
TransportTCPTCPQUIC (UDP)
Concurrency~6 conns/originmultiplexed streamsmultiplexed streams
App-layer HOL blockingYesNoNo
Transport HOL blockingYesYesNo
Header compressionNoHPACKQPACK
HandshakeTCP + TLS separatelyTCP + TLS separatelycombined, 0-RTT capable

The TLS handshake tax

Every HTTPS connection pays a setup cost before any application byte moves: a TCP handshake (1 RTT), then a TLS handshake (1 RTT in TLS 1.3, 2 in TLS 1.2). On a cross-region link where one RTT is ~150ms, that’s 150–450ms of pure setup before your first byte. This is exactly why connection reuse matters so much — and why HTTP/3’s combined, resumable handshake is a real latency win for chatty clients.

gRPC rides on HTTP/2

gRPC is an RPC framework that uses HTTP/2 as its transport and Protocol Buffers as its binary serialization. It leans directly on HTTP/2’s streaming to offer four call styles: unary, server-streaming, client-streaming, and bidirectional streaming. The compact binary framing and multiplexing make it a strong fit for internal service-to-service traffic, where you control both ends and want low overhead. We compare it head-to-head with REST and GraphQL in the next lesson.

The JavaScript angle

Node’s http2 module speaks HTTP/2 natively, and you opt into protocol negotiation rather than getting it for free. With ALPN (Application-Layer Protocol Negotiation) baked into TLS, the client and server agree on the highest version both support during the handshake.

An HTTP/2 server and a multiplexed client in Node script.js
import http2 from 'node:http2';
import { readFileSync } from 'node:fs';

// Server: one secure HTTP/2 endpoint.
const server = http2.createSecureServer({
  key: readFileSync('key.pem'),
  cert: readFileSync('cert.pem'),
});

server.on('stream', (stream, headers) => {
  // Each request is a stream on the SAME connection.
  stream.respond({ ':status': 200, 'content-type': 'application/json' });
  stream.end(JSON.stringify({ path: headers[':path'] }));
});
server.listen(8443);

// Client: open ONE session, fire many concurrent requests over it.
const client = http2.connect('https://localhost:8443');
for (const path of ['/a', '/b', '/c']) {
  const req = client.request({ ':path': path });   // multiplexed stream
  let body = '';
  req.on('data', (chunk) => (body += chunk));
  req.on('end', () => console.log(path, body));
}
▶ Preview: console

Most Node apps still terminate HTTP/2 and HTTP/3 at a reverse proxy (nginx, Caddy, or a cloud load balancer) and speak plain HTTP/1.1 to the Node process behind it. That’s a perfectly good pattern — the proxy gets you modern transport and TLS termination without rewriting your app servers.

With the transport understood, the next question is what shape of API you build on top of it: REST, GraphQL, or gRPC.