Sổ tay kỹ thuật

Nginx reverse proxy với TLS, HTTP/2, WebSocket và rate limit

how-to#nginx#tls#linux· cập nhật 2026-11-06

Dùng khi đặt Nginx trước app nội bộ để terminate TLS, bật HTTP/2, proxy WebSocket, gắn header chuẩn và giới hạn tốc độ request.

Sửa trang này
Mục lục

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)

/etc/nginx/conf.d/00-common.conf
# 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

/etc/nginx/conf.d/app.example.com.conf
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êng http2 on;. Nginx cũ hơn vẫn phải dùng cú pháp cũ.
  • proxy_set_header và add_header không cộng dồn: nếu một location khai báo bất kỳ proxy_set_header nào, nó bỏ hết các proxy_set_header kế 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ớp location bằ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 -c

Lỗ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

Tham khảo