API Gateway Trên VPS: NGINX Rate Limiting + JWT Auth + Load Balancing Microservices 2026

📅 07/2026 · ⏱ 17 phút đọc · 🏷 DevOps

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.

🎯 Bạn sẽ học được gì: (1) Setup NGINX làm API Gateway trong 30 phút, (2) Cấu hình rate limiting chặn bot mà không chặn user thật, (3) Xác thực JWT ngay tại Gateway — backend không cần check auth, (4) Load balancing 3 backend service từ 1 Gateway, (5) 4 gotcha production tôi đã dính và cách tránh.

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:

  1. 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ờ.
  2. 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%.
  3. 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.
  4. 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;
}
⚙️ Giải thích upstream: 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.

⚠️ Bẫy #1: Dùng $binary_remote_addr làm key. Nếu bạn có mobile app, hàng trăm user có thể share chung IP NAT của nhà mạng (đặc biệt Viettel, Mobifone dùng CGNAT). Rate limit 10r/s theo IP sẽ chặn nhầm user thật. Giải pháp: thêm zone thứ hai dùng $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;
}
⚙️ Tham số burst: Khi vượt rate, request được xếp hàng đợi thay vì từ chối ngay. 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.
⚠️ Bẫy #2: Dùng limit_req không có burst. Không có burst, chỉ cần vượt rate đúng 1 request là user bị 429 ngay. Tôi từng setup 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 });
});
⚠️ Bẫy #3: Backend nhận headers từ NGINX nhưng vẫn để open port. Nếu attacker scan được port 3001, họ có thể gọi trực tiếp backend với headers giả mạo 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:

AlgorithmCấu hìnhPhù hợpNhược điểm
Round Robin (mặc định)Không cần tham sốBackend đồng đều về tài nguyênKhông tính đến tải thực tế
Least Connectionsleast_conn;Backend xử lý job nặng, thời gian khác nhauCần shared state nếu dùng sticky session
IP Haship_hash;Cần session stickiness (web socket, long-polling)Phân phối không đều nếu user từ 1 IP range
Weightedweight=NBackend 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 VPSAWS API GatewayKong/Traefik
Chi phí/tháng80K (VPS 2GB)$3.5/1M requests + data transfer80K VPS + maintain
Độ phức tạp setupThấp — 1 file configTrung bình — IAM, Lambda, mappingCao — DB, plugin, dashboard
Rate limitingCó, 3 tầngCó, usage plan + API keyCó, plugin phong phú
JWT authCó, built-inCó, Cognito/Lambda authorizerCó, plugin
WebSocketCó (NGINX 1.3+)
Latency thêm~1ms~5-15ms (cold start Lambda có thể 100ms+)~2-5ms
Phù hợpDự án nhỏ-vừa, <1M req/ngàyEnterprise, serverless, scale globalMicroservices 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

# 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ô APIRequests/ngàyRAMBandwidthGói TrumVPSGiá
Nhỏ (demo, cá nhân)~10K1GB1TBVPS Cơ Bản40K
Vừa (startup, ~100 user)~100K-500K2GB2TBVPS Phổ Thông80K
Lớn (SaaS, ~1000 user)~1M-5M4GB4TBVPS Cao Cấp160K
Enterprise (API public)5M+8GB+UnmeteredVPS Pro320K

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 Ngay

Hoặc tham khảo Netihot — hosting giá rẻ cho website nhỏ