How HTTP Works
4xx · Client errorNon-standard · nginx
< HTTP/1.1 499 Client Closed Request

499 Client Closed Request (nginx)

nginx 499 means the client hung up before the response was sent. Why it spikes with slow upstreams, how proxy_ignore_client_abort works, and how to debug it.

Reviewed 5 min readintermediate4 sourcesTry itMarkdown
Cacheable
No
Retry?
Nothing: the client hung up first
Usually sent by
nginx (logged only, never sent)
On this page

TL;DR: 499 is nginx’s log-only code for “the client closed the connection before I could answer.” Nothing is sent over the wire; it shows up in access.log and is usually caused by clients timing out on a slow upstream, users navigating away, or a load balancer in front with a shorter timeout than nginx.

What it means

499 is not in any RFC. nginx defines it internally as NGX_HTTP_CLIENT_CLOSED_REQUEST so the access log has something to record when a request ends because the client’s TCP connection (or, on HTTP/2 and HTTP/3, the stream) went away mid-request. A browser, curl or SDK can never receive a 499 from nginx, because by definition the connection is gone. If you see 499 in a client, a different proxy or gateway produced it.

A typical access log line, using the default combined format:

203.0.113.7 - - [04/Oct/2026:10:15:32 +0000] "POST /api/reports/export HTTP/1.1" 499 0 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X)"

Body bytes sent is 0, which is the giveaway. The line only becomes useful once you add timing to the log format:

log_format timed '$remote_addr [$time_local] "$request" $status '
                 '$body_bytes_sent rt=$request_time urt=$upstream_response_time '
                 'uct=$upstream_connect_time ua="$http_user_agent"';
access_log /var/log/nginx/access.log timed;
203.0.113.7 [04/Oct/2026:10:15:32 +0000] "POST /api/reports/export HTTP/1.1" 499 0 rt=30.001 urt=- uct=0.001 ua="okhttp/4.12.0"

rt=30.001 with urt=- (the upstream had not finished) means the client waited exactly 30 seconds and left. A round number like that is almost always a client-side timeout constant. Find who owns it.

nginx also notes the event in error.log, but only at info level, so you will not see it unless you run error_log ... info;:

2026/10/04 10:15:32 [info] 1234#1234: *5678 client prematurely closed connection, client: 203.0.113.7, server: api.example.com, request: "POST /api/reports/export HTTP/1.1", upstream: "http://10.0.2.15:8080/api/reports/export"

Who closed the connection?

The closer is the party directly in front of nginx, which is not always the end user.

DirectionWhat to check
Browser or mobile appRound timeout values in $request_time (10, 15, 30, 60 seconds) match an HTTP client default. The user agent shows the SDK (okhttp, axios, python-requests). Users navigating away or hitting reload also produce 499s, with varied durations.
Cloud load balancer in front of nginxIf the LB idle timeout (AWS ALB default: 60 s) is shorter than your nginx and app timeouts, the LB gives up on the target and closes the connection, so nginx logs 499 while the ALB returns 504 to the client. An ALB 460 is different: the ALB’s own client hung up first. If every 499 comes from a private LB address, the closer is the LB.
Health checkers and monitorsProbes with a 1-5 second timeout against a slow endpoint produce steady 499s. Filter on user agent (ELB-HealthChecker, kube-probe, Pingdom).
HTTP/2 clientsA stream cancel (RST_STREAM with CANCEL), such as from AbortController.abort() or a navigated-away page, is logged as 499 the same way.
CDN in frontCDNs have their own origin timeouts (see 524); when they abandon the origin connection, nginx sees a closed client.

Mobile networks inflate the numbers: a client on a flaky connection that backgrounds the app sends a FIN or simply vanishes, and nginx records 499 once it notices. A small steady baseline of 499s on a consumer-facing API is normal. A sudden jump, or a cluster on a single endpoint, is the signal.

