How to Fix a 499 Status Code
Find who gave up first, then fix its timer or the slow endpoint, with cURL, Python and Node.js timing code.
Updated
TL;DR
499 Client Closed Request is what nginx logs when a client hangs up before the response starts; nginx never sends it, as the client is gone. To fix it, find whose time limit ran out, then speed up the endpoint or raise that limit.
Where a 499 appears and what the client printed
nginx logs the 499; the client that left prints its own error at that moment.
| Where | What you see |
|---|---|
| nginx access log (combined format) | "GET /export HTTP/1.1" 499 0 |
| nginx error log, info level, Linux | epoll_wait() reported that client prematurely closed connection, so upstream connection is closed too |
| nginx error log, info level, HTTP/2 cancel | client canceled stream 5 |
| curl --max-time 10 | curl: (28) Operation timed out after 10006 milliseconds with 0 bytes received |
| Python requests, timeout=10 | requests.exceptions.ReadTimeout: HTTPConnectionPool(host='api.example.com', port=80): Read timed out. (read timeout=10) |
| Node.js fetch with AbortSignal.timeout() | DOMException [TimeoutError]: The operation was aborted due to timeout |
Why this happens
Whoever asked for the page stopped waiting before the server had an answer ready.
Often a script, app, load balancer or CDN has a time limit shorter than a slow endpoint needs, so it hangs up mid-wait.
Diagnose your 499 first
These checks tell a client, balancer, CDN or visitor apart; the first needs only a browser.
Load the slow URL in Chrome with DevTools' Network tab open; if its Time is longer than your app's timeout, your app gave up first.
Log $request_time in a copy of the combined format and group 499s by path; one repeated value means a fixed timer ran out.
Compare that value with the limits in front of nginx; near 60 seconds points to an AWS load balancer's default, near 125 to Cloudflare's.
Check the user agents on the 499 lines; browsers with short, scattered times mean visitors closed pages, which is expected.
Look for your health-check path among the 499s; near 5 seconds, it means the AWS target group's checks are failing too.
Solutions ranked by effectiveness
Match the card to what the checks found; change only the timer or endpoint they name.
- Most common fix
Time the request, then set your limit
Applies when your script or app gave up as nginx logged the 499. Time the request with a generous limit, then set yours above it if the endpoint is meant to take that long.
curl -sS -o /dev/null --max-time 120 \ -w '%{http_code} first byte %{time_starttransfer}s, total %{time_total}s\n' \ https://example.com/export - Check next
Line up the timeouts in front of nginx
Applies when the 499 times match a load balancer or proxy limit rather than your client's. Make each outer layer wait longer than the one behind it.
On an AWS ALB, set Connection idle timeout above your slowest expected response.
Set nginx proxy_read_timeout a few seconds below the balancer's idle timeout.
With several upstream servers, set proxy_next_upstream_timeout below proxy_read_timeout, or nginx retries a timed-out GET.
If a forward proxy you scrape through gave up, treat it as a proxy timeout.
- Site owners
Fix the endpoint clients give up on
Applies when the endpoint is slow without good reason, or a limit you cannot change ran out, such as a CDN's. Make the path answer inside it, or stop making clients wait.
Read $upstream_response_time on the path's successful lines to see how long the app takes.
Answer long jobs with 202 Accepted and a status URL the client can poll.
Where work must finish anyway, such as an order webhook, set proxy_ignore_client_abort on.
Leave it off elsewhere, since with it on the app finishes replies nobody reads.
Stop the 499 from coming back
Habits for site owners that keep 499s rare and the next spike easy to explain.
Leave the timing fields in your log format after the fix, so the next spike arrives already measured.
Keep a list of every timeout from client to app, and recheck its order when a CDN or balancer joins.
Alert on the 499 share per path rather than the total, so one slow endpoint stands out.
Keep health-check endpoints free of calls to databases and other slow services.
Related errors
Learn more
FAQ
Poll long jobs from one IP
A sticky session holds one IP for 3 minutes to 24 hours, as you set.


