Lỗi Gặp Phải
502 Bad Gateway xuất hiện đột ngột không báo trước. Người dùng bị văng ra ngoài và log lỗi Nginx có nội dung như sau:
2024/01/15 10:23:41 [error] 1234#1234: *1 upstream sent too big header while reading response header from upstream,
client: 192.168.1.100, server: example.com, request: "GET /dashboard HTTP/1.1",
upstream: "http://127.0.0.1:3000/dashboard", host: "example.com"
Mô hình thường khá rõ ràng khi bạn nhận ra: lỗi 502 xuất hiện ngay sau khi đăng nhập, hoặc trên các trang có set JWT token và session cookie. Các trang không yêu cầu xác thực thì vẫn tải bình thường.
Nguyên Nhân
Nginx đọc response header từ upstream vào một buffer có kích thước cố định trước khi chuyển tiếp đến client. Khi các header này quá lớn, Nginx từ chối toàn bộ response — upstream không bao giờ gửi được phần body.
Giá trị mặc định của proxy_buffer_size là 4 KB. Nghe có vẻ đủ dùng, cho đến khi bạn thấy những gì ứng dụng hiện đại thực sự gửi đi. Một JWT token đơn lẻ đã chiếm 1–3 KB. Session cookie của Rails thêm vào 1–2 KB nữa. Cộng thêm vài header Set-Cookie từ luồng OAuth là bạn đã vượt giới hạn. Một response đã xác thực từ ứng dụng Rails hay Django có thể đẩy tới 6–10 KB chỉ riêng phần header.
Directive proxy_buffers kiểm soát tổng pool dùng để buffer response body. Cả hai cài đặt đều cần đủ lớn, nhưng proxy_buffer_size là thứ hay làm người ta vấp ngã đầu tiên.
Bước 1 — Xác Nhận Nguyên Nhân
Trước khi chỉnh config, hãy xác minh rằng upstream thực sự đang gửi header quá lớn. Bỏ qua Nginx và query trực tiếp vào ứng dụng:
# Truy cập upstream trực tiếp — bỏ qua Nginx hoàn toàn
curl -si http://127.0.0.1:3000/dashboard | head -50
# Đo tổng kích thước header theo byte
curl -si http://127.0.0.1:3000/dashboard \
| awk '/^\r?$/{exit} {total += length($0) + 2} END{print "Header bytes:", total}'
Vượt quá 4 KB? Đó chính là thủ phạm. Những nguyên nhân phổ biến:
- JWT token lưu trong
Set-Cookie— dễ dàng chiếm 1–3 KB mỗi cái - Nhiều auth cookie từ session của Rails hoặc Django
- OAuth token hoặc SAML assertion được truyền qua response header
- Ứng dụng tích lũy cookie mà không xóa những cái cũ
Bước 2 — Tăng Kích Thước Proxy Buffer
Mở server block tương ứng (thường nằm trong /etc/nginx/sites-available/ hoặc /etc/nginx/conf.d/) và thêm các directive buffer vào trong block location:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
# Mặc định là 4k — tăng lên 16k hoặc 32k tùy kích thước header
proxy_buffer_size 16k;
# Tổng buffer pool cho response body
proxy_buffers 8 16k;
# Giữ giá trị này khoảng 2× proxy_buffer_size
proxy_busy_buffers_size 32k;
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;
}
}
Chọn kích thước phù hợp:
16klà đủ cho phần lớn trường hợp dùng JWT và session cookie- Tăng lên
32knếu header luôn ở mức lớn (nhiều token cồng kềnh) - Đặt
proxy_busy_buffers_sizekhoảng 2×proxy_buffer_size - Giá trị phải là bội số của kích thước memory page của hệ thống — thường là 4 KB trên Linux
Bước 3 — Áp Dụng Config
Luôn kiểm tra trước khi reload. Một lỗi cú pháp sẽ làm sập toàn bộ site đang chạy trên instance Nginx đó.
# Kiểm tra trước — đừng bao giờ bỏ qua bước này
nginx -t
# Kết quả mong đợi:
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful
# Reload không làm gián đoạn — các kết nối đang hoạt động không bị ngắt
nginx -s reload
# hoặc
systemctl reload nginx
Bước 4 — Kiểm Tra Kết Quả
# Kiểm tra endpoint đang trả về 502
curl -si https://example.com/dashboard -o /dev/null -w "%{http_code}\n"
# Nên trả về 200
# Theo dõi error log — xác nhận thông báo lỗi đã biến mất
tail -f /var/log/nginx/error.log
Cách Khác: Áp Dụng Toàn Cục Qua Block http
Khi sự cố xảy ra ở nhiều location hoặc virtual host, hãy cấu hình buffer một lần trong block http bên trong /etc/nginx/nginx.conf:
http {
# ...
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
# ...
}
Directive ở cấp location luôn ghi đè directive ở cấp http, vì vậy bạn vẫn có thể tinh chỉnh theo từng route khi cần.
Cũng Cần Kiểm Tra: fastcgi_buffer_size (PHP-FPM)
Đang dùng fastcgi_pass thay vì proxy_pass? Thông báo lỗi hoàn toàn giống nhau, nhưng cách sửa lại dùng một bộ directive khác:
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_buffer_size 16k;
fastcgi_buffers 8 16k;
fastcgi_busy_buffers_size 32k;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
Cùng một nguyên nhân gốc rễ, chỉ khác tiền tố directive. Dùng cùng kích thước buffer như khi cấu hình proxy_*.
Phòng Ngừa
- Không lưu JWT trong cookie: Đây là nguyên nhân phổ biến nhất gây ra lỗi này. Chỉ lưu session ID ngắn trong cookie và lưu token thực ở phía server — kích thước header của bạn sẽ giảm đáng kể.
- Đặt thời hạn hết hạn cookie đúng cách: Các framework cứ liên tục thêm cookie mà không dọn dẹp cookie cũ sẽ cuối cùng làm tràn bất kỳ buffer nào dù lớn đến đâu. Hãy kiểm tra lại vòng đời cookie của bạn.
- Chia nhỏ token lớn: Nếu không thể tránh khỏi token quá cỡ, một số framework (chẳng hạn NextAuth) có thể tự động chia token thành nhiều cookie nhỏ hơn.
- Tăng giá trị mặc định ngay từ đầu: Thêm
proxy_buffer_size 16kvào config Nginx cơ sở trước khi triển khai. Giá trị mặc định 4 KB thuộc về một thời đại khác — bất kỳ ứng dụng hiện đại nào có xác thực đều sẽ đụng phải giới hạn này. - Kiểm tra kích thước header trên môi trường staging: Chạy
curl -sitrên các endpoint đã xác thực và xem con số thực tế. Phát hiện header 8 KB ở staging còn hơn để xảy ra sự cố lúc 3 giờ sáng trên production.

