HTTP/1.1, nền tảng, nhưng có điểm yếu

HTTP/1.1 dùng TCP làm transport và mặc định bật keep-alive, tái sử dụng cùng một TCP connection cho nhiều request thay vì đóng sau mỗi request.

# HTTP/1.0 — mỗi tài nguyên hay đổi TCP mới: handshake + đóng lặp lại → tốn RTT
Client ⇄ Server   [mở TCP] → GET /a → response → [đóng]
                    [mở TCP] → GET /b → response → [đóng]

# HTTP/1.1 — keep-alive: một TCP cho nhiều request, đóng khi idle
Client ⇄ Server   [một TCP] → GET /a → GET /b → GET /c → … → [đóng khi idle timeout]

Vấn đề còn lại: head-of-line blocking HTTP

Trong HTTP/1.1, các request xếp hàng trên một connection. Request B không thể nhận response cho đến khi request A hoàn thành. Trình duyệt thường mở 6 TCP connection song song tới một origin để lách giới hạn này.


HTTPS = HTTP + TLS (không phải giao thức mới)

HTTPS không thay thế HTTP, nó đặt HTTP bên trong một TLS tunnel:

Application (NGINX, Node, app)
│
▼
TLS
│
▼
TCP
│
▼
IP
│
▼
Ethernet

TLS 1.3 giảm handshake xuống 1 RTT (và 0-RTT resumption cho session cũ). Đây là lý do nên ưu tiên TLS 1.3 trên server mới.

Reverse proxy terminate TLS:

Internet
│  HTTPS
▼
LB / NGINX (terminate TLS)
│  HTTP nội bộ  hoặc  HTTPS re-encrypt tới app
▼
App container

Log access của NGINX sẽ ghi request HTTP (sau khi terminate), không còn TLS. Đây là lý do “tại sao log không thấy HTTPS”.


HTTP/2, một TCP, nhiều stream song song

HTTP/2 giữ TCP làm transport nhưng giới thiệu binary framingmultiplexing:

# HTTP/1.1 — thường mở nhiều TCP tới cùng origin (ví dụ 3 asset)
Client → Server   Conn1: GET /style.css
Client → Server   Conn2: GET /script.js
Client → Server   Conn3: GET /image.png

# HTTP/2 — một TCP, nhiều stream song song (multiplex)
Client → Server   Conn1: Stream 1/2/3 — ba GET trên cùng connection

Lợi ích:

  • Giảm số TCP handshake và TLS handshake.
  • Header compression (HPACK) tiết kiệm bandwidth.
  • Stream priority và flow control chi tiết hơn HTTP/1.1.

HTTP/2 Server Push đã chết. Chrome gỡ từ 106 (2022), Firefox cũng đã tắt; Fastly và nhiều CDN bỏ hỗ trợ. Lý do: push không biết client đã cache chưa → thường lãng phí bandwidth. Thay thế: 103 Early Hints với Link: rel=preload, server báo sớm resource cần tải mà không push dữ liệu.

Hạn chế còn lại: TCP head-of-line blocking

Nếu một TCP packet bị mất, toàn bộ stream trên connection đó phải chờ retransmit, dù nhiều stream không liên quan đến packet đó. Đây là điểm mà HTTP/3 xử lý.

CVE-2023-44487, HTTP/2 Rapid Reset

Tháng 10/2023 Cloudflare, Google, AWS công bố lỗ hổng Rapid Reset: attacker mở stream rồi gửi RST_STREAM ngay lập tức, vòng lặp triệu lần/s → server phát sinh tải CPU để xử lý trạng thái stream mà attacker đã huỷ → DDoS layer-7 kỷ lục (hơn 398 triệu RPS tại Google).

Cách giảm thiểu (bắt buộc cập nhật):

  • Nâng phiên bản: NGINX ≥ 1.25.3, Envoy ≥ 1.27.3, Go ≥ 1.21.3, Node.js ≥ 18.18.2, HAProxy ≥ 2.8.3, Kestrel (.NET) đã patch.
  • Giới hạn số stream đồng thời (http2_max_concurrent_streams mặc định giảm, ~100).
  • Theo dõi metric RST_STREAM rate bất thường; WAF/CDN (Cloudflare, AWS Shield) đã có rule mặc định.

Đây là lý do 2025 mọi reverse proxy chạy production phải audit patch-level HTTP/2.


HTTP/3 và QUIC, transport mới

HTTP/3 thay TCP bằng QUIC (thường chạy trên UDP):

HTTP/3
│
▼
QUIC (TLS 1.3 gắn trong QUIC)
│
▼
UDP
│
▼
IP
│
▼
Ethernet

