Cách Sửa Lỗi Nginx 499 (Client Closed Request) Trong Access Logs

intermediate Nginx2026-04-28| Nginx 1.14+ trên Ubuntu/Debian/CentOS, thường gặp với các upstream backend (PHP-FPM, Node.js, Python/uWSGI, hoặc bất kỳ app server nào qua reverse proxy)

Error Message

GET /api/data HTTP/1.1" 499 0 "-" "Mozilla/5.0"
#nginx#lỗi-499#timeout#client-đóng-kết-nối

Lỗi 499 Có Nghĩa Là Gì

Nginx 499 không phải là chuẩn HTTP — đây là mã trạng thái tùy chỉnh mà Nginx ghi lại khi client đóng kết nối trước khi server phản hồi xong. Bạn sẽ thấy các dòng như thế này trong /var/log/nginx/access.log:

192.168.1.10 - - [27/Apr/2026:10:45:03 +0000] "GET /api/data HTTP/1.1" 499 0 "-" "Mozilla/5.0"

Chú ý kích thước body phản hồi là 0. Nginx chưa kịp gửi gì cả — client đã cúp máy trước.

Nguyên Nhân Gốc Rễ

Backend của bạn phản hồi quá chậm. Client — trình duyệt, ứng dụng mobile, công cụ CLI, hoặc load balancer upstream — đã hết kiên nhẫn và đóng kết nối TCP. Nginx vẫn đang chờ PHP-FPM, Node.js, uWSGI, hoặc một truy vấn database khi socket bị ngắt.

Các nguyên nhân thường gặp:

  • Một truy vấn database đáng lẽ chỉ mất 50ms nhưng lại mất tới 8 giây do thiếu index
  • Timeout tích hợp sẵn của trình duyệt (thường 30–60 giây) kích hoạt trước khi backend phản hồi
  • Một load balancer upstream — AWS ALB, HAProxy, Cloudflare — có timeout ngắn hơn và ngắt kết nối trước
  • Người dùng tải lại trang hoặc điều hướng đi giữa chừng khi đang có request
  • Client mobile mất kết nối trên mạng 4G không ổn định

Vài chục lỗi 499 mỗi giờ là bình thường — đó chỉ là người dùng điều hướng đi. Hàng trăm hay hàng nghìn? Backend của bạn đang có vấn đề thực sự.

Bước 1 — Tìm Endpoint Nào Đang Chậm

Chưa cần động vào config. Trước tiên, hãy tìm xem URL nào đang sinh ra lỗi 499 và các request đó thực sự chạy mất bao lâu:

# Đếm số lỗi 499 theo đường dẫn URL
grep ' 499 ' /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20

# Kiểm tra thời gian xử lý request cho các endpoint đó (cần có $request_time trong định dạng log)
grep ' 499 ' /var/log/nginx/access.log | awk '{print $NF, $7}' | sort -rn | head -20

Log của bạn chưa có $request_time? Thêm vào /etc/nginx/nginx.conf:

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
               '$status $body_bytes_sent "$http_referer" "$http_user_agent" '
               'rt=$request_time uct=$upstream_connect_time uht=$upstream_header_time urt=$upstream_response_time';

Sau đó reload: nginx -s reload

Bước 2 — Sửa Backend (Đây Mới Là Cách Giải Quyết Thực Sự)

Lỗi 499 chỉ là triệu chứng. Tăng timeout chỉ che giấu vấn đề. Hãy phân tích và sửa backend trước.

# Với PHP-FPM: bật slow log để bắt các request mất hơn 5 giây
; Trong /etc/php/8.1/fpm/pool.d/www.conf
slowlog = /var/log/php-fpm-slow.log
request_slowlog_timeout = 5s
# Với Node.js: tìm các thao tác blocking hoặc promise chưa được resolve
# Với Python/Django/Flask: dùng django-silk hoặc py-spy để xác định view chậm

# Đo trực tiếp một endpoint cụ thể
curl -o /dev/null -s -w "Connect: %{time_connect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" https://example.com/api/data

Một phản hồi mất 12 giây trong khi đáng lẽ chỉ mất 200ms thường có nghĩa là đang quét toàn bộ bảng hoặc có vấn đề N+1 query. Sửa điều đó, và 80% lỗi 499 của bạn sẽ biến mất mà không cần chạm vào Nginx.

Bước 3 — Tinh Chỉnh Proxy Timeout Của Nginx

Một số endpoint thực sự cần thêm thời gian — xuất file CSV số lượng lớn, một báo cáo tổng hợp dữ liệu nhiều tháng. Với những trường hợp đó, hãy tăng timeout cho location cụ thể thay vì áp dụng toàn cục:

