Nginx đã trở thành lựa chọn hàng đầu cho vai trò reverse proxy trong hầu hết mô hình triển khai hiện đại — từ startup nhỏ đến enterprise lớn. Trong bài viết thực chiến này, bạn sẽ học cách cấu hình Nginx làm reverse proxy bảo mật hoàn chỉnh: SSL/TLS termination, HTTP security headers, rate limiting, caching, upstream health check và xử lý sự cố thực tế trong môi trường production.
Reverse Proxy là gì? Khi nào cần dùng?
Một reverse proxy là máy chủ trung gian đứng trước các backend server (application server, API server, database server…), tiếp nhận toàn bộ request từ client và forward đến backend phù hợp, rồi trả kết quả về cho client.
Khác với forward proxy (client dùng để ẩn danh khi ra internet), reverse proxy bảo vệ và tối ưu hóa phía server:
- Che giấu kiến trúc backend: Client chỉ thấy IP của Nginx, không biết backend là Node.js, Python, PHP hay Java.
- SSL termination: Nginx xử lý mã hóa TLS thay backend, giảm tải CPU cho application server.
- Load balancing: Phân phối traffic đến nhiều backend instance.
- Caching: Cache static/dynamic content, giảm tải backend.
- Bảo mật: WAF cơ bản, rate limiting, block IP xấu.
- Compression: Gzip/Brotli tại reverse proxy thay vì từng backend.
Mô hình kiến trúc điển hình
Internet
│
▼
[Nginx Reverse Proxy :443] ← SSL/TLS termination, Security Headers, Rate Limiting
│
├──► [App Server 1 :3000] (Node.js / Python / PHP)
├──► [App Server 2 :3000]
└──► [Static Files / Cache]
Client chỉ thấy: https://yourdomain.com
Backend ẩn sau: 127.0.0.1:3000 hoặc 10.0.0.x:3000
Cài đặt Nginx
Ubuntu / Debian
sudo apt update
sudo apt install -y nginx
# Kiểm tra version và trạng thái
nginx -v
# Output: nginx version: nginx/1.24.0
sudo systemctl enable nginx
sudo systemctl start nginx
sudo systemctl status nginx
CentOS / RHEL / Rocky Linux
sudo dnf install -y epel-release
sudo dnf install -y nginx
sudo systemctl enable --now nginx
sudo firewall-cmd --permanent --add-service=http --add-service=https
sudo firewall-cmd --reload
Cấu hình Reverse Proxy Cơ Bản
Giả sử bạn có một ứng dụng Node.js đang chạy trên port 3000 tại localhost. Tạo file config:
sudo nano /etc/nginx/sites-available/myapp.conf
upstream backend {
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
# Enable config
sudo ln -s /etc/nginx/sites-available/myapp.conf /etc/nginx/sites-enabled/
sudo nginx -t # Test config
# Output: nginx: configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful
sudo systemctl reload nginx
SSL/TLS Termination với Let’s Encrypt
Đây là bước quan trọng nhất — Nginx đảm nhận toàn bộ mã hóa TLS, backend chỉ cần nhận HTTP thông thường qua mạng nội bộ.
Cài đặt Certbot
sudo apt install -y certbot python3-certbot-nginx
# Lấy certificate tự động (Certbot sẽ sửa nginx config)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Output mẫu:
# Successfully received certificate.
# Certificate is saved at: /etc/letsencrypt/live/yourdomain.com/fullchain.pem
# Key is saved at: /etc/letsencrypt/live/yourdomain.com/privkey.pem
# This certificate expires on 2026-09-04.
Cấu hình SSL tối ưu (thủ công)
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
# SSL Certificates
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# SSL Settings - chỉ dùng TLS 1.2 và 1.3
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/yourdomain.com/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
# SSL Session Cache
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# DH Params (tạo: sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048)
# ssl_dhparam /etc/nginx/dhparam.pem;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
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 https;
}
}
# Redirect HTTP → HTTPS
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$host$request_uri;
}
HTTP Security Headers — Lớp Bảo Vệ Quan Trọng
Security headers là những HTTP response header giúp trình duyệt hiểu cách xử lý nội dung an toàn hơn. Đây là những header bạn phải có trong production:
# Tạo file include riêng để tái sử dụng
sudo nano /etc/nginx/snippets/security-headers.conf
# HSTS - Buộc dùng HTTPS trong 1 năm, kể cả subdomain
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Ngăn clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;
# Ngăn MIME type sniffing
add_header X-Content-Type-Options "nosniff" always;
# Kiểm soát thông tin referrer
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Content Security Policy - điều chỉnh theo ứng dụng của bạn
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self'; frame-ancestors 'self';" always;
# Permissions Policy (thay thế Feature-Policy)
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(self), payment=()" always;
# Ẩn Nginx version
server_tokens off;
Include vào server block:
server {
listen 443 ssl http2;
# ...
include snippets/security-headers.conf;
# ...
}
Kiểm tra headers sau khi apply:
curl -I https://yourdomain.com
# Output mẫu:
# HTTP/2 200
# strict-transport-security: max-age=31536000; includeSubDomains; preload
# x-frame-options: SAMEORIGIN
# x-content-type-options: nosniff
# referrer-policy: strict-origin-when-cross-origin
# content-security-policy: default-src 'self'; ...
Bạn cũng có thể dùng tool online SecurityHeaders.com để kiểm tra điểm bảo mật.
Rate Limiting — Chống DDoS và Brute Force
Rate limiting giới hạn số request mỗi IP trong một khoảng thời gian, giúp chống brute force login, API abuse và DDoS cơ bản.
Cấu hình rate limiting cơ bản
# Trong http block (nginx.conf hoặc conf.d/default.conf)
http {
# Zone "api_limit": 10MB memory, max 10 req/giây mỗi IP
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
# Zone "login_limit": strict hơn cho login endpoint
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;
# Zone "general": cho toàn site
limit_req_zone $binary_remote_addr zone=general:10m rate=30r/s;
}
server {
# ...
# Apply rate limit cho toàn site (burst 20, nodelay)
limit_req zone=general burst=20 nodelay;
limit_req_status 429;
# Strict rate limit cho API
location /api/ {
limit_req zone=api_limit burst=5 nodelay;
proxy_pass http://backend;
}
# Rất strict cho login
location /auth/login {
limit_req zone=login_limit burst=3 nodelay;
proxy_pass http://backend;
}
}
Custom error page cho 429
# Tạo file error page
cat > /var/www/html/429.html << 'EOF'
Too Many Requests
429 - Quá nhiều yêu cầu
Bạn đã gửi quá nhiều request. Vui lòng thử lại sau.
EOF
# Trong server block:
error_page 429 /429.html;
location = /429.html {
root /var/www/html;
internal;
}
Whitelist IP nội bộ
# Bỏ qua rate limit cho IP nội bộ
geo $limit {
default 1;
10.0.0.0/8 0;
192.168.0.0/16 0;
127.0.0.1 0;
}
map $limit $limit_key {
0 "";
1 $binary_remote_addr;
}
limit_req_zone $limit_key zone=general:10m rate=30r/s;
Proxy Caching — Tăng Hiệu Năng Đáng Kể
# Trong http block
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app_cache:10m max_size=1g inactive=60m use_temp_path=off;
server {
# ...
location / {
proxy_pass http://backend;
# Enable cache
proxy_cache app_cache;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
# Thêm header để debug cache
add_header X-Cache-Status $upstream_cache_status;
# HIT = từ cache, MISS = từ backend, BYPASS = bỏ qua cache
}
# Không cache các request POST, có cookie session, có Authorization header
location /api/ {
proxy_pass http://backend;
proxy_no_cache $http_authorization;
proxy_cache_bypass $http_authorization;
}
}
# Kiểm tra cache status
curl -I https://yourdomain.com/api/products
# X-Cache-Status: MISS (lần đầu)
curl -I https://yourdomain.com/api/products
# X-Cache-Status: HIT (lần hai - từ cache)
Upstream Health Check
Nginx open source có passive health check mặc định (đánh dấu upstream fail nếu trả lỗi). Với Nginx Plus hoặc module nginx_upstream_check, bạn có active health check. Dưới đây là cấu hình passive health check tiêu chuẩn:
upstream backend {
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s backup;
keepalive 32;
}
# Giải thích:
# max_fails=3: Sau 3 lần fail liên tiếp → đánh dấu server không khả dụng
# fail_timeout=30s: Server được thử lại sau 30 giây
# backup: Chỉ dùng khi server chính fail
Load Balancing cơ bản
upstream backend {
# Round Robin (mặc định)
server app1.internal:3000 weight=3; # nhận 75% traffic
server app2.internal:3000 weight=1; # nhận 25% traffic
# Hoặc dùng least_conn (ít connection nhất)
# least_conn;
# server app1.internal:3000;
# server app2.internal:3000;
# Hoặc ip_hash (sticky session theo IP)
# ip_hash;
# server app1.internal:3000;
# server app2.internal:3000;
keepalive 64;
}
Logging và Monitoring
Custom log format cho reverse proxy
# Trong http block
log_format proxy_log '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'upstream=$upstream_addr '
'upstream_time=$upstream_response_time '
'request_time=$request_time '
'cache=$upstream_cache_status';
server {
access_log /var/log/nginx/yourdomain.access.log proxy_log;
error_log /var/log/nginx/yourdomain.error.log warn;
}
Phân tích log cơ bản
# Top 10 IP tấn công nhiều nhất
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
# HTTP status codes phân phối
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# Request chậm nhất (>5 giây)
awk '$NF > 5 {print $NF, $7}' /var/log/nginx/yourdomain.access.log | sort -rn | head -20
# Real-time monitoring
tail -f /var/log/nginx/yourdomain.access.log | grep --line-buffered "HTTP/1"
Kết hợp với Prometheus + Grafana
# Cài nginx-prometheus-exporter
docker run -d \
--name nginx-exporter \
-p 9113:9113 \
nginx/nginx-prometheus-exporter:latest \
--nginx.scrape-uri=http://localhost/nginx_status
# Enable stub_status trong nginx
location /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}
Gzip Compression
# Trong http block
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
gzip_min_length 256;
# Kiểm tra
curl -H "Accept-Encoding: gzip" -I https://yourdomain.com
# Content-Encoding: gzip
Lỗi Thường Gặp và Troubleshooting
1. 502 Bad Gateway
# Nguyên nhân: Backend không chạy hoặc không kết nối được
# Kiểm tra backend
curl -v http://127.0.0.1:3000
# Kiểm tra log nginx
tail -50 /var/log/nginx/error.log
# Kiểm tra SELinux (CentOS/RHEL)
setsebool -P httpd_can_network_connect 1
# Kiểm tra kết nối
ss -tlnp | grep :3000
2. 504 Gateway Timeout
# Tăng timeout nếu backend xử lý lâu
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
# Hoặc tìm điểm nghẽn trong backend
# Kiểm tra upstream_response_time trong log
3. WebSocket không hoạt động
# BẮT BUỘC thêm các header này cho WebSocket
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400; # 24h cho long-lived connections
}
4. CORS lỗi khi dùng reverse proxy
location /api/ {
proxy_pass http://backend;
# Xử lý CORS tại Nginx (hoặc để backend tự xử lý)
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://app.yourdomain.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
add_header 'Access-Control-Max-Age' 1728000;
return 204;
}
add_header 'Access-Control-Allow-Origin' 'https://app.yourdomain.com' always;
}
5. Upload file lớn bị reject
# Tăng giới hạn upload
client_max_body_size 100M;
client_body_timeout 120s;
# Trong location upload cụ thể
location /upload {
client_max_body_size 500M;
proxy_pass http://backend;
proxy_request_buffering off; # Stream thẳng lên backend, không buffer
}
6. IP thực của client bị mất
# Nginx phải forward IP thực:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Backend (Node.js) phải trust proxy:
app.set('trust proxy', 1); // Express.js
# Backend (Python/Flask):
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)
Config Hoàn Chỉnh — Production Ready
Dưới đây là file config tổng hợp đầy đủ cho một ứng dụng web production:
# /etc/nginx/sites-available/myapp.conf
upstream backend {
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s backup;
keepalive 32;
}
# HTTP → HTTPS redirect
server {
listen 80;
listen [::]:80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://yourdomain.com$request_uri;
}
# www → non-www
server {
listen 443 ssl http2;
server_name www.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
return 301 https://yourdomain.com$request_uri;
}
# Main server
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name yourdomain.com;
# SSL
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/yourdomain.com/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;
# Security Headers
include snippets/security-headers.conf;
# Logging
access_log /var/log/nginx/yourdomain.access.log proxy_log;
error_log /var/log/nginx/yourdomain.error.log warn;
# File upload size
client_max_body_size 50M;
# Gzip
gzip on;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
# Static files trực tiếp từ disk (nếu có)
location /static/ {
alias /var/www/myapp/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
# API với rate limit strict
location /api/ {
limit_req zone=api_limit burst=10 nodelay;
proxy_pass http://backend;
proxy_http_version 1.1;
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 https;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
}
# WebSocket endpoint
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400;
}
# Main app
location / {
limit_req zone=general burst=20 nodelay;
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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 https;
proxy_cache_bypass $http_upgrade;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
}
}
Checklist Nghiệm Thu
Trước khi đưa vào production, kiểm tra đầy đủ các mục sau:
- ✅
nginx -tkhông có lỗi syntax - ✅ HTTPS hoạt động, HTTP tự redirect sang HTTPS
- ✅ Certificate hợp lệ:
echo | openssl s_client -connect yourdomain.com:443 2>/dev/null | openssl x509 -noout -dates - ✅ TLS 1.0/1.1 bị tắt:
nmap --script ssl-enum-ciphers -p 443 yourdomain.com - ✅ Security headers đầy đủ:
curl -I https://yourdomain.com - ✅ Score A+ trên SecurityHeaders.com
- ✅ Rate limiting hoạt động: test bằng
ab -n 100 -c 20 https://yourdomain.com/ - ✅ Nginx version ẩn trong response headers
- ✅ X-Real-IP truyền đúng sang backend
- ✅ Log format ghi đủ thông tin upstream
- ✅ Auto-renew SSL:
sudo certbot renew --dry-run - ✅ Cron job renew certificate:
0 12 * * * /usr/bin/certbot renew --quiet - ✅ Firewall chỉ mở port 80, 443 (không expose port backend ra ngoài)
- ✅ fail2ban cài đặt và chạy
Lab Thực Hành
Sau khi đọc lý thuyết, hãy thực hành theo các bài lab sau:
Lab 1: Reverse proxy cho Node.js app
# 1. Tạo app Node.js đơn giản
mkdir /tmp/testapp && cd /tmp/testapp
cat > app.js << 'EOF'
const http = require('http');
http.createServer((req, res) => {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(`Hello from backend! Path: ${req.url}\nHeaders: ${JSON.stringify(req.headers, null, 2)}`);
}).listen(3000, '127.0.0.1');
console.log('Backend running on :3000');
EOF
node app.js &
# 2. Cấu hình Nginx reverse proxy (dùng localhost thay domain)
# Thêm vào /etc/hosts: 127.0.0.1 myapp.local
# 3. Test
curl http://myapp.local
# Verify X-Real-IP có trong output headers
Lab 2: Test rate limiting
# Cài ApacheBench
sudo apt install apache2-utils
# Gửi 50 request, concurrency 10
ab -n 50 -c 10 https://yourdomain.com/api/test
# Xem có 429 responses không
# Kiểm tra log:
grep "429" /var/log/nginx/yourdomain.access.log | wc -l
Lab 3: Kiểm tra bảo mật SSL
# SSL Labs test (online)
# Mở https://www.ssllabs.com/ssltest/analyze.html?d=yourdomain.com
# Hoặc dùng testssl.sh (local)
git clone https://github.com/drwetter/testssl.sh
cd testssl.sh
./testssl.sh --fast yourdomain.com
# Mục tiêu: Grade A hoặc A+
Tổng Kết
Cấu hình Nginx làm reverse proxy bảo mật không chỉ là đặt proxy_pass — đó là một lớp kiến trúc quan trọng bảo vệ toàn bộ hạ tầng backend của bạn. Những điểm mấu chốt cần nhớ:
- SSL termination tại Nginx giúp backend đơn giản hơn và tăng hiệu năng
- Security headers là lớp bảo vệ miễn phí nhưng cực kỳ hiệu quả
- Rate limiting là bắt buộc cho bất kỳ endpoint public nào
- Log đầy đủ với upstream info giúp debug và monitor hiệu quả
- Test đầy đủ bằng SSL Labs và SecurityHeaders.com trước khi production
Bài tiếp theo trong series: Cấu hình fail2ban kết hợp Nginx để tự động block IP tấn công. Theo dõi SysAdminSkills.com để không bỏ lỡ!
