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ính | Nginx 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òng | proxy_pass gửi request tới upstream backend |
| Phòng ban không biết khách đến từ đâu | Backend chỉ thấy Nginx, không thấy IP client thật |
| Lễ tân ghi sổ khách | Nginx ghi access.log |
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).
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.
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:
| Header | Nội dung | Vì sao backend cần |
|---|---|---|
X-Real-IP | IP của client ngay trước Nginx | Logging, ban IP, geo-restriction |
X-Forwarded-For | Chuỗi IP qua các proxy: client, proxy1, proxy2 | Audit trail, rate-limit |
X-Forwarded-Proto | http hoặc https | Tạo redirect URL đúng scheme |
Host | Domain 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 JSON7. 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:
upstreamblock 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-ProtovàHostheader ảnh hưởng đến CORS origin check và cookieSecureflag 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
ngx_http_proxy_module— toàn bộ directiveproxy_pass,proxy_set_header,proxy_cache, timeout. - Nginx
ngx_http_limit_req_module—limit_req_zone,burst,nodelay. - Nginx
ngx_http_upstream_module—upstreamblock, weight,max_fails,fail_timeout. - Nginx
ngx_http_realip_module— cách trust IP từ proxy trên/dưới.
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;upstreamblock 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_zonegiớ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-Forgiả mạo, quênHostheader, backend tạo redirecthttp://, trailing slash trongproxy_pass.
12. Tự kiểm tra
Q1Reverse proxy khác forward proxy thế nào? Ai đặt proxy ở đây?▸
Q2TLS termination tại Nginx nghĩa là gì? Backend nhận được giao thức gì?▸
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.Q3Vì sao phải set `proxy_set_header Host $host` khi dùng proxy_pass?▸
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.Q4Tại sao `X-Forwarded-For` có thể bị giả mạo và cách phòng?▸
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ó.Q5Nginx có thể phục vụ file tĩnh mà không cần backend không? Lợi ích gì?▸
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_static và expires để 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.Q6Bạn thấy lỗi `connect() failed (111: Connection refused) while connecting to upstream` trong error.log. Nguyên nhân gì?▸
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.Q7Trailing slash trong `proxy_pass http://backend/` so với `proxy_pass http://backend` khác nhau thế nào?▸
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
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