TCP, HTTP & Web cho Backend/Reverse proxy với Nginx — cửa ngõ trước backend
19/29
Bài 19 / 29~20 phútHTTP & WebMiễn phí lượt xem

Reverse proxy với Nginx — cửa ngõ trước backend

TLS termination, routing/upstream, rate limiting, static serving, header forwarding. Vì sao client chỉ thấy proxy và app backend không cần quan tâm tới HTTPS.

TL;DR: Reverse proxy là một server đứng trước backend app: client gửi request tới proxy, proxy chuyển tiếp vào backend rồi trả kết quả về. Client không bao giờ kết nối thẳng vào app. Nginx đảm nhiệm vai trò này trong hầu hết stack web hiện đại — xử lý TLS (backend chạy HTTP thuần nội bộ), định tuyến nhiều service theo path, giới hạn tốc độ request, phục vụ file tĩnh, và ghi log. Hiểu Nginx giúp bạn đọc được config thật, debug header sai, và thiết kế luồng request từ internet tới app đúng chỗ.

bài 07 — caching, CDN & proxy bạn đã thấy sự khác biệt giữa forward proxy (phía client) và reverse proxy (phía server). Bài này đào sâu vào reverse proxy với Nginx — component bạn sẽ gặp trong mọi deployment production.

1. Analogy — Lễ tân toà nhà

Hình dung một toà nhà văn phòng: khách đến không được vào thẳng các phòng ban mà phải qua quầy lễ tân. Lễ tân kiểm tra danh tính, hỏi khách cần gặp ai, rồi gọi điện vào, dẫn khách tới đúng phòng hoặc trả lời thay nếu đơn giản. Khách không biết toà nhà có bao nhiêu phòng, phòng nào ở tầng mấy.

Nginx là "lễ tân" đó trong kiến trúc web. Client (trình duyệt, app mobile) chỉ thấy Nginx tại một địa chỉ duy nhất; các backend service ẩn phía sau — khác port, khác container, khác máy.

Toà nhàWeb stack
Lễ tân kiểm tra danh tínhNginx xác thực TLS certificate (HTTPS)
Lễ tân hỏi "gặp phòng nào?"Nginx đọc URL path, quyết định forward tới service nào
Lễ tân dẫn khách đúng phòngproxy_pass gửi request tới upstream backend
Phòng ban không biết khách đến từ đâuBackend chỉ thấy Nginx, không thấy IP client thật
Lễ tân ghi sổ kháchNginx ghi access.log
Client
Client
HTTPS :443
Nginx
Nginx
HTTP :8080
Backend
Backend app

Client thấy HTTPS/443 — backend chỉ chạy HTTP thuần nội bộ

2. TLS termination — Nginx giải mã HTTPS thay backend

TLS termination là kỹ thuật để Nginx xử lý toàn bộ mã hoá/giải mã TLS (tức HTTPS), còn backend app chỉ nhận request HTTP thuần trên mạng nội bộ. "Terminate" có nghĩa là kết nối TLS kết thúc tại Nginx — không kéo dài vào backend.

Vì sao làm vậy?

  • Đơn giản hoá backend: app Node.js, Java, Python không cần lo TLS certificate, renewal, cipher suite — chỉ viết HTTP handler.
  • Tập trung certificate management: toàn bộ cert (và logic gia hạn Let's Encrypt) ở một chỗ.
  • Hiệu năng: Nginx tối ưu TLS handshake (session resumption, OCSP stapling) tốt hơn mọi app framework.
  • Kết nối nội bộ an toàn đủ: máy Nginx và backend thường cùng datacenter/VPC — HTTP thuần đủ, không cần encrypt nội bộ (trừ compliance yêu cầu).
⚠️ Hệ quả: backend tưởng mình đang chạy HTTP

Sau TLS termination, backend nhận X-Forwarded-Proto: https trong header — nhưng bản thân nó đang lắng nghe HTTP. Nếu app tạo redirect URL mà không đọc header này, URL redirect sẽ bắt đầu bằng http:// thay vì https://. Framework như Spring Boot có property server.forward-headers-strategy=native để tự động tin header forwarding.

3. Routing và upstream — định tuyến request theo path

Nginx quyết định gửi request đến đâu dựa trên location block — mỗi block khớp một pattern URL. Backend được khai báo trong upstream block (tên logic cho một hoặc nhiều server).

# nginx.conf toi gian -- reverse proxy co TLS termination
upstream backend_app {
    server 127.0.0.1:8080;
}

server {
    listen 443 ssl;
    server_name api.example.com;

    ssl_certificate     /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;

    # API requests -> backend app
    location /api/ {
        proxy_pass         http://backend_app;
        proxy_set_header   Host              $host;
        proxy_set_header   X-Real-IP         $remote_addr;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }

    # Static files -- Nginx phuc vu truc tiep, khong qua backend
    location /static/ {
        root /var/www;
        gzip_static on;
        expires 30d;
    }

    # Default -- fallback toi frontend SPA
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
    }
}

