API Gateway Trên VPS: NGINX Rate Limiting + JWT Auth + Load Balancing Microservices 2026
Tháng 3 năm ngoái, tôi deploy một API đơn giản cho app mobile — Node.js Express, database PostgreSQL, chạy trên một VPS 80K/tháng. Tuần đầu mọi thứ ngon lành. Tuần thứ hai, API bắt đầu lag. Tuần thứ ba, database sập vì bị bot scrapers quét API /products với tốc độ 500 requests/giây từ IP Trung Quốc. Tôi mở CloudWatch lên nhìn: 1.2 TRIỆU requests trong 24 giờ. Bill bandwidth vượt gói. App người dùng thật không vào được.
Chỉ mất 2 tiếng fix với NGINX làm API Gateway. Từ đó tôi KHÔNG BAO GIỜ expose trực tiếp backend ra internet nữa. Bài này chia sẻ toàn bộ setup tôi đang chạy production: API Gateway bằng NGINX — rate limiting, JWT validation, load balancing, CORS, caching. Tất cả chạy trên 1 VPS 2GB RAM, tổng chi phí 80K/tháng.
Tại Sao Cần API Gateway?
Hầu hết developer khi mới deploy API đều làm kiểu này: Backend Node.js/Python/Go listen port 3000, mở firewall cho port 3000, trỏ domain vào, xong. Đây là công thức cho thảm họa:
- Không rate limiting: Một script curl đơn giản có thể quét toàn bộ database của bạn trong vài giờ.
- Backend phải xử lý mọi thứ: Auth, CORS, compression, static files — tất cả đè lên app server. Node.js single-threaded mà phải check JWT mỗi request thì throughput giảm 40-60%.
- Không có single entry point: Sau này bạn tách thành 3-4 microservices (auth service, product service, order service), mỗi service một port. Client phải biết gọi service nào ở port nào? Lộn xộn.
- Không che giấu được kiến trúc bên trong: Attacker chỉ cần scan port là biết bạn đang chạy Express (port 3000), Flask (5000), hay Go (8080).
API Gateway giải quyết tất cả: MỘT entry point (port 443), NGINX nhận request, check rate limit, validate JWT, rồi proxy_pass sang service phù hợp. Backend chỉ tập trung business logic.
Kiến Trúc API Gateway Trên 1 VPS
Đây là kiến trúc tôi đang chạy cho một SaaS nhỏ, tất cả trên cùng 1 VPS 2GB RAM:
Internet → NGINX (port 443, SSL terminate)
├── /api/auth/* → Auth Service (Node.js, port 3001)
├── /api/products/* → Product Service (Go, port 3002)
├── /api/orders/* → Order Service (Python, port 3003)
└── /api/health → NGINX trả luôn (stub_status)
NGINX làm SSL termination, rate limiting, JWT validation, CORS handling, gzip compression. Backend services chỉ nhận request đã được xác thực và đúng format — code backend sạch hơn hẳn.
Bước 1: Cài NGINX + Cấu Hình Cơ Bản
# Ubuntu/Debian
apt update && apt install nginx -y
# Kiểm tra
nginx -v
# nginx version: nginx/1.24.0
Tạo file cấu hình API Gateway:
# /etc/nginx/sites-available/api-gateway
upstream auth_backend {
server 127.0.0.1:3001 weight=3 max_fails=2 fail_timeout=30s;
server 127.0.0.1:3002 weight=1 backup; # fallback
keepalive 32;
}
upstream product_backend {
server 127.0.0.1:3003;
keepalive 64;
}
upstream order_backend {
server 127.0.0.1:3004;
server 127.0.0.1:3005; # 2 instances
keepalive 32;
}
server {
listen 80;
server_name api.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name api.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# ===== RATE LIMITING =====
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $http_x_api_key zone=key_limit:10m rate=100r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
# ===== JWT VALIDATION =====
auth_jwt "API Gateway";
auth_jwt_key_file /etc/nginx/jwt-public.pem;
# Health check — không cần auth
location = /api/health {
auth_jwt off;
return 200 '{"status":"ok","timestamp":"$time_iso8601"}';
add_header Content-Type application/json;
}
# Auth endpoints — KHÔNG cần JWT (login/register)
location /api/auth/ {
auth_jwt off;
limit_req zone=api_limit burst=5 nodelay;
proxy_pass http://auth_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
}
# Protected endpoints — CẦN JWT
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
limit_conn conn_limit 10;
limit_req zone=key_limit burst=50 nodelay;
proxy_pass http://product_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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-User-ID $jwt_claim_sub;
proxy_set_header X-User-Role $jwt_claim_role;
}
# CORS headers (global)
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-API-Key" always;
if ($request_method = OPTIONS) {
return 204;
}
# Security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}
weight=3 nghĩa là server 3001 nhận 75% traffic, server 3002 nhận 25%. max_fails=2 fail_timeout=30s nghĩa là nếu fail 2 lần trong 30 giây thì NGINX tự ngắt server đó 30 giây. backup chỉ dùng khi server chính chết. keepalive giữ kết nối đến backend — giảm 30% latency so với mở connection mới mỗi request.Bước 2: Rate Limiting — Chặn Bot Không Chặn User
Đây là phần quan trọng nhất. Rate limiting sai là chết: chặn quá tay thì user thật bị 429; nới quá tay thì bot tha hồ quét. Tôi đã phải tune 3 lần mới ổn.
$http_x_api_key — mỗi API key có quota riêng.Cấu hình 3 tầng rate limiting
# Tầng 1: Giới hạn request/second theo IP (chặn bot cấp thấp)
limit_req_zone $binary_remote_addr zone=ip_rps:10m rate=10r/s;
# Tầng 2: Giới hạn request/minute theo IP (chặn scraping dài hạn)
limit_req_zone $binary_remote_addr zone=ip_rpm:10m rate=60r/m;
# Tầng 3: Giới hạn theo API key (cho authenticated user)
limit_req_zone $http_x_api_key zone=key_zone:10m rate=100r/s;
location /api/ {
# Tầng 1 + 2: theo IP
limit_req zone=ip_rps burst=20 nodelay;
limit_req zone=ip_rpm burst=200 nodelay;
# Tầng 3: theo API key (chỉ áp dụng nếu request có X-API-Key)
limit_req zone=key_zone burst=500 nodelay;
proxy_pass http://backend;
}
# Custom error page khi bị rate limit
error_page 503 =429 /rate_limit_error.json;
location = /rate_limit_error.json {
internal;
return 429 '{"error":"rate_limited","retry_after":30}';
add_header Content-Type application/json;
add_header Retry-After 30;
}
nodelay nghĩa là không trì hoãn các request trong queue. Ví dụ: rate=10r/s, burst=20, nodelay — NGINX cho phép tối đa 20 request/s bùng nổ (burst), vượt quá thì 429. Không có nodelay thì các request trong burst bị trễ dần — tệ cho API.rate=5r/s không burst, kết quả: frontend gọi 3 API cùng lúc khi load trang → tổng 6 requests/s, user bị chặn sau 1 giây. Thêm burst=20 là fix.Bước 3: JWT Authentication Ngay Tại Gateway
Đây là tính năng tôi thích nhất: backend KHÔNG cần code auth. NGINX check JWT trước khi proxy_pass.
Generate JWT key pair
# Tạo RSA key pair cho JWT signing
openssl genrsa -out jwt-private.pem 2048
openssl rsa -in jwt-private.pem -pubout -out jwt-public.pem
# Public key cho NGINX (verify)
cp jwt-public.pem /etc/nginx/jwt-public.pem
chmod 644 /etc/nginx/jwt-public.pem
Cấu hình JWT validation trong NGINX
# Global: tất cả endpoint cần JWT
auth_jwt "API Gateway";
auth_jwt_key_file /etc/nginx/jwt-public.pem;
# Exception: endpoint public không cần JWT
location /api/auth/login {
auth_jwt off;
proxy_pass http://auth_backend;
}
location /api/auth/register {
auth_jwt off;
proxy_pass http://auth_backend;
}
# Truyền JWT claims sang backend qua headers
location /api/ {
proxy_set_header X-User-ID $jwt_claim_sub;
proxy_set_header X-User-Email $jwt_claim_email;
proxy_set_header X-User-Role $jwt_claim_role;
proxy_pass http://backend;
}
Backend Node.js nhận user info từ headers — không cần decode JWT lại:
// Backend: KHÔNG CẦN JWT middleware
app.get('/api/me', (req, res) => {
// NGINX đã verify JWT và truyền claims qua headers
const userId = req.headers['x-user-id'];
const userRole = req.headers['x-user-role'];
// Chỉ cần query database với userId
res.json({ id: userId, role: userRole });
});
X-User-ID: 1. Luôn bind backend service vào 127.0.0.1 thay vì 0.0.0.0. Kiểm tra: netstat -tlnp | grep 3001 phải ra 127.0.0.1:3001, không phải 0.0.0.0:3001.Bước 4: Load Balancing Giữa Nhiều Backend Service
Khi bạn có 2-3 instance của cùng một service (scale horizontally), NGINX làm load balancer cực kỳ hiệu quả với 3 algorithm:
| Algorithm | Cấu hình | Phù hợp | Nhược điểm |
|---|---|---|---|
| Round Robin (mặc định) | Không cần tham số | Backend đồng đều về tài nguyên | Không tính đến tải thực tế |
| Least Connections | least_conn; | Backend xử lý job nặng, thời gian khác nhau | Cần shared state nếu dùng sticky session |
| IP Hash | ip_hash; | Cần session stickiness (web socket, long-polling) | Phân phối không đều nếu user từ 1 IP range |
| Weighted | weight=N | Backend cấu hình khác nhau (mạnh/yếu) | Cần biết chính xác tỉ lệ tài nguyên |
# least_conn — phân phối đến server ít connection nhất
upstream backend_pool {
least_conn;
server 127.0.0.1:3001 weight=2; # Server mạnh, nhận gấp đôi
server 127.0.0.1:3002 weight=1; # Server yếu hơn
server 127.0.0.1:3003 weight=1;
keepalive 64;
}
Health Check: Tự động loại backend chết
upstream backend_pool {
server 127.0.0.1:3001 max_fails=3 fail_timeout=60s;
server 127.0.0.1:3002 max_fails=3 fail_timeout=60s;
# NGINX Plus mới có active health check,
# bản free dùng passive: đếm lỗi TCP/HTTP rồi cắt
}
Passive health check hoạt động thế này: Mỗi lần proxy_pass thất bại (timeout, connection refused, 502/504), NGINX đếm +1. Đủ max_fails lần trong fail_timeout giây thì server bị đánh dấu "down" trong fail_timeout giây đó. Đây là lý do keepalive quan trọng — tránh mở connection mới mỗi request, giảm false-positive khi backend đang quá tải tạm thời.
Bước 5: Monitoring API Gateway
Có Gateway là có single point of failure. Nếu NGINX chết, toàn bộ API chết. Nên monitoring là bắt buộc:
# Bật stub_status để monitoring
location = /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1; # Chỉ cho localhost
deny all; # Chặn tất cả IP khác
}
# Kết quả curl http://127.0.0.1/nginx_status:
# Active connections: 291
# server accepts handled requests
# 12457892 12457892 39845612
# Reading: 12 Writing: 89 Waiting: 190
Kết hợp với Prometheus + Grafana để có dashboard real-time. Hoặc đơn giản hơn, dùng Netdata để có dashboard ngay trong 1 lệnh cài.
So Sánh: Tự Build API Gateway vs Dịch Vụ Cloud
| Tiêu chí | NGINX trên VPS | AWS API Gateway | Kong/Traefik |
|---|---|---|---|
| Chi phí/tháng | 80K (VPS 2GB) | $3.5/1M requests + data transfer | 80K VPS + maintain |
| Độ phức tạp setup | Thấp — 1 file config | Trung bình — IAM, Lambda, mapping | Cao — DB, plugin, dashboard |
| Rate limiting | Có, 3 tầng | Có, usage plan + API key | Có, plugin phong phú |
| JWT auth | Có, built-in | Có, Cognito/Lambda authorizer | Có, plugin |
| WebSocket | Có (NGINX 1.3+) | Có | Có |
| Latency thêm | ~1ms | ~5-15ms (cold start Lambda có thể 100ms+) | ~2-5ms |
| Phù hợp | Dự án nhỏ-vừa, <1M req/ngày | Enterprise, serverless, scale global | Microservices lớn, multi-team |
4 Gotcha Production Tôi Đã Dính
Gotcha 1: Rate limit sai key
Đã nói ở trên: dùng $binary_remote_addr cho mobile app → chặn nhầm user chung NAT. Luôn thêm zone theo API key cho authenticated traffic.
Gotcha 2: Không giới hạn body size
Không set client_max_body_size, attacker upload file 1GB → NGINX buffer đầy RAM → OOM kill. Fix:
client_max_body_size 10m; # Cho API thông thường
location /api/upload {
client_max_body_size 100m; # Riêng upload endpoint
}
Gotcha 3: Timeout không đồng bộ
NGINX mặc định proxy_read_timeout 60s. Backend xử lý job ảnh hết 90s → NGINX trả 504 trong khi backend vẫn đang xử lý (và tốn tài nguyên). Fix: set timeout dài hơn cho các endpoint xử lý chậm:
location /api/reports/generate {
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
Gotcha 4: Reload NGINX gây downtime
nginx -s reload thực ra không phải zero-downtime 100%. Worker process cũ chỉ đóng SAU KHI xử lý hết request đang active. Nhưng nếu config sai syntax → NGINX không reload được → config cũ vẫn chạy (may). Luôn test trước:
nginx -t && nginx -s reload
Tối Ưu Performance Cho API Gateway
- worker_processes auto; — Tự động set bằng số CPU core. VPS 2 core = 2 worker.
- worker_connections 2048; — Mỗi worker xử lý 2048 connection đồng thời. Tổng = 2 × 2048 = 4096 connections.
- sendfile on; tcp_nopush on; — Tối ưu gửi static files, giảm syscall.
- gzip on; gzip_types application/json; — Nén JSON response. API response 50KB → ~12KB sau gzip. Tiết kiệm 75% bandwidth.
- proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m; — Cache response cho endpoint ít thay đổi (danh sách sản phẩm, categories).
# Cache API response cho endpoint ít thay đổi
location /api/products/categories {
proxy_cache api_cache;
proxy_cache_valid 200 10m;
proxy_cache_key "$scheme$request_method$host$request_uri";
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://product_backend;
}
Chọn VPS Nào Cho API Gateway?
API Gateway không cần nhiều CPU — NGINX xử lý hàng chục nghìn request/giây trên CPU 1 core. Cái cần là RAM (cho buffer) và bandwidth:
| Quy mô API | Requests/ngày | RAM | Bandwidth | Gói TrumVPS | Giá |
|---|---|---|---|---|---|
| Nhỏ (demo, cá nhân) | ~10K | 1GB | 1TB | VPS Cơ Bản | 40K |
| Vừa (startup, ~100 user) | ~100K-500K | 2GB | 2TB | VPS Phổ Thông | 80K |
| Lớn (SaaS, ~1000 user) | ~1M-5M | 4GB | 4TB | VPS Cao Cấp | 160K |
| Enterprise (API public) | 5M+ | 8GB+ | Unmetered | VPS Pro | 320K |
Nếu bạn mới bắt đầu, gói VPS Phổ Thông 2GB RAM 80K của TrumVPS là đủ chạy API Gateway + 2-3 microservices nhỏ. Tôi đang chạy chính cấu hình này, NGINX xử lý ~300K requests/ngày, CPU load trung bình 15%.
🚀 Sẵn sàng bảo vệ API của bạn?
Thuê VPS 80K/tháng tại TrumVPS — cài NGINX API Gateway trong 30 phút
Thuê VPS NgayHoặc tham khảo Netihot — hosting giá rẻ cho website nhỏ