Nginx — Tài Liệu Toàn Diện
Kiến trúc, Load Balancing, SSL/TLS, Caching và 4 Use Cases thực tế
Nginx Toàn Diện
Từ cơ bản đến production-ready — kiến trúc, cấu hình, load balancing, SSL/TLS, caching, và 4 use case thực tế đầy đủ cho DevOps Engineer.
Nginx (đọc là "Engine-X") được Igor Sysoev tạo ra năm 2002, ra mắt công khai năm 2004 nhằm giải quyết bài toán C10K Problem — thách thức xử lý 10,000 kết nối đồng thời mà Apache gặp phải. Ngày nay, Nginx là web server được sử dụng rộng rãi nhất thế giới, chiếm ~34% tổng số website toàn cầu.
Kiến Trúc Hoạt Động
Nginx sử dụng asynchronous, event-driven architecture — hoàn toàn khác Apache. Hiểu rõ model này là chìa khóa để cấu hình đúng.
So Sánh Nginx vs Apache
| Tiêu chí | Nginx | Apache |
|---|---|---|
| Kiến trúc | Event-driven, async | Thread/process-based |
| RAM usage (10k conn) | ~150MB | ~1.5GB+ |
| Static file performance | Rất cao ⚡ | Trung bình |
| Dynamic content | Cần proxy pass | mod_php native |
| .htaccess | Không hỗ trợ | Hỗ trợ đầy đủ |
| Config reload | Zero-downtime | Graceful restart |
| Modules | Compile-time | Dynamic (DSO) |
| Use case tốt nhất | High-traffic, microservices | Shared hosting, PHP legacy |
Worker Process Model
Nginx có 1 master process và nhiều worker processes. Master process quản lý config và signal; workers xử lý actual connections.
worker_processes auto; để Nginx tự detect số CPU cores và spawn đúng số workers. Mỗi worker chạy 1 event loop, xử lý hàng nghìn connections đồng thời mà không block.Cấu Trúc nginx.conf
File config của Nginx được tổ chức theo dạng blocks lồng nhau: main → events → http → server → location.
# ═══════════════════════════════════════════════════ # MAIN CONTEXT — Global settings, chạy trước tất cả # ═══════════════════════════════════════════════════ user nginx; # User chạy worker processes worker_processes auto; # 1 worker per CPU core (recommended) error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; # PID file của master process # ═══════════════════════════════════════════════════ # EVENTS CONTEXT — Cấu hình connection handling # ═══════════════════════════════════════════════════ events { worker_connections 1024; # Max connections per worker # Total max connections = worker_processes × worker_connections use epoll; # Linux: epoll (best), macOS: kqueue multi_accept on; # Accept nhiều connections cùng lúc } # ═══════════════════════════════════════════════════ # HTTP CONTEXT — Web server settings # ═══════════════════════════════════════════════════ http { include /etc/nginx/mime.types; default_type application/octet-stream; # Access log format log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; # Dùng sendfile() syscall — hiệu quả hơn read/write tcp_nopush on; # Gửi headers và data cùng một TCP packet tcp_nodelay on; # Disable Nagle algorithm cho WebSockets keepalive_timeout 65; # Keep connection alive 65 giây server_tokens off; # Ẩn version Nginx khỏi response headers # ───────────────────────────────────────────────── # SERVER CONTEXT — Virtual host / site config # ───────────────────────────────────────────────── server { listen 80; server_name example.com www.example.com; root /var/www/html; # ───────────────────────────────────── # LOCATION CONTEXT — URL pattern matching # ───────────────────────────────────── location / { try_files $uri $uri/ =404; } location /api/ { proxy_pass http://backend:3000; } } # Load thêm config từ thư mục conf.d include /etc/nginx/conf.d/*.conf; }
Location Block — Matching Priority
Nginx match location theo thứ tự ưu tiên từ cao đến thấp:
# 1. EXACT MATCH (=) — Ưu tiên cao nhất location = /favicon.ico { return 204; } # 2. PREFERENTIAL PREFIX (^~) — Tìm thấy thì dừng, không thử regex location ^~ /static/ { root /var/www; } # 3. CASE-SENSITIVE REGEX (~) — Theo thứ tự trong file location ~ .php$ { fastcgi_pass php-fpm:9000; } # 4. CASE-INSENSITIVE REGEX (~*) location ~* .(jpg|jpeg|png|gif)$ { expires 30d; } # 5. PREFIX MATCH (không có modifier) — Ưu tiên thấp nhất location /api/ { proxy_pass http://api-backend; }
Các Thuật Toán Load Balancing
Nginx hỗ trợ 6 thuật toán chính — mỗi cái phù hợp với một kiểu workload khác nhau. Hiểu rõ từng thuật toán giúp bạn chọn đúng ngay từ đầu, tránh phải debug sau khi production bị overload.
upstream backend { # Không cần khai báo gì — Round Robin là default server 10.0.0.1:3000; # nhận R1, R4, R7... server 10.0.0.2:3000; # nhận R2, R5, R8... server 10.0.0.3:3000; # nhận R3, R6, R9... }
upstream backend { # weight mặc định = 1 nếu không khai báo # Tổng weight = 6 → tỷ lệ phân phối tương ứng server 10.0.0.1:3000 weight=3; # 3/6 = 50% — server mạnh nhất server 10.0.0.2:3000 weight=2; # 2/6 = 33% server 10.0.0.3:3000 weight=1; # 1/6 = 17% — server yếu nhất }
least_conn)upstream backend { least_conn; # Bắt buộc khai báo tường minh server 10.0.0.1:3000; server 10.0.0.2:3000; server 10.0.0.3:3000; # Kết hợp với weight: server mạnh có "capacity" cao hơn # server 10.0.0.1:3000 weight=2; ← Nginx cân nhắc # → server cần có weight×2 connections để bằng server weight=1 }
ip_hash)upstream backend { ip_hash; # hash dựa trên 3 octet đầu của IPv4: x.x.x.* server 10.0.0.1:3000; server 10.0.0.2:3000; server 10.0.0.3:3000; # Khi cần tạm thời remove server mà không reset sessions: server 10.0.0.4:3000 down; # 'down' thay vì xóa — giữ hash mapping }
$uri, $request_uri, $cookie_sessionid, $arg_id...Use case: Cache locality — cùng URL luôn về cùng 1 server → cache hit rate cao hơn. Thường dùng với Varnish hoặc Memcached backend.
two least_conn — chọn 2 server ngẫu nhiên rồi lấy server ít connection hơn.Use case: Distributed environments với nhiều Nginx instances để tránh coordination overhead.
# ─── Generic Hash — hash theo URI ─────────────────── upstream cache_backends { hash $request_uri consistent; # 'consistent' = ketama consistent hashing # → thêm/bớt server chỉ rehash ~1/N keys thay vì toàn bộ server cache1:11211; server cache2:11211; server cache3:11211; } # ─── Hash theo cookie sessionid ───────────────────── upstream session_backends { hash $cookie_sessionid; # sticky theo session cookie server 10.0.0.1:3000; server 10.0.0.2:3000; } # ─── Random (Nginx Plus / community module) ───────── # upstream random_backends { # random two least_conn; # 2 random → pick least conn # server 10.0.0.1:3000; # server 10.0.0.2:3000; # }
So Sánh Tổng Thể & Hướng Dẫn Chọn Thuật Toán
| Thuật Toán | Khai Báo | Open Source | Ưu Điểm | Nhược Điểm | Best For |
|---|---|---|---|---|---|
| Round Robin DEFAULT | (không cần) |
✓ OSS | Đơn giản, phân phối đều | Không quan tâm server load | Stateless APIs, microservices đồng nhất |
| Weighted RR | weight=N |
✓ OSS | Tận dụng server mạnh hơn | Vẫn không theo real-time load | Mixed hardware, gradual rollout (canary) |
| Least Connections | least_conn; |
✓ OSS | Adaptive, tránh overload server | Overhead tracking; kém hiệu quả với short requests | Long-running connections, WebSockets, streaming |
| IP Hash | ip_hash; |
✓ OSS | Sticky sessions, đơn giản | Mất cân bằng với NAT; server down mất session | Legacy apps không có shared session |
| Generic Hash | hash $key [consistent]; |
✓ OSS | Flexible, cache locality tốt | Có thể mất cân bằng theo key distribution | Cache backends (Memcached, Varnish) |
| Random | random [two]; |
Nginx Plus | Tốt cho multi-LB environments | Cần Nginx Plus; ít predictable | Distributed deployments với nhiều LB nodes |
weight để triển khai Canary Release — đưa version mới cho 10% traffic trước:server app-v1:3000 weight=9; # 90% — stable versionserver app-v2:3000 weight=1; # 10% — canary versionNếu v2 OK → tăng weight dần. Nếu v2 lỗi → set
weight=0 hoặc thêm down.
# ═══════════════════════════════════════════════════ # UPSTREAM — Định nghĩa pool của backend servers # ═══════════════════════════════════════════════════ upstream app_backend { # Load balancing algorithm (chọn 1): # 1. Round Robin (default) — lần lượt # (không cần khai báo gì thêm) # 2. Least Connections — ưu tiên server ít connection nhất # least_conn; # 3. IP Hash — sticky sessions theo client IP # ip_hash; # 4. Weighted Round Robin — server mạnh hơn nhận nhiều hơn server 192.168.1.10:3000 weight=3; # 60% traffic server 192.168.1.11:3000 weight=2; # 40% traffic server 192.168.1.12:3000 weight=1; # 20% traffic # Backup server — chỉ dùng khi tất cả primary down server 192.168.1.20:3000 backup; # Health check parameters # max_fails: số lần fail trước khi mark down # fail_timeout: thời gian mark down server 192.168.1.13:3000 max_fails=3 fail_timeout=30s; # Keepalive connections tới upstream keepalive 32; # Max 32 idle connections per worker } server { listen 80; server_name example.com; location / { proxy_pass http://app_backend; proxy_http_version 1.1; # Headers quan trọng cho upstream 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; # Timeout settings proxy_connect_timeout 5s; # Timeout kết nối tới upstream proxy_send_timeout 60s; # Timeout gửi request lên upstream proxy_read_timeout 60s; # Timeout nhận response từ upstream # Buffer settings proxy_buffer_size 4k; proxy_buffers 8 4k; proxy_busy_buffers_size 8k; } }
# ═══════════════════════════════════════════════════ # HTTP → HTTPS redirect # ═══════════════════════════════════════════════════ server { listen 80; server_name example.com www.example.com; # Redirect tất cả HTTP sang HTTPS với 301 return 301 https://$server_name$request_uri; } # ═══════════════════════════════════════════════════ # HTTPS Server Block # ═══════════════════════════════════════════════════ server { listen 443 ssl http2; # HTTP/2 cần ssl server_name example.com; # ─────────────── Certificate ─────────────── ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # ─────────────── Protocol & Ciphers ──────── ssl_protocols TLSv1.2 TLSv1.3; # Chỉ dùng TLS 1.2+ (tắt TLS 1.0/1.1) ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512; ssl_prefer_server_ciphers on; # Server chọn cipher thay vì client ssl_ecdh_curve secp384r1; # ─────────────── Session Cache ───────────── ssl_session_cache shared:SSL:10m; # Share session cache giữa workers ssl_session_timeout 10m; # Resume session trong 10 phút ssl_session_tickets off; # Tắt tickets vì PFS concerns # ─────────────── OCSP Stapling ───────────── ssl_stapling on; # Server tự fetch OCSP response ssl_stapling_verify on; resolver 8.8.8.8 1.1.1.1 valid=300s; # ─────────────── Security Headers ────────── add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Frame-Options SAMEORIGIN; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; add_header Referrer-Policy "no-referrer-when-downgrade"; location / { proxy_pass http://app_backend; include /etc/nginx/proxy_params; } }
# ═══════════════════════════════════════════════════ # PROXY CACHE — Cache response từ upstream # ═══════════════════════════════════════════════════ http { # Khai báo cache zone (trong http context) # keys_zone=name:size — shared memory cho cache keys # max_size — tổng disk space cho cache # inactive — xóa entries không được access trong 60p proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m max_size=1g inactive=60m use_temp_path=off; # ═══════════════════════════════════════════════════ # GZIP COMPRESSION # ═══════════════════════════════════════════════════ gzip on; gzip_vary on; # Thêm Vary: Accept-Encoding header gzip_proxied any; # Compress cả proxied responses gzip_comp_level 6; # 1-9, level 6 = tốt nhất cost/benefit gzip_min_length 1024; # Không compress file nhỏ hơn 1KB gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss image/svg+xml; server { listen 80; # ─────────────── Proxy Cache Usage ───────── location /api/ { proxy_pass http://api_backend; proxy_cache api_cache; # Dùng zone đã khai báo proxy_cache_valid 200 302 5m; # Cache 200/302 trong 5 phút proxy_cache_valid 404 1m; # Cache 404 trong 1 phút proxy_cache_use_stale error timeout updating; # Serve stale nếu backend lỗi proxy_cache_lock on; # Chỉ 1 request đến backend khi cache miss # Bypass cache theo header (cho testing) proxy_cache_bypass $http_cache_bypass; proxy_no_cache $http_cache_bypass; # Thêm header để debug cache HIT/MISS/BYPASS add_header X-Cache-Status $upstream_cache_status; } # ─────────────── Browser Cache cho Static Files ─ location ~* .(jpg|jpeg|png|gif|ico|css|js|woff2)$ { root /var/www/html; expires 1y; # Cache 1 năm trên browser add_header Cache-Control "public, immutable"; access_log off; # Tắt log cho static files } } }
server { listen 80; # ─────────────── RETURN (nhanh nhất, không process regex) ──── # Dùng cho redirect đơn giản, không cần logic return 301 https://$host$request_uri; # HTTP → HTTPS # return 302 /new-path$request_uri; # Temporary redirect # return 200 "OK"; # Trả về text # return 204; # No content (favicon) # ─────────────── REWRITE (có thể dùng regex) ───────────────── # Syntax: rewrite regex replacement [flag] # Flags: last, break, redirect(302), permanent(301) rewrite ^/old-blog/(.*)$ /blog/$1 permanent; # 301 rewrite ^/user/(d+)$ /profile?id=$1 last; # ─────────────── TRY_FILES ─────────────────────────────────── # Thử từng file, fallback cuối cùng là named location hoặc code location / { # Thử: exact file → directory index → 404 try_files $uri $uri/ =404; } location /spa/ { # Single Page App: fallback về index.html (React/Vue router) try_files $uri $uri/ /spa/index.html; } location /api/ { # Thử file static trước, nếu không có thì proxy lên backend try_files $uri @api_backend; } location @api_backend { proxy_pass http://backend:3000; } # ─────────────── MAP — Dynamic variable ────────────────────── # Dùng trong http{} context để mapping variables }
Map Directive — Dynamic Logic
http { # Map để routing dựa theo subdomain map $host $backend { default backend_prod; ~^api. backend_api; ~^staging. backend_staging; ~^admin. backend_admin; } # Map để limit rate theo loại IP map $remote_addr $limit_key { default $binary_remote_addr; 10.0.0.0/8 ""; # Internal IPs không bị rate limit 172.16.0.0/12 ""; } limit_req_zone $limit_key zone=api_limit:10m rate=10r/s; server { location /api/ { limit_req zone=api_limit burst=20 nodelay; proxy_pass http://$backend; } } }
- worker_processes auto — match CPU cores
- worker_rlimit_nofile — tăng file descriptor limit
- use epoll — Linux event mechanism tốt nhất
- multi_accept on — nhận nhiều conn cùng lúc
- sendfile on — zero-copy file transfer
- tcp_nopush on — batch TCP packets
- keepalive_timeout — reuse connections
- gzip — compress text responses
# SYSTEM LIMITS — /etc/security/limits.conf # nginx soft nofile 65535 # nginx hard nofile 65535 worker_processes auto; # = số CPU cores worker_rlimit_nofile 65535; # Max file descriptors per worker events { worker_connections 4096; # Max conn/worker. Total = workers × 4096 use epoll; multi_accept on; } http { # TCP optimization sendfile on; tcp_nopush on; tcp_nodelay on; # Keepalive keepalive_timeout 30; # 30s idle timeout keepalive_requests 1000; # Max requests per connection reset_timedout_connection on; # Free up memory cho timed-out conn # Buffer tuning client_body_buffer_size 128k; client_max_body_size 50m; # Max upload size client_header_buffer_size 1k; large_client_header_buffers 4 8k; client_body_timeout 10; client_header_timeout 10; send_timeout 10; # Open file cache open_file_cache max=1000 inactive=20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; open_file_cache_errors on; # Hide Nginx version server_tokens off; }
# ═══════════════════════════════════════════════════ # Reverse Proxy — Microservices Gateway # Context: Docker Compose, services communicate by name # ═══════════════════════════════════════════════════ # Định nghĩa upstream cho từng service upstream auth_service { server auth:4001; keepalive 16; } upstream product_service { server product:4002; keepalive 16; } upstream order_service { server order:4003; keepalive 16; } upstream payment_service { server payment:4004; keepalive 8; } upstream notify_service { server notify:4005; keepalive 8; } # Reusable proxy config snippet # (thường đặt trong /etc/nginx/proxy_params) map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { listen 443 ssl http2; server_name api.shop.com; ssl_certificate /etc/ssl/certs/shop.pem; ssl_certificate_key /etc/ssl/private/shop.key; # Common proxy headers — inject vào mọi request 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_http_version 1.1; proxy_set_header Connection ""; # ─── Auth Service ───────────────────────────────── location /auth/ { proxy_pass http://auth_service/; # trailing / strips prefix proxy_read_timeout 30s; } # ─── Product Service ────────────────────────────── location /products/ { proxy_pass http://product_service/; proxy_cache api_cache; proxy_cache_valid 200 2m; add_header X-Cache-Status $upstream_cache_status; } # ─── Order Service ──────────────────────────────── location /orders/ { proxy_pass http://order_service/; # Rate limit để chống spam limit_req zone=api_limit burst=10 nodelay; } # ─── Payment Service — Extra security ───────────── location /payment/ { proxy_pass http://payment_service/; # Chỉ cho phép từ authenticated users (verify upstream) proxy_read_timeout 60s; # Không cache payment responses add_header Cache-Control "no-store, no-cache"; } # ─── WebSocket for Notifications ────────────────── location /ws/notify/ { proxy_pass http://notify_service/; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 3600s; # WebSocket cần timeout dài } # Health check endpoint cho load balancer bên ngoài location /health { return 200 '{"status":"ok"}' ; add_header Content-Type application/json; access_log off; } }
# ═══════════════════════════════════════════════════ # Load Balancer — High Traffic News Site # Architecture: Internet → CDN → Nginx LB → App Servers # ═══════════════════════════════════════════════════ upstream news_app { least_conn; # Route tới server có ít active connections nhất # Primary servers với health check params server app1.internal:8080 weight=5 max_fails=3 fail_timeout=30s; server app2.internal:8080 weight=5 max_fails=3 fail_timeout=30s; server app3.internal:8080 weight=3 max_fails=3 fail_timeout=30s; server app4.internal:8080 weight=3 max_fails=3 fail_timeout=30s; server app5.internal:8080 weight=1 max_fails=3 fail_timeout=30s; # Backup — chỉ dùng khi tất cả primary fail server backup.internal:8080 backup; # Keepalive 64 connections tới upstream per worker keepalive 64; keepalive_requests 100; keepalive_timeout 60s; } # Separate upstream cho static assets upstream static_servers { server static1.internal:9090; server static2.internal:9090; keepalive 32; } server { listen 443 ssl http2; server_name news.example.com; ssl_certificate /etc/ssl/certs/news.crt; ssl_certificate_key /etc/ssl/private/news.key; # Gzip cho HTML/JS/CSS gzip on; gzip_types text/html text/css application/javascript application/json; gzip_comp_level 4; # ─── Dynamic content → App servers ──────────────── location / { proxy_pass http://news_app; 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; # Retry logic: nếu server lỗi thì thử server khác proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; proxy_next_upstream_tries 3; # Thử tối đa 3 servers proxy_next_upstream_timeout 10s; } # ─── Static assets → dedicated servers ─────────── location ~* .(css|js|png|jpg|woff2|svg)$ { proxy_pass http://static_servers; expires 30d; add_header Cache-Control "public, immutable"; add_header Vary "Accept-Encoding"; access_log off; } # ─── Maintenance page (khi deploy) ─────────────── # Uncomment khi cần maintenance: # error_page 502 503 /maintenance.html; # location = /maintenance.html { # root /var/www/maintenance; # internal; # } }
server { listen 443 ssl http2; server_name cdn.example.com; root /var/www/dist; # React build output index index.html; # ─── Compression ──────────────────────────────── gzip on; gzip_static on; # Serve pre-compressed .gz files nếu có gzip_types text/html text/css application/javascript application/json image/svg+xml; gzip_comp_level 9; # Level cao nhất cho static files # ─── HTML files — NO cache (revalidate every time) ─ location ~* .html$ { add_header Cache-Control "no-cache, must-revalidate"; add_header ETag $uri; expires 0; try_files $uri /index.html; # SPA fallback } # ─── Hashed assets — Cache 1 year ──────────────── # Vite/CRA tạo tên file có hash: main.abc123.js location ~* .[0-9a-f]{8}.(css|js)$ { add_header Cache-Control "public, max-age=31536000, immutable"; expires 1y; access_log off; } # ─── Images — Cache 30 days ────────────────────── location ~* .(png|jpg|jpeg|gif|webp|avif|ico|svg)$ { add_header Cache-Control "public, max-age=2592000"; expires 30d; access_log off; } # ─── Fonts — Cache 1 year, CORS needed ─────────── location ~* .(woff|woff2|ttf|otf|eot)$ { add_header Cache-Control "public, max-age=31536000, immutable"; add_header Access-Control-Allow-Origin "*"; # Required cho cross-origin fonts expires 1y; } # ─── Downloads — No cache, force download ───────── location /downloads/ { alias /var/files/downloads/; add_header Content-Disposition "attachment"; add_header Cache-Control "no-store"; # Limit bandwidth per connection limit_rate 5m; # 5MB/s per connection limit_rate_after 10m; # Bắt đầu limit sau 10MB đầu } # ─── SPA catch-all ─────────────────────────────── location / { try_files $uri $uri/ /index.html; } # Security — block dot files và hidden location ~ /. { deny all; return 404; } }
http { # ─── Rate Limit Zones ──────────────────────────── # Zone cho anonymous users: 10 req/s limit_req_zone $binary_remote_addr zone=anon_limit:10m rate=10r/s; # Zone cho authenticated users: 100 req/s limit_req_zone $binary_remote_addr zone=auth_limit:20m rate=100r/s; # Connection limit per IP limit_conn_zone $binary_remote_addr zone=conn_limit:10m; # Map: detect authenticated request by header map $http_x_api_key $is_authenticated { default 0; ~^[A-Za-z0-9]{32}$ 1; # Nếu API key đúng format } # Detailed log format cho API access log_format api_log '$time_iso8601 $remote_addr "$request" $status ' '$body_bytes_sent ${request_time}s ' '"$http_x_api_key" "$http_user_agent"'; server { listen 443 ssl http2; server_name api.example.com; access_log /var/log/nginx/api_access.log api_log; # ─── CORS Headers ──────────────────────────── map $http_origin $cors_origin { default ""; ~^https://app.example.com$ $http_origin; ~^https://.*.trusted.io$ $http_origin; } location /v1/ { # CORS preflight handling if ($request_method = OPTIONS) { add_header Access-Control-Allow-Origin $cors_origin; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-API-Key"; add_header Access-Control-Max-Age 86400; return 204; } add_header Access-Control-Allow-Origin $cors_origin always; # Apply rate limit theo tier if ($is_authenticated = 1) { # set $rate_zone auth_limit; ← không thể dùng if cho limit_req # Workaround: dùng 2 location blocks } limit_req zone=anon_limit burst=20; limit_req_status 429; # Trả 429 Too Many Requests limit_conn conn_limit 20; # Strip internal headers trước khi forward proxy_set_header X-Internal-Token ""; # Remove để security proxy_set_header X-API-Key $http_x_api_key; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Request-ID $request_id; # Unique request ID proxy_pass http://api_backend; # Add response headers add_header X-Request-ID $request_id always; add_header X-Rate-Limit-Limit "10" always; } # Authenticated endpoint — higher rate limit location /v1/premium/ { limit_req zone=auth_limit burst=200 nodelay; proxy_pass http://api_backend; } # API docs — public, no rate limit location /docs/ { proxy_pass http://docs_server; expires 1h; } # 429 custom error page error_page 429 /rate_limit.json; location = /rate_limit.json { return 429 '{"error":"rate_limit_exceeded","retry_after":60}'; add_header Content-Type application/json; add_header Retry-After 60; } } }
Cài Đặt Nginx
# ─── Ubuntu / Debian ───────────────────────────────── sudo apt update sudo apt install -y nginx # Dùng official Nginx repo để có version mới nhất curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo gpg --dearmor -o /usr/share/keyrings/nginx-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" | sudo tee /etc/apt/sources.list.d/nginx.list sudo apt update && sudo apt install -y nginx # ─── CentOS / RHEL / Amazon Linux ─────────────────── sudo yum install -y epel-release sudo yum install -y nginx # ─── Docker ───────────────────────────────────────── docker run -d --name nginx -p 80:80 -p 443:443 -v /path/to/nginx.conf:/etc/nginx/nginx.conf:ro -v /path/to/certs:/etc/ssl/certs:ro nginx:alpine # Kiểm tra version và modules nginx -V 2>&1 | head -5
Lifecycle Commands
Log Commands
Useful One-liners
# Kiểm tra process đang chạy ps aux | grep nginx # Đếm số active connections ss -tnp | grep nginx | wc -l # Xem Nginx status (nếu có stub_status module) curl http://localhost/nginx_status # Test SSL certificate openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates # Check certificate expiry echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -enddate # Benchmark với ab (Apache Benchmark) ab -n 10000 -c 100 https://example.com/ # Benchmark với wrk wrk -t4 -c100 -d30s https://example.com/ # Xem open files của Nginx sudo lsof -p $(cat /var/run/nginx.pid) | head -20 # Kiểm tra file descriptor limit cat /proc/$(cat /var/run/nginx.pid)/limits | grep "open files"
Debug:
• Check backend process:
systemctl status app• Check port:
ss -tlnp | grep :3000• Check Nginx error log:
grep 502 /var/log/nginx/error.log• Curl trực tiếp backend:
curl -v http://localhost:3000
Debug:
• Tăng timeout:
proxy_read_timeout 120s;• Check backend performance
• Check network latency:
ping backend-host• Monitor với:
watch -n1 "ss -tnp | grep :3000 | wc -l"
client_max_body_size.Fix:
client_max_body_size 50m; trong server/location block.Lưu ý: Set đúng context (location upload > server > http)
Fix:
• Add
add_header Access-Control-Allow-Origin "*" always;• Handle OPTIONS preflight với
if ($request_method = OPTIONS)• Chú ý:
always để include cả error responses
Debug Config Issues
# BƯỚC 1: Test syntax config sudo nginx -t # BƯỚC 2: Xem full config đã parse (detect include issues) sudo nginx -T 2>&1 | less # BƯỚC 3: Enable debug logging cho 1 IP cụ thể # Thêm vào server block: # error_log /var/log/nginx/debug.log debug; # events { debug_connection YOUR_IP; } # BƯỚC 4: Check nếu location đúng bằng return debug # Tạm thời thêm vào location cần test: # return 200 "location matched: $uri host: $host "; # add_header Content-Type "text/plain"; # BƯỚC 5: Test request với curl verbose curl -v -H "Host: example.com" http://localhost/api/test # BƯỚC 6: Check upstream connectivity từ Nginx server curl -v http://backend-host:3000/health # BƯỚC 7: Xem real-time logs với filter tail -f /var/log/nginx/error.log | grep -v "favicon"
Common Config Gotchas
# ❌ WRONG: Trailing slash vấn đề với proxy_pass location /api { proxy_pass http://backend; # /api/v1 → backend/api/v1 } # ✅ CORRECT: Với trailing slash sẽ strip prefix location /api/ { proxy_pass http://backend/; # /api/v1 → backend/v1 } # ❌ WRONG: if là antipattern trong location location /download/ { if ($request_method = POST) { proxy_pass http://upload_server; # CÓ THỂ gây undefined behavior! } } # ✅ CORRECT: Dùng separate location hoặc limit_except location /download/ { limit_except GET HEAD { deny all; } root /var/files; } # ❌ WRONG: add_header không có 'always' sẽ bỏ qua error responses add_header X-Frame-Options SAMEORIGIN; # ✅ CORRECT: Dùng 'always' để include trong mọi response code add_header X-Frame-Options SAMEORIGIN always; # ❌ WRONG: Nested location với regex sẽ ignore nhau location /images/ { location ~* .php$ { deny all; } # ĐÂY KHÔNG WORK NHƯ MONG ĐỢI } # ✅ CORRECT: Dùng separate top-level location location ~* /images/.*.php$ { deny all; return 403; }
nginx -t → nginx -s reloadDebug 502: Check backend process → port → firewall → error.log
Debug slow: Check
$upstream_response_time trong access logDeploy zero-downtime: Nginx reload không drop existing connections
Cert renewal: Certbot auto renews +
nginx -s reload
server_tokens off — ẩn Nginx version☐
ssl_protocols TLSv1.2 TLSv1.3 — tắt TLS cũ☐ Security headers: HSTS, X-Frame-Options, CSP
☐
client_max_body_size — set phù hợp☐ Rate limiting — chống DDoS/brute force
☐ Log rotation — tránh disk full
☐ Monitoring:
/nginx_status → Prometheus/Grafana☐ Health check endpoint cho upstream servers