I’m writing this because I was recently debugging another network issue and ended up revisiting the evolution of these HTTP versions. HTTP/1.1 has been in use for more than twenty years, HTTP/2 was standardized in 2015, and HTTP/3 has only spread across major sites in the past two years. They weren’t simply a matter of adding a few fields; the underlying transport model was changing.
This post puts HTTP/1.1, HTTP/2, and HTTP/3 (QUIC) side by side, following one thread: “why it wasn’t enough → how it changed → what problems remained afterward.”
Related Notes
- [[笔记/web/计算机网络/TCP 重传、滑动窗口、流量控制与拥塞控制|TCP Retransmission, Sliding Window, Flow Control, and Congestion Control]]
- [[笔记/web/计算机网络/计算机网络框架|Computer Network Framework]]
- [[笔记/web/计算机网络/HTTP 1.1 与 HTTP 2 协议对比|Comparison of the HTTP/1.1 and HTTP/2 Protocols]]
1. A Brief History of HTTP/1.0
Tim Berners-Lee came up with HTTP in 1989. The original need was simple: let physicists around the world cite one another’s documents. Each document was a file; the browser sent its filename to the server, and the server returned the content.
The core rule of the first official version, HTTP/1.0 (RFC 1945, 1996), was simple: one request, one response, then close the TCP connection.
With 10 images on a page, you needed 10 TCP connections. The TCP handshake cost 1.5 RTTs, and TLS added several more. In the dial-up era, handshakes were the bottleneck.
HTTP/1.0 didn’t solve that problem, but it established an extensible protocol skeleton: methods, status codes, header fields, and the body. That skeleton never changed—HTTP/2 changed “how these things were transmitted,” not “what they meant.”
3. Why Browsers Open Six Connections
Because requests on an HTTP/1.1 connection are serialized, concurrency requires multiple TCP connections.
Chrome defaults to six concurrent connections to the same domain, and Firefox uses around six as well. This isn’t a bug; it’s a necessary compromise from the HTTP/1.1 era.
浏览器 → 域名 example.com
连接 #1: GET /
连接 #2: GET /a.js
连接 #3: GET /b.css
连接 #4: GET /1.png
连接 #5: GET /2.png
连接 #6: GET /3.png
Side effects:
- Each connection runs TCP congestion control independently, with no coordination between connections;
- Each connection also consumes memory, a file descriptor, and a port;
- Front-end engineers invented sprite sheets, CSS/JS bundling, and domain sharding—various hacks for “reducing the number of requests.”
These techniques are no longer necessary after HTTP/2.
5. Binary Framing
HTTP/1.x messages are plain text:
GET /index.html HTTP/1.1\r\n
Host: example.com\r\n
\r\n
The advantage of a text protocol is readability and easy debugging. The cost is slower parsing and more ambiguity (whether a line break is \n, \r\n, or something else).
HTTP/2 switched to binary. Every frame has a fixed 9-byte header:
Length: Number of bytes in the frame payloadType: Frame type (HEADERS, DATA, PRIORITY, etc.)Flags: Flag bits (ENDSTREAM, ENDHEADERS)Stream Identifier: Stream ID
Common frame types:
| Type | Purpose |
|---|---|
| HEADERS | Carries request/response headers |
| DATA | Carries the body |
| PRIORITY | Declares a stream’s priority |
| RST_STREAM | Cancels a stream |
| SETTINGS | Negotiates connection parameters |
| WINDOW_UPDATE | Flow control |
| PING | Heartbeats and RTT measurement |
| GOAWAY | Signals that the connection is about to close |
| PUSH_PROMISE | Announces a server push |
Note that HEADERS and DATA are separate frames. A request can therefore send HEADERS first, then send the body across multiple DATA frames, each of arbitrary length.
7. HPACK Header Compression
HTTP headers are a perennial web-performance headache:
- A typical request’s headers run about 600-800 bytes;
Cookie,User-Agent,Accept, andRefererare almost identical on every request;- HTTP/1.x doesn’t compress headers (gzip compresses the body);
- For a page with 50 requests, the headers alone add up to 30-40 KB.
HTTP/2 introduced HPACK (RFC 7541).
Static and Dynamic Tables
At its core are two tables:
- Static table: 61 common header name/value pairs hard-coded into the spec, such as
:method GET,:scheme https, and:authority example.com. - Dynamic table: A mutable table maintained independently by each side of the connection.
When sending a header:
- In the static table → send only a 1-byte index;
- In the dynamic table → send the index plus the corresponding encoding method;
- In neither → send the literal value and optionally insert it into the dynamic table.
On the first request:
On the second request to the same domain:
Huffman Encoding
Literal values are compressed again with a Huffman encoding tailored to the character frequencies in HTTP headers. Decoding takes O(n) bit operations, making it highly efficient.
Why HPACK Is a Better Fit for HTTPS Than gzip
Why not just compress it with gzip?
Security is the key:
- gzip is context-based compression that relies on repeated patterns;
- attackers can infer plaintext by observing the compressed length—the basis of the CRIME and BREACH attacks;
- HPACK is a predefined, dictionary-index-based encoding, so attackers cannot infer plaintext from the length alone.
HPACK was specifically designed to deliver both compression and security over HTTPS.
9. What HTTP/2 Didn’t Solve: Head-of-Line Blocking at the TCP Layer
HTTP/2 solved head-of-line blocking at the application layer, but not at the transport layer.
Why? HTTP/2’s multiplexing operates at the logical layer, but underneath it is still a single TCP connection. TCP treats data as an ordered byte stream, and the protocol requires:
If any packet is lost, TCP must buffer every subsequent packet until the lost packet has been retransmitted and delivered before the data can be passed to the application above.
So once a TCP packet is lost, every HTTP/2 stream on the connection stalls—even if frames from the other streams have already arrived.
On wired networks with low packet-loss rates, this isn’t a problem. But in mobile networks, satellite networks, and subway trains, HTTP/2’s advantages are severely diminished.
HTTP/2 also left two problems unsolved:
- Handshake overhead: A TCP three-way handshake plus TLS 1.2 takes at least 3 RTTs before the first byte can be sent;
- Connection migration: A TCP connection is bound to a four-tuple (IP + port), so switching networks (Wi-Fi → 4G) breaks it.
To solve these, Google went further and replaced TCP too, implementing QUIC over UDP. That’s HTTP/3.
11. Handshake Comparison: Why HTTP/3 Is 0-RTT
This is HTTP/3’s most underrated improvement. First, let’s compare the handshakes:
HTTP/1.1 + TLS 1.2
HTTP/2 + TLS 1.2
It has the same handshake overhead as HTTP/1.1; the only difference is that it advertises h2 during ALPN negotiation.
HTTP/2 + TLS 1.3
TLS 1.3 cuts the handshake from 2 RTTs to 1 RTT.
HTTP/3 + QUIC
QUIC merges the TCP and TLS handshakes. A 1-RTT handshake plus 0-RTT resumption.
0-RTT isn’t free—it creates a risk of replay attacks, so it is generally used only for idempotent requests (GET, HEAD). Operations involving money (POST, transfers) cannot use 0-RTT.
13. Putting It into Practice: How to Enable HTTP/2 and HTTP/3 in Nginx
HTTP/2
http2 is a parameter of listen, not a separate directive. It must be paired with HTTPS—browsers don’t support cleartext HTTP/2.
HTTP/3
Nginx 1.25.0+ supports QUIC natively (it was previously an experimental module):
Key points:
- Port 443 must listen on both TCP and UDP—HTTP/3 runs over UDP, while older clients use TCP/HTTP/2;
add_header Alt-Svctells browsers that the site supports HTTP/3;reuseportlets multiple worker processes share the same UDP port;- The firewall must allow UDP 443 (many corporate networks allow only TCP 443, making HTTP/3 unavailable on those networks).
Verification
15. References
[1] RFC 1945 - HTTP/1.0. https://datatracker.ietf.org/doc/html/rfc1945
[2] RFC 7230 - HTTP/1.1: Message Syntax and Routing. https://datatracker.ietf.org/doc/html/rfc7230
[3] RFC 7540 - HTTP/2. https://datatracker.ietf.org/doc/html/rfc7540
[4] RFC 7541 - HPACK. https://datatracker.ietf.org/doc/html/rfc7541
[5] RFC 8446 - TLS 1.3. https://datatracker.ietf.org/doc/html/rfc8446
[6] RFC 9000 - QUIC. https://datatracker.ietf.org/doc/html/rfc9000
[7] RFC 9114 - HTTP/3. https://datatracker.ietf.org/doc/html/rfc9114
[8] High Performance Browser Networking. Ilya Grigorik. https://hpbn.co/
[9] Introduction to HTTP/2. Google Developers. https://developers.google.com/web/fundamentals/performance/http2
[10] Chrome Status - HTTP/2 Server Push Removal. https://chromestatus.com/feature/5762410658996224
[11] Nginx HTTP/2 Module. https://nginx.org/en/docs/http/ngx_http_v2_module.html
[12] Illustrated Networking. Xiaolin. https://xiaolincoding.com/network/