HTTP 1.1, HTTP 2 and HTTP 3: Connecting the Three Protocol Evolutions
I wrote this because recently I was troubleshooting a network issue and went through the HTTP protocol evolution again. HTTP/1.1 has been in use for over twenty years, HTTP/2 was standardized in 2015, and HTTP/3 has only become widely adopted in the past couple of years. They're not just about adding a few fields—the underlying transmission model is changing.
This article covers HTTP/1.1, HTTP/2, and HTTP/3 (QUIC) together, following the line of "why it wasn't sufficient → how it was changed → what problems remain after the change."
Related Notes
- [[笔记/web/计算机网络/TCP 重传、滑动窗口、流量控制与拥塞控制]]
- [[笔记/web/计算机网络/计算机网络框架]]
- [[笔记/web/计算机网络/HTTP 1.1 与 HTTP 2 协议对比]]
1. A Brief History of HTTP/0
HTTP was created by Tim Berners-Lee in 1989, with a simple initial goal: to let physicists around the world reference each other's documents. Each document was a file, the browser sent the filename to the server, and the server returned the content.
The first official version, HTTP/1.0 (RFC 1945, 1996), had one core rule: one request, one response, then close the TCP connection.
Browser ── GET /index.html ──> Server
Browser <─ 200 OK + HTML ── Server
Browser ── GET /logo.png ──> Server (new TCP connection)
Browser <─ 200 OK + PNG ── Server
If a page has 10 images, that's 10 TCP connections. TCP handshake is 1.5 RTT, plus TLS adds a few more RTTs. In the dial-up era, handshakes were the bottleneck.
HTTP/1.0 didn't solve this, but it established an extensible protocol skeleton: methods, status codes, header fields, and Body. This skeleton never changed—HTTP/2 modified "how to transmit these things," not "what these things mean."
3. Why Browsers Open 6 Connections
Since HTTP/1.1 connections are serial, concurrency means opening multiple TCP connections.
Chrome defaults to 6 concurrent connections to the same domain, Firefox also around 6. This isn't a bug—it's a necessary compromise from the HTTP/1.1 era.
Browser → domain example.com
Connection #1: GET /
Connection #2: GET /a.js
Connection #3: GET /b.css
Connection #4: GET /1.png
Connection #5: GET /2.png
Connection #6: GET /3.png
Side effects:
- Each connection runs TCP congestion control independently, uncoordinated;
- Each connection consumes memory, file descriptors, and ports;
- Frontend engineers invented tricks like sprites, CSS/JS concatenation, and domain sharding to "reduce request count."
None of these tricks are needed anymore after HTTP/2.
5. Binary Framing
HTTP/1.x messages are text:
GET /index.html HTTP/1.1\r\n
Host: example.com\r\n
\r\n
The benefit of text protocols is readability and easy debugging. The cost is slow parsing and ambiguity (is the newline \n, \r\n, or something else?).
HTTP/2 switched to binary. Each frame has a 9-byte fixed header:
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
Length: frame payload byte countType: frame type (HEADERS, DATA, PRIORITY, etc.)Flags: flags (ENDSTREAM, ENDHEADERS)Stream Identifier: stream ID
Common frame types:
| Type | Purpose |
|---|---|
| HEADERS | Carries request/response headers |
| DATA | Carries Body |
| PRIORITY | Declares stream priority |
| RST_STREAM | Cancels a stream |
| SETTINGS | Negotiates connection parameters |
| WINDOW_UPDATE | Flow control |
| PING | Heartbeat and RTT measurement |
| GOAWAY | Notifies that the connection is closing |
| PUSH_PROMISE | Server push announcement |
Note that HEADERS and DATA are separate frames. So a request can send HEADERS first, then send Body across multiple DATA frames, with each DATA frame of arbitrary length.
7. HPACK Header Compression
HTTP headers are a persistent web performance problem:
- Headers for a normal request are about 600-800 bytes;
- Fields like
Cookie,User-Agent,Accept,Refererare almost identical for every request; - HTTP/1.x doesn't compress headers (gzip compresses body);
- A page with 50 requests means 30-40 KB just for headers.
HTTP/2 introduced HPACK (RFC 7541).
Static Table and Dynamic Table
The core is two tables:
- Static Table: 61 common header name-value pairs hardcoded in the spec, like
:method GET,:scheme https,:authority example.com. - Dynamic Table: A mutable table maintained by each side of the connection.
When sending a header:
- If in static table → send 1-byte index;
- If in dynamic table → send index + match type;
- If in neither → send literal value, optionally inserted into dynamic table.
First send:
:method = GET → Index 2
:scheme = https → Index 7
:authority = example.com → Literal, insert into dynamic table #62
:path = /api/users → Literal, insert into dynamic table #63
user-agent = curl/7.0 → Literal
Second request to same domain:
:method = GET → Index 2 (1 byte)
:scheme = https → Index 7 (1 byte)
:authority = example.com → Index 62 (dynamic table, 1 byte)
:path = /api/posts → Literal
user-agent = curl/7.0 → Literal
Huffman Encoding
Literals are further compressed with Huffman encoding specifically designed for HTTP header character frequency. Decoding is O(n) bit operations, highly performant.
Why HPACK Is Better Than gzip for HTTPS
Someone might ask: why not just use gzip?
The key is security:
- gzip is context-based compression, relying on repeated patterns;
- Attackers can infer plaintext by observing compressed length—this is the CRIME and BREACH attacks;
- HPACK is dictionary index-based predefined encoding, attackers cannot infer plaintext from length.
HPACK was specifically designed to "compress while remaining secure under HTTPS."
9. What HTTP/2 Didn't Solve: TCP Layer Head-of-Line Blocking
HTTP/2 solved application-layer head-of-line blocking, but not transport-layer.
Why? HTTP/2's multiplexing is logical—underneath it's still a single TCP. TCP treats data as an ordered byte stream, and the protocol specifies:
If any packet is lost, TCP must buffer all subsequent packets until the lost packet is retransmitted and delivered before passing data to the upper layer.
So once a TCP packet is lost, all HTTP/2 streams on that connection freeze—even if frames for other streams arrived long ago.
Stream 1: HEADERS → DATA → DATA → [packet lost] → waiting for retransmit
Stream 3: HEADERS → DATA → DATA → [packet arrived] → but stuck in TCP buffer
Stream 5: HEADERS → DATA → DATA → [packet arrived] → but stuck in TCP buffer
On low-packet-loss wired networks this isn't an issue. But on mobile networks, satellite networks, subway tunnels, HTTP/2's advantages are severely weakened.
Two more things HTTP/2 didn't solve:
- Handshake overhead: TCP three-way handshake + TLS 1.2 requires at least 3-RTT before the first byte can be transmitted;
- Connection migration: TCP connection is bound to a four-tuple (IP + port), so switching networks (Wi-Fi → 4G) breaks the connection.
To solve these, Google simply replaced TCP too, building QUIC on top of UDP. This is HTTP/3.
11. Handshake Comparison: Why HTTP/3 Is 0-RTT
This is the most underestimated improvement in HTTP/3. First, a comparison:
HTTP/1.1 + TLS 1.2
TCP handshake: SYN → SYN+ACK → ACK (1 RTT)
TLS handshake: ClientHello → ServerHello+Cert+... → Finished (2 RTT)
HTTP request: GET / (3 RTT before first byte)
HTTP/2 + TLS 1.2
Same handshake overhead as HTTP/1.1, just declares h2 during ALPN negotiation.
HTTP/2 + TLS 1.3
TCP handshake: SYN → SYN+ACK → ACK (1 RTT)
TLS 1.3: ClientHello → ServerHello+Finished+Cert (1 RTT)
HTTP request: GET / (2 RTT)
TLS 1.3 reduced handshake from 2-RTT to 1-RTT.
HTTP/3 + QUIC
QUIC handshake (built-in TLS 1.3): ClientHello → ServerHello+Cert+Finished (1 RTT)
HTTP request: GET / (1 RTT)
If resuming connection (connected before): ClientHello carries Early Data
↓
(0 RTT to send application data)
QUIC merged TCP and TLS handshakes. 1-RTT handshake + 0-RTT resume.
0-RTT isn't free—it carries replay attack risk, so it's typically only used for idempotent requests (GET, HEAD). Operations involving money (POST, transfers) cannot use 0-RTT.
13. Deployment: How to Enable HTTP/2 and HTTP/3 in Nginx
HTTP/2
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
# Performance tuning
http2_max_field_size 16k;
http2_max_header_size 32k;
http2_max_requests 1000;
http2_body_buffer_size 16k;
location / {
root /var/www/html;
}
}
http2 is a parameter for listen, not a separate config item. HTTPS is required—browsers don't support plaintext HTTP/2.
HTTP/3
Nginx 1.25.0+ has native QUIC support (previously experimental):
server {
# Listen on UDP 443 (not TCP)
listen 443 quic reuseport;
# Also listen on TCP 443 as fallback
listen 443 ssl http2;
server_name example.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
# Tell clients HTTP/3 is available
add_header Alt-Svc 'h3=":443"; ma=86400';
location / {
root /var/www/html;
}
}
Key points:
- Port 443 must listen on both TCP and UDP—HTTP/3 uses UDP, legacy clients use TCP/HTTP/2;
add_header Alt-Svclets browsers know this site supports HTTP/3;reuseportlets multiple worker processes share the same UDP port;- Firewall must allow UDP 443 (many corporate networks only allow TCP 443, making HTTP/3 unusable in such environments).
Verification
# HTTP/2
curl -I --http2 https://example.com
# Output: HTTP/2 200
# HTTP/3
curl -I --http3 https://example.com
# Requires curl compiled with quiche or ngtcp2
# Browser
# Chrome DevTools → Network → Protocol column
# h2 means HTTP/2, h3 means HTTP/3
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 Network. Xiaolin. https://xiaolincoding.com/network/