# Redirect HTTP -> HTTPS
server {
    listen 80;
    server_name api.example.com;
    return 301 https://$host$request_uri;
}

Nginx khớp location theo độ cụ thể giảm dần: location /api/ khớp trước location / khi URL bắt đầu bằng /api/. proxy_pass gửi request tới upstream tương ứng.

📌 upstream block và load balancing

Khi upstream có nhiều dòng server, Nginx tự động phân tải theo round-robin. Đây là nền tảng của load balancing — được đào sâu ở bài 09 — Load balancer L4 vs L7.

4. Header forwarding — backend cần biết IP thật của client

Khi Nginx chuyển request, phía backend chỉ thấy Nginx kết nối vào — mất thông tin về client thật. Để app có thể logging, rate-limit theo IP, hoặc check https scheme, Nginx phải forward thông tin đó qua HTTP header:

HeaderNội dungVì sao backend cần
X-Real-IPIP của client ngay trước NginxLogging, ban IP, geo-restriction
X-Forwarded-ForChuỗi IP qua các proxy: client, proxy1, proxy2Audit trail, rate-limit
X-Forwarded-Protohttp hoặc httpsTạo redirect URL đúng scheme
HostDomain client gõ vào (api.example.com)Virtual hosting, URL generation
proxy_set_header X-Real-IP         $remote_addr;
proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host              $host;

$proxy_add_x_forwarded_for là biến Nginx tự build: nó lấy giá trị X-Forwarded-For đang có (nếu client đã qua proxy nào đó) và nối thêm $remote_addr vào cuối — tạo ra chuỗi IP đầy đủ.

5. Rate limiting — giới hạn tốc độ request

Rate limiting là kỹ thuật giới hạn số lượng request một client có thể gửi trong một khoảng thời gian, nhằm bảo vệ backend khỏi bị quá tải hoặc bị tấn công brute-force. Nginx thực hiện bằng module ngx_http_limit_req_module:

# Khai bao zone: "one" luu trang thai 10MB, gioi han 10 req/giay theo IP
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

server {
    location /api/ {
        # burst=20: cho phep "burst" toi 20 req vuot rate
        # nodelay: xu ly burst ngay, khong xep hang cho
        limit_req zone=api_limit burst=20 nodelay;
        proxy_pass http://backend_app;
    }
}

Khi client vượt giới hạn, Nginx trả 429 Too Many Requests mà không đụng tới backend.

6. Sơ đồ luồng request

sequenceDiagram
    participant C as Client (browser)
    participant N as Nginx (:443)
    participant B as Backend (:8080)
    C->>N: HTTPS GET /api/users
    Note over N: Giai ma TLS, doc Location block
    N->>B: HTTP GET /api/users (X-Forwarded-For, X-Real-IP, ...)
    B-->>N: HTTP 200 JSON
    Note over N: Ghi access.log
    N-->>C: HTTPS 200 JSON

7. Access log và error log — đọc gì khi debug

Nginx ghi hai loại log theo mặc định:

  • access.log — mỗi request một dòng, ghi method, URI, status code, bytes, user-agent, thời gian xử lý. Đây là nguồn đầu tiên khi cần biết "request đó có tới Nginx không?" và "Nginx trả gì?".
  • error.log — lỗi kết nối tới upstream (backend không lắng nghe, timeout), lỗi config, permission.
# Doc 50 dong cuoi access log
tail -50 /var/log/nginx/access.log

# Loc request tra 5xx (loi upstream)
grep ' 5[0-9][0-9] ' /var/log/nginx/access.log

# Xem loi ket noi toi backend
tail -f /var/log/nginx/error.log | grep upstream

Khi debug, kiểm tra error.log trước: nếu thấy connect() failed (111: Connection refused) while connecting to upstream, backend app chưa chạy hoặc sai port.

