Tóm tắt: Một server port 80 chỉ redirect sang HTTPS, một server 443 terminate TLS (http2 on;, TLS 1.2/1.3) rồi proxy_pass tới upstream. Thêm map $http_upgrade cho WebSocket, proxy_set_header cho Host/X-Forwarded-*, và limit_req cho endpoint nhạy cảm. Cú pháp theo Nginx ≥ 1.25.1.
Luồng hoạt động
flowchart LR C[Client] -->|"HTTP :80"| R["301 sang HTTPS"] C -->|"HTTPS :443 TLS + HTTP/2"| N[Nginx] N -->|"limit_req"| L{"Vượt rate?"} L -->|Có| E["429"] L -->|Không| U["upstream app 127.0.0.1:8000"] N -->|"Upgrade: websocket"| W["ws 127.0.0.1:8001"]
Cấu hình
Cấu hình dùng chung (khối http)
# WebSocket: chỉ gửi "Connection: upgrade" khi client yêu cầu upgrade
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# Rate limit theo IP client: 10 request/giây, vùng nhớ 10 MB
limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
limit_req_status 429;
upstream app_backend {
server 127.0.0.1:8000;
keepalive 32;
}Virtual host
server {
listen 80;
listen [::]:80;
server_name app.example.com;
location /.well-known/acme-challenge/ { root /var/www/certbot; }
location / { return 301 https://$host$request_uri; }
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options nosniff always;
client_max_body_size 20m;
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection ""; # cho keepalive tới upstream
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_read_timeout 60s;
}
location /api/login {
limit_req zone=perip burst=20 nodelay;
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /ws/ {
proxy_pass http://127.0.0.1:8001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 1h; # mặc định 60s sẽ cắt kết nối ws im lặng
}
}Điểm cần nhớ:
listen 443 ssl http2;đã deprecated từ 1.25.1, dùng directive riênghttp2 on;. Nginx cũ hơn vẫn phải dùng cú pháp cũ.proxy_set_headervàadd_headerkhông cộng dồn: nếu mộtlocationkhai báo bất kỳproxy_set_headernào, nó bỏ hết cácproxy_set_headerkế thừa từserver. Vì vậy mỗi location ở trên khai báo lại đủ header.proxy_pass http://host:port/;(có/cuối) thay phần URI khớplocationbằng/. Không có/cuối thì URI giữ nguyên.burst=20 nodelay: cho phép dồn 20 request vượt rate được xử lý ngay, quá nữa trả 429.- Lấy chứng chỉ Let's Encrypt: certbot.
Áp dụng
sudo nginx -t && sudo systemctl reload nginx
curl -sI https://app.example.com | head -5
curl -sI --http2 https://app.example.com | head -1 # mong đợi HTTP/2 200
openssl s_client -connect app.example.com:443 -servername app.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -dates
for i in $(seq 1 40); do curl -s -o /dev/null -w '%{http_code}\n' https://app.example.com/api/login; done | sort | uniq -cLỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách xử lý |
|---|---|---|
502 Bad Gateway, log connect() failed (111: Connection refused) |
Upstream chưa chạy hoặc sai port | ss -ltnp | grep 8000, curl -v http://127.0.0.1:8000 |
502, log connect() ... (13: Permission denied) |
SELinux chặn Nginx kết nối mạng | setsebool -P httpd_can_network_connect 1 |
504 Gateway Timeout |
Upstream xử lý lâu hơn proxy_read_timeout |
Tăng timeout cho location đó, hoặc sửa app |
| WebSocket đóng sau đúng 60 giây | proxy_read_timeout mặc định 60s |
Tăng proxy_read_timeout hoặc gửi ping từ app |
| WebSocket handshake lỗi 400 | Thiếu Upgrade/Connection header hoặc proxy_http_version 1.0 |
Dùng block /ws/ như trên |
App thấy mọi request từ 127.0.0.1, sinh link http:// |
Không gửi/không đọc X-Forwarded-* |
Gửi header, cấu hình app tin proxy (vd uvicorn --proxy-headers) |
413 Request Entity Too Large |
client_max_body_size mặc định 1m |
Tăng trong server/location |
Cảnh báo the "listen ... http2" directive is deprecated |
Cú pháp cũ trên Nginx ≥ 1.25.1 | Chuyển sang http2 on; |
| Trình duyệt báo chain không tin cậy, curl máy khác lỗi | Dùng cert.pem thay vì fullchain.pem |
Trỏ ssl_certificate tới fullchain.pem, xem TLS troubleshooting |
| Header HSTS mất ở một số location | Location có add_header riêng nên không kế thừa |
Khai báo lại hoặc dùng include snippet |