Gần như mọi web thật ngày nay không cho client nói chuyện thẳng với server ứng dụng — ở giữa có một reverse proxy, thường là nginx. Nó đứng trước các backend và thay mặt chúng trả lời: phân tải qua nhiều instance, kết thúc TLS (bài trước), cache, chặn lọc, gom nhiều service sau một tên miền. Hiểu reverse proxy là hiểu cách phần lớn hệ thống web được ráp lại. Bài này dựng nginx proxy tới 3 backend thật và đo cách nó phân phối request, cùng cái bẫy header X-Forwarded-*.
Reverse proxy khác forward proxy
Client -> nginx (reverse proxy) -> backend 1 / 2 / 3
- Reverse proxy đứng trước server: client chỉ thấy nginx, không biết có mấy backend phía sau. Nó đại diện cho server.
- Forward proxy (khác hẳn) đứng trước client: đại diện cho client đi ra Internet (ví dụ proxy công ty).
Với reverse proxy, một tên miền có thể ẩn sau nó cả chục service, và nginx quyết định request nào đi backend nào.
upstream backend {
server host:9101; server host:9102; server host:9103;
} # mặc định round-robin; còn least_conn, ip_hash, weight=...
location / {
proxy_pass http://backend;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
}

Hình 1: Reverse proxy đứng trước các backend; upstream + proxy_pass để phân tải; proxy_set_header X-Forwarded-* để chuyển tiếp IP/scheme/host gốc của client cho backend.
Đo thật: round-robin
Dựng nginx với upstream trỏ tới 3 backend (mỗi cái tự xưng danh be1/be2/be3), rồi gửi nhiều request:

Hình 2: Qua reverse proxy, request luân phiên các backend; trên 30 request round-robin chia đúng 10/10/10. Backend nhận X-Forwarded-For (IP client thật), X-Forwarded-Proto (http/https), X-Forwarded-Host (tên miền client gõ).
Kết quả rõ ràng:
- Round-robin đều tăm tắp: 30 request chia đúng 10 be1 / 10 be2 / 10 be3. Đây là thuật toán mặc định của nginx
upstream— lần lượt mỗi backend một request. Muốn đổi:least_conn(đẩy tới backend ít kết nối nhất, hợp khi request dài ngắn khác nhau),ip_hash(cùng IP luôn về một backend, giữ session dính), hayweight=N(backend mạnh nhận nhiều hơn). - X-Forwarded chuyển tiếp ngữ cảnh gốc: backend nhận
X-Forwarded-For=192.168.65.1(IP thật của client),X-Forwarded-Proto=http,X-Forwarded-Host=127.0.0.1. Nếu không có các header này, backend chỉ thấy IP của nginx và scheme của kết nối nội bộ nginx↔backend.
Vì sao X-Forwarded-* quan trọng
Sau khi qua proxy, mọi request tới backend đều từ nginx — cùng một IP, thường là HTTP nội bộ (TLS đã kết thúc ở nginx). Nếu backend không được cho biết ngữ cảnh gốc, ba thứ hỏng:
- Log sai IP: mọi request trông như đến từ một IP (nginx) — không phân biệt được client, không chặn được IP xấu, thống kê vô nghĩa.
- Sinh link sai giao thức: backend thấy
http(kết nối nội bộ) nên tự sinh URLhttp://...dù client vào bằnghttps://— gây cảnh báo mixed content, hoặc vòng lặp redirect http↔https. - Lọc/giới hạn theo IP mất tác dụng: rate limit đếm theo IP sẽ gộp tất cả thành một.
Đây chính xác là lý do file cấu hình của dự án blog này (CLAUDE.md) nhấn mạnh ba header X-Forwarded-* là bắt buộc và app bật server.forward-headers-strategy để đọc chúng — thiếu là bộ lọc IP và rate limit của /api/** mất tác dụng, link email sai giao thức.
Đánh đổi và lưu ý
Đừng tin X-Forwarded-For một cách mù quáng. Client có thể tự đặt header này. Chỉ tin nó khi request đến từ proxy bạn kiểm soát; nginx nên ghi đè (dùng $proxy_add_x_forwarded_for để nối, và ở proxy ngoài cùng cân nhắc dùng real_ip module để lấy đúng IP). Tin header do client gửi = kẻ tấn công giả mạo IP để vượt rate limit.
Health check và failover. nginx open-source cơ bản chỉ có passive health check (đánh dấu backend hỏng sau vài lần lỗi qua max_fails/fail_timeout). Active health check (chủ động ping backend) là tính năng của nginx Plus hoặc phải dùng công cụ khác. Biết giới hạn này khi thiết kế failover.
proxy_pass và dấu / cuối rất nhạy. proxy_pass http://backend và proxy_pass http://backend/ xử lý phần path khác nhau (có/không cắt tiền tố location). Đây là nguồn lỗi 404 kinh điển khi chuyển service — luôn kiểm bằng request thật sau khi đổi.
Ba ý mang về
- Reverse proxy đứng trước backend, thay mặt chúng trả lời:
upstream+proxy_passcho nginx phân tải — đo thật, round-robin chia đúng 10/10/10 trên 30 request; đổi thuật toán bằngleast_conn/ip_hash/weight. X-Forwarded-*chuyển tiếp ngữ cảnh gốc của client (IP thật, http/https, tên miền) cho backend — đo thật backend nhận đúngX-Forwarded-For; thiếu chúng thì log sai IP, sinh link sai giao thức, rate limit theo IP mất tác dụng.- Cẩn thận khi tin và cấu hình: đừng tin
X-Forwarded-Fordo client tự gửi (chỉ tin từ proxy bạn kiểm soát); nginx OSS chỉ có passive health check; và dấu/cuối trongproxy_passđổi cách xử lý path — kiểm bằng request thật.
Nguồn
- nginx docs — Load balancing và ngx_http_proxy_module: https://nginx.org/en/docs/http/load_balancing.html
- MDN — Proxy servers and tunneling / X-Forwarded-For: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
Phần sau — bài cuối sê-ri — ta tối ưu nginx phục vụ nội dung tĩnh: sendfile, gzip, cache header, và đo thông lượng thật bằng ab/curl.