8. Liên hệ các bài khác

  • Bài 07 — Caching, CDN & proxy: phân biệt forward proxy (phía client) và reverse proxy (phía server) — bài này đào sâu vào reverse proxy với Nginx.
  • Bài 09 — Load balancer L4 vs L7: upstream block với nhiều server là L7 load balancing — Nginx vừa là reverse proxy vừa là load balancer khi cần.
  • Bài 03 — Cookie, session & CORS: X-Forwarded-ProtoHost header ảnh hưởng đến CORS origin check và cookie Secure flag khi app chạy sau reverse proxy.
  • Module 3 — TLS & Công cụ Debug: TLS termination tại Nginx là điểm đầu — module 3 đào sâu vào TLS handshake, certificate chain, và cách debug bằng openssl s_client.

9. ⚠️ Pitfall — lỗi thường gặp với reverse proxy

Pitfall 1 — X-Forwarded-For bị giả mạo

Client độc hại có thể tự thêm header X-Forwarded-For: 1.2.3.4 trước khi gửi request — Nginx sẽ nối IP giả đó vào đầu chuỗi. Nếu backend lấy phần tử đầu của chuỗi làm "IP thật", nó bị lừa.

# SAI: tin X-Forwarded-For tu client
# -> client gui "X-Forwarded-For: 127.0.0.1" -> bypass IP ban

# DUNG: xoa header tu client, chi set lai tu Nginx
proxy_set_header X-Forwarded-For $remote_addr;
# Hoac dung real_ip_header neu co nhieu tang proxy tin cay:
# set_real_ip_from 10.0.0.0/8;
# real_ip_header X-Forwarded-For;

Chỉ tin X-Forwarded-For khi proxy của bạn set nó — strip header từ client trước.

Pitfall 2 — Quên proxy_set_header Host

Nếu bỏ proxy_set_header Host $host, backend nhận Host: 127.0.0.1:8080 (địa chỉ upstream) thay vì Host: api.example.com (domain client gõ). App dùng Host header để tạo absolute URL (redirect, email link) sẽ tạo URL trỏ vào localhost.

Pitfall 3 — Backend tạo redirect http:// sau TLS termination

Sau TLS termination, backend đang lắng nghe HTTP. Nếu app tạo redirect mà không đọc X-Forwarded-Proto: https, nó redirect về http://api.example.com/new-path — trình duyệt thấy mixed content hoặc redirect loop. Fix: cấu hình framework trust forwarded headers (Spring: ForwardedHeaderFilter; Express: app.set('trust proxy', 1)).

Pitfall 4 — proxy_pass với trailing slash gây double path

# KHAC NHAU:
location /api/ {
    proxy_pass http://backend/;   # /api/users -> /users  (cat /api/)
}
location /api/ {
    proxy_pass http://backend;    # /api/users -> /api/users  (giu nguyen)
}

Trailing slash sau upstream URL trong proxy_pass làm Nginx cắt bỏ prefix location trước khi forward. Thiếu trailing slash thì giữ nguyên toàn bộ path.

10. 📚 Deep Dive — tài liệu gốc

📚 Nginx docs chính thức

Ghi chú: Nginx docs có format ngắn gọn, tra theo directive name. Khi muốn hiểu hành vi proxy_pass với/không có trailing slash, đọc thẳng phần "If the proxy_pass directive is specified with a URI" trong docs proxy module.

11. Tóm tắt

  • Reverse proxy đứng trước backend — client chỉ thấy proxy, không thấy app server thật.
  • TLS termination tại Nginx: backend chạy HTTP thuần, Nginx lo toàn bộ mã hoá/giải mã TLS — đơn giản hoá backend và tập trung certificate management.
  • location + proxy_pass định tuyến request theo URL path tới các upstream service khác nhau; upstream block khai báo một hoặc nhiều server backend.
  • Header forwarding (X-Real-IP, X-Forwarded-For, X-Forwarded-Proto, Host) truyền thông tin client thật vào backend — thiếu chúng app không biết IP thật, scheme thật.
  • Rate limiting với limit_req_zone giới hạn request theo IP, chặn trước backend.
  • Static files Nginx tự phục vụ không cần backend — nhanh hơn và giảm tải.
  • Log: access.log (mọi request) và error.log (lỗi upstream/config) — hai file đầu tiên cần đọc khi debug.
  • Pitfall chính: X-Forwarded-For giả mạo, quên Host header, backend tạo redirect http://, trailing slash trong proxy_pass.

12. Tự kiểm tra