Fix it

  1. Confirm the upstream is slow. Group 499s by URI and compare $upstream_response_time for 200s on the same route. If p95 is near the clients’ timeout, the fix is making the endpoint faster (indexes, caching, or moving the work to a queue and returning 202).
  2. Align the timeout chain. Each hop’s timeout should be longer than the one behind it, so the layer closest to the work gives up first with a meaningful error: app < nginx proxy_read_timeout (default 60 s) < load balancer idle timeout < client timeout. When the client is shortest you get 499 and no useful error anywhere.
  3. Stop abandoned work from piling up. Make sure the app notices a closed connection: in Node, listen for req.on('close') and cancel the database query; in Go, honour r.Context().Done(). Under gunicorn or PHP-FPM the worker generally keeps going, so rely on query timeouts.
  4. Make retries safe. A client that times out and retries a POST while the first one is still running creates duplicates. Use an idempotency key.
  5. Only then consider proxy_ignore_client_abort.
location /webhooks/ {
    proxy_pass http://app_backend;

    # Keep the upstream request running even if the sender disconnects.
    proxy_ignore_client_abort on;
}

The default is off. When it is on, nginx does not close the upstream connection when the client leaves, so the backend completes the request. The directive exists for work that must finish regardless, such as webhook receivers whose sender has a short timeout. On slow endpoints it is counterproductive: abandoned requests keep consuming workers, clients retry, and load amplifies. The equivalents for other backends are fastcgi_ignore_client_abort, uwsgi_ignore_client_abort and scgi_ignore_client_abort.

Kubernetes and ingress-nginx

ingress-nginx logs 499 in its access log exactly as stock nginx does, in a format that includes upstream timings and a request ID. The usual cause is a mismatch between the cloud load balancer in front of the ingress and the ingress timeouts. Timeouts are set per Ingress through annotations:

metadata:
  annotations:
    nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "120"

If a cloud LB with a 60 s idle timeout sits in front, raising the annotation to 120 changes nothing: the LB closes first and the ingress logs 499.

Reproduce it

# Client gives up after 2 seconds against a slow endpoint
curl -m 2 -i https://api.example.com/slow
# curl: (28) Operation timed out after 2001 milliseconds with 0 bytes received

# nginx access log now has: "GET /slow HTTP/1.1" 499 0 rt=2.001

Frequently asked questions

What does nginx 499 mean?

It means the client closed its connection before nginx finished sending a response. The code is invented by nginx for its own access log and is never actually sent to anyone, because there is no longer a connection to send it on.

Is a 499 an error on my server or on the client?

Neither, strictly: it records that the client lost patience or went away. But a sustained rise in 499s almost always points to a slow upstream, because clients abandon requests that take longer than their own timeout. Compare $request_time and $upstream_response_time in the log line to see where the time went.

Does nginx stop the backend when it logs a 499?

By default yes: with proxy_ignore_client_abort off, nginx closes its connection to the upstream when the client disconnects. Whether the application actually stops working depends on whether it notices the closed socket. Many frameworks keep running the handler to completion.

What is the difference between 499 and 504?

With a 504, nginx gave up waiting on the upstream (proxy_read_timeout) and sent an error to a client that was still connected. With a 499 the client gave up first. If a client timeout is 30 seconds and proxy_read_timeout is 60, slow requests produce 499, not 504.

Should I set proxy_ignore_client_abort on to get rid of 499s?

Only if the upstream work must complete even when nobody is listening, such as a payment callback. It hides the symptom and lets abandoned requests keep occupying upstream workers, which makes a slow-backend incident worse.

Sources

  1. nginx: proxy_ignore_client_abortnginx.org
  2. nginx: ngx_http_log_modulenginx.org
  3. AWS: Application Load Balancer access logs (460 client closed)docs.aws.amazon.com
  4. RFC 9110: HTTP Semantics, Status Codesrfc-editor.org

Keep going

Browse /search