Geonode logo
Website and server errors

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.

WhereWhat you see
nginx access log (combined format)"GET /export HTTP/1.1" 499 0
nginx error log, info level, Linuxepoll_wait() reported that client prematurely closed connection, so upstream connection is closed too
nginx error log, info level, HTTP/2 cancelclient canceled stream 5
curl --max-time 10curl: (28) Operation timed out after 10006 milliseconds with 0 bytes received
Python requests, timeout=10requests.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.

  1. 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
  2. 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.

    1. On an AWS ALB, set Connection idle timeout above your slowest expected response.

    2. Set nginx proxy_read_timeout a few seconds below the balancer's idle timeout.

    3. With several upstream servers, set proxy_next_upstream_timeout below proxy_read_timeout, or nginx retries a timed-out GET.

    4. If a forward proxy you scrape through gave up, treat it as a proxy timeout.

  3. 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.

    1. Read $upstream_response_time on the path's successful lines to see how long the app takes.

    2. Answer long jobs with 202 Accepted and a status URL the client can poll.

    3. Where work must finish anyway, such as an order webhook, set proxy_ignore_client_abort on.

    4. 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.

  1. Leave the timing fields in your log format after the fix, so the next spike arrives already measured.

  2. Keep a list of every timeout from client to app, and recheck its order when a CDN or balancer joins.

  3. Alert on the 499 share per path rather than the total, so one slow endpoint stands out.

  4. Keep health-check endpoints free of calls to databases and other slow services.

Keep the proxy hop shortConnect through the closest of Geonode's gateways in France, the United States or Singapore.
Try residential proxies

Related errors

Learn more

FAQ

It is nginx's code NGX_HTTP_CLIENT_CLOSED_REQUEST, logged when a client disconnects before nginx sends the response header. nginx has no error page for it and rejects error_page 499 in its config, since the client is already gone.

It means the client closed the connection before the server replied. The server refused nothing; 499 is only the label nginx writes to its log for that hang-up.

Its number puts it in the 4xx class, which RFC 9110 uses when the client seems to have erred; the client did end the request, though a slow server may be the cause.

No. IANA lists 452 to 499 as unassigned and RFC 9110 does not define it; nginx introduced 499 for its own log, and Google's RPC error codes map CANCELLED to 499.

With a 499, the client left before nginx had a reply. With a 504, nginx gave up on the app first, by default after 60 seconds without data under proxy_read_timeout, and told the client so.

Match $request_time on the 499 lines to your timeouts to see which one fired. Speed up the endpoint if it is slower than it should be; raise that timeout only if the wait is normal.

Poll long jobs from one IP

A sticky session holds one IP for 3 minutes to 24 hours, as you set.