Góc vận hành:

  • Cần firewall/SG mở UDP (cổng 443), nhiều môi trường corporate chặn UDP.
  • CDN (Cloudflare, Fastly) bật HTTP/3 mặc định tại edge; AWS CloudFrontGCP Cloud CDN hỗ trợ nhưng cần bật thủ công trong distribution config. App backend thường vẫn HTTP/1.1 hoặc HTTP/2 nội bộ.
  • Client tự fallback về HTTP/2 nếu QUIC không thông, biết qua header Alt-Svc: h3=":443".

Alt-Svc và fallback khi UDP bị chặn

Client không dùng HTTP/3 ngay lần đầu. Quy trình:

1. Client kết nối HTTP/2 (TCP) bình thường
2. Server trả header: Alt-Svc: h3=":443"; ma=86400
3. Client cache Alt-Svc, lần sau thử QUIC (UDP 443)
4. Nếu QUIC thông → dùng HTTP/3
5. Nếu UDP bị chặn → fallback về HTTP/2 (trong vài giây timeout)

Vấn đề enterprise: nhiều corporate firewall/proxy chặn UDP 443. Kết quả: mỗi lần Alt-Svc hết hạn (ma), client thử QUIC lại → timeout vài giây rồi fallback. Giải pháp: giảm ma (max-age) nếu đối tượng người dùng chủ yếu là enterprise, hoặc chỉ bật H3 cho CDN edge (không bật trên origin).

ALPN, chọn phiên bản trong TLS handshake

Client và server thoả thuận phiên bản HTTP trong TLS ClientHello bằng extension ALPN (Application-Layer Protocol Negotiation):

ClientHello ALPN: ["h2", "http/1.1"]      ← client hỗ trợ HTTP/2 và HTTP/1.1
ServerHello  ALPN: "h2"                    ← server chọn HTTP/2

HTTP/3: ALPN "h3", nhưng ký qua QUIC chứ không qua TLS trên TCP.

Khi debug curl --http2 -v ... thấy dòng ALPN: offers h2, ALPN: server accepted h2 là tốt. Nếu server chỉ hỗ trợ http/1.1 thì client HTTP/2 sẽ downgrade âm thầm, đôi khi là nguồn của performance regression bất ngờ sau khi đổi LB.

0-RTT và rủi ro replay

QUIC (và TLS 1.3) hỗ trợ 0-RTT: client gửi request kèm luôn dữ liệu ngay trong ClientHello khi đã có session cached → tiết kiệm 1 RTT.

Rủi ro: attacker chặn được gói 0-RTT có thể replay nhiều lần, server không cách nào phân biệt. Quy tắc:

  • Chỉ cho phép idempotent request (GET, HEAD) qua 0-RTT.
  • Application phải anti-replay cho mutation (hoặc disable 0-RTT cho POST/PUT/DELETE).
  • NGINX: ssl_early_data on phải đi kèm header Early-Data: 1 để app biết và xử lý an toàn.

Timeout trong LB và proxy, điểm hay cấu hình sai

Client
│
▼
LB
│
▼
App

Quy tắc: LB timeout ≥ app worst-case response time + buffer.


Tóm tắt

  • HTTPS = HTTP + TLS, cùng giao thức, thêm mã hóa và xác thực.
  • HTTP/2 giải quyết head-of-line ở tầng HTTP, nhưng TCP vẫn là điểm yếu.
  • HTTP/3/QUIC thay TCP bằng UDP+QUIC, giải quyết head-of-line thực sự.
  • Timeout giữa LB và app phải được cấu hình đồng bộ, không khớp là nguồn gốc 502/504 bí ẩn.

Câu hỏi hay gặp

HTTPS + TLS 1.2: khoảng bao nhiêu RTT trước khi có response?

Trả lời: TCP ~1 RTT (handshake) + TLS 1.2 thường ~2 RTT (full handshake) trước khi gửi HTTP request; sau đó còn thời gian xử lý app. Tổng tối thiểu vài RTT trước byte đầu tiên của response (chưa kể DNS nếu chưa cache).

HTTP/2 có hết head-of-line blocking hoàn toàn không?

Trả lời: Hết ở tầng HTTP (nhiều stream trên một connection), nhưng TCP vẫn HOL nếu mất gói, mọi stream trên cùng connection có thể bị chậm theo. HTTP/3/QUIC giảm vấn đề này ở tầng transport.

502 khi tác vụ dài ~45s, app log sạch, kiểm tra gì trước?

Trả lời: Timeout phía proxy/LB/Ingress nhỏ hơn thời gian xử lý (idle/request timeout). So sánh cấu hình NGINX/Ingress/ALB với timeout ứng dụng và upstream.


Bài tiếp theo (Giai đoạn II): TLS, chứng chỉ, chuỗi CA và SNI, đi sâu vào cơ chế trust và lý do ERR_CERT_* xuất hiện.