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;
}

Ảnh chụp đoạn mã nền tối giải thích nginx reverse proxy một cửa trước nhiều backend, reverse proxy đứng trước server thay mặt chúng trả lời Client tới nginx reverse proxy tới backend 1 2 3 client chỉ thấy nginx nginx cân bằng tải TLS cache chặn gom nhiều service sau một tên miền khác forward proxy đứng trước client để đi ra ngoài, cấu hình upstream cộng proxy_pass upstream backend server host 9101 9102 9103 mặc định round-robin có least_conn ip_hash weight, location proxy_pass http backend proxy_set_header X-Forwarded-For proxy_add_x_forwarded_for X-Forwarded-Proto scheme X-Forwarded-Host host, vì sao cần X-Forwarded sau proxy backend thấy IP scheme host của proxy không phải client nginx chuyển tiếp thông tin gốc qua header X-Forwarded-For IP thật của client X-Forwarded-Proto http hay https thiếu chúng log sai IP sinh link sai giao thức lọc IP mất tác dụng

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:

Ảnh chụp bảng kết quả đo thật nền tối chạy nginx cộng 3 backend nginx cổng 9100 proxy tới be1 be2 be3, phần một 6 request qua proxy luân phiên backend BACKEND be3 BACKEND be2 BACKEND be3 BACKEND be1 BACKEND be2 BACKEND be3 mỗi request đẩy sang một backend theo round-robin, phần hai phân phối trên 30 request đều tăm tắp 10 be1 10 be2 10 be3 round-robin chia đúng 10 trên 10 trên 10, phần ba header X-Forwarded backend nhận được XFF bằng 192.168.65.1 IP thật của client không phải IP nginx XFProto bằng http giao thức client dùng tới proxy XFHost bằng 127.0.0.1 tên miền client gõ backend dựng lại đúng ngữ cảnh gốc nhờ các header này

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), hay weight=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:

  1. 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.
  2. Sinh link sai giao thức: backend thấy http (kết nối nội bộ) nên tự sinh URL http://... dù client vào bằng https:// — gây cảnh báo mixed content, hoặc vòng lặp redirect http↔https.
  3. 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ề

  1. Reverse proxy đứng trước backend, thay mặt chúng trả lời: upstream + proxy_pass cho 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ằng least_conn/ip_hash/weight.
  2. 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 đúng X-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.
  3. Cẩn thận khi tin và cấu hình: đừng tin X-Forwarded-For do 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 trong proxy_pass đổi cách xử lý path — kiểm bằng request thật.

Nguồn

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.