# /etc/nginx/conf.d/your-site.conf

location /api/ {
    proxy_pass http://backend;

    proxy_connect_timeout  10s;   # thời gian thiết lập kết nối tới upstream
    proxy_send_timeout     60s;   # thời gian giữa các lần ghi lên upstream
    proxy_read_timeout     60s;   # thời gian chờ phản hồi từ upstream

    proxy_http_version 1.1;
    proxy_set_header Connection "";
}

Chỉ ghi đè cho route chậm — đừng tăng ngân sách timeout cho tất cả endpoint:

location /api/export {
    proxy_pass http://backend;
    proxy_read_timeout 300s;  # 5 phút chỉ dành riêng cho endpoint này
}
nginx -t && nginx -s reload

Bước 4 — Kiểm Tra Timeout Của Load Balancer

Khi Nginx đứng sau một lớp khác, lớp đó có thể đang ngắt kết nối trước khi timeout của Nginx kịp kích hoạt.

AWS ALB mặc định có idle timeout là 60 giây. Để tăng lên: EC2 → Load Balancers → chọn ALB của bạn → Attributes → Idle timeout.

Cloudflare áp dụng origin timeout tối đa 100 giây trên tất cả các gói. Các endpoint chạy lâu hơn cần cách tiếp cận khác — chunked responses, WebSockets, hoặc chuyển công việc sang Cloudflare Workers.

HAProxy — kiểm tra timeout clienttimeout server trong block defaults:

defaults
    timeout connect  5s
    timeout client   60s
    timeout server   60s

Bước 5 — Xử Lý Lỗi 499 Do Người Dùng Điều Hướng

Một số lỗi 499 là không thể tránh khỏi. Người dùng nhấn Refresh vào thời điểm không may và Nginx ghi lại lỗi 499 — nhưng backend upstream vẫn đang xử lý request đó, chiếm một worker thread mà không ai đang chờ kết quả.

Bạn có thể yêu cầu Nginx để upstream xử lý xong bất kể thế nào:

# /etc/nginx/nginx.conf (khối http)
proxy_ignore_client_abort on;

Hãy dùng tùy chọn này cẩn thận. Nó giữ kết nối upstream còn sống và chiếm tài nguyên server ngay cả sau khi client đã ngắt. Ổn với các request ngắn dưới 5 giây; nhưng là ý tưởng tồi cho các tác vụ export chạy lâu, nơi bạn có thể tích lũy nhiều kết nối zombie.

Bước 6 — Xác Nhận Đã Sửa Xong

# Theo dõi lỗi 499 giảm dần theo thời gian thực
tail -f /var/log/nginx/access.log | grep ' 499 '

# Đếm số lỗi 499 mỗi phút trong 5 phút
for i in $(seq 5); do
  echo -n "$(date +%H:%M): "
  grep ' 499 ' /var/log/nginx/access.log | grep "$(date +%d/%b/%Y:%H:%M)" | wc -l
  sleep 60
done

# Xác nhận endpoint chậm đã cải thiện (xem thời gian urt= trong log)
grep '/api/data' /var/log/nginx/access.log | tail -20

Phòng Ngừa

  • Timeout theo từng route, không áp dụng toàn cục. Endpoint nhanh nên thất bại nhanh. Chỉ cấp ngân sách timeout dài hơn cho những route vốn dĩ chậm theo thiết kế — nếu không, một truy vấn bị lỗi sẽ được phép chạy tới 300 giây ở khắp nơi.
  • Cảnh báo theo tỷ lệ lỗi 499, không chỉ đếm số lượng. Khi lỗi 499 vượt quá 1–2% tổng số request, có nghĩa là có gì đó đang thực sự bị hỏng — không chỉ là người dùng nhấn Back. Hãy kết nối điều này vào PagerDuty, Grafana, hoặc bất cứ công cụ nào bạn đang dùng.
  • Chuyển tác vụ chậm ra khỏi HTTP thread. Nếu một request liên tục mất hơn 5–10 giây, hãy trả về job ID ngay lập tức và để client polling. Giữ kết nối HTTP mở 30 giây dưới tải cao sẽ khiến bạn gặp nhiều lỗi 499.
  • Dùng connection pool cho database. PgBouncer cho PostgreSQL, hoặc connection pool trong ORM của bạn. Các request phải chờ kết nối database khả dụng là nguyên nhân phổ biến nhất gây ra lỗi 499 trong các API backend.

Related Error Notes