Tự kiểm tra
Q1
Reverse proxy khác forward proxy thế nào? Ai đặt proxy ở đây?
Forward proxy đứng phía client: client cấu hình để request đi qua proxy trước khi ra internet — người dùng hoặc IT đặt. Reverse proxy đứng phía server: client không biết và không cần cấu hình gì, người vận hành server đặt Nginx trước app. Từ góc nhìn client, reverse proxy và server trông giống nhau (cùng domain, cùng IP); sự khác biệt nằm ở ai kiểm soát proxy và proxy bảo vệ ai.
Q2
TLS termination tại Nginx nghĩa là gì? Backend nhận được giao thức gì?
TLS termination có nghĩa là Nginx giải mã kết nối HTTPS của client ngay tại proxy — kết nối TLS kết thúc tại đây, không kéo dài vào backend. Backend nhận request HTTP thuần trên mạng nội bộ (thường cùng máy hoặc cùng VPC). Backend biết request ban đầu là HTTPS bằng cách đọc header X-Forwarded-Proto: https mà Nginx đặt vào. Lợi ích: backend không cần quản lý certificate, Nginx tối ưu TLS tốt hơn mọi app framework.
Q3
Vì sao phải set `proxy_set_header Host $host` khi dùng proxy_pass?
Khi Nginx forward request, mặc định nó đặt Host header bằng địa chỉ upstream (ví dụ 127.0.0.1:8080) thay vì domain client gõ (api.example.com). Nhiều app dùng Host header để tạo absolute URL trong redirect, email xác nhận, hay CORS check — nếu nhận 127.0.0.1:8080 thay vì tên miền thật, các URL đó sẽ sai hoặc vô nghĩa. proxy_set_header Host $host giữ lại domain gốc từ client.
Q4
Tại sao `X-Forwarded-For` có thể bị giả mạo và cách phòng?
Client độc hại có thể tự thêm header X-Forwarded-For: 127.0.0.1 trước khi gửi request. Nginx mặc định nối thêm $remote_addr vào sau — tạo ra 127.0.0.1, 203.x.x.x. Nếu backend lấy phần tử đầu tiên làm "IP thật", nó bị đánh lừa (tưởng request từ localhost). Cách phòng: strip header từ client trước khi Nginx forward, dùng proxy_set_header X-Forwarded-For $remote_addr (chỉ đặt IP của kết nối vào Nginx) hoặc dùng module real_ip với danh sách trusted proxy. Chỉ tin X-Forwarded-For khi proxy của bạn kiểm soát việc set nó.
Q5
Nginx có thể phục vụ file tĩnh mà không cần backend không? Lợi ích gì?
Có — directive root kết hợp location /static/ cho phép Nginx đọc file trực tiếp từ filesystem và trả về client, không qua backend app. Lợi ích: nhanh hơn nhiều (không overhead HTTP proxy round-trip), không chiếm thread/process của app, dễ bật gzip_staticexpires để cache phía browser. Trong production, CSS/JS/image thường được phục vụ thẳng từ Nginx hoặc CDN, chỉ API mới đi qua backend.
Q6
Bạn thấy lỗi `connect() failed (111: Connection refused) while connecting to upstream` trong error.log. Nguyên nhân gì?
Lỗi này có nghĩa Nginx không kết nối được tới backend ở địa chỉ/port đã cấu hình trong proxy_pass hoặc upstream. Nguyên nhân thường gặp: backend app chưa khởi động, app bị crash, sai port trong config Nginx (ví dụ cấu hình :8080 nhưng app đang lắng nghe :8000), hoặc firewall nội bộ chặn kết nối giữa Nginx và backend. Kiểm tra: ss -tlnp | grep 8080 để xem process nào đang listen port 8080.
Q7
Trailing slash trong `proxy_pass http://backend/` so với `proxy_pass http://backend` khác nhau thế nào?
Đây là một trong những điểm dễ nhầm nhất của Nginx. Khi proxy_pass có URI (kể cả trailing slash): proxy_pass http://backend/ — Nginx cắt bỏ phần prefix khớp với location trước khi forward. Ví dụ location /api/ + proxy_pass http://backend/: request /api/users forward thành /users. Khi không có URI: proxy_pass http://backend — Nginx giữ nguyên toàn bộ path gốc, /api/users forward thành /api/users. Nhầm cái này gây 404 hoặc double prefix trong URL backend.

Bài tiếp theo: Load balancer — L4 vs L7, thuật toán phân tải

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên

Bài tiếp theo

Load balancer — L4 vs L7, thuật toán phân tải