Nginx Error: '499 Client Closed Request' — Cause, Fix, and Troubleshooting Guide
Understand nginx status 499 'Client Closed Request': the client hung up before nginx replied, usually because a slow upstream made it time out and give up.
- #nginx
- #web-server
- #troubleshooting
- #proxy
Stuck on this NGINX error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
What this error means
499 is a non-standard status code specific to nginx. Nginx logs it in the access log when the client closes the connection before nginx has finished producing a response — the request was cancelled by the client, not failed by the server:
203.0.113.44 - - [12/Jul/2026:16:03:22 +0000] "GET /api/report HTTP/1.1" 499 0 "-" "curl/8.0"
The 499 sits in the status field and the body-bytes-sent is 0 because nginx never got to send a response. It almost always means the client, browser, mobile app, or an upstream load balancer waited on a slow request, hit its own timeout, and gave up. Nginx notices the closed socket and records 499. It is not a server error in the 5xx sense, but a high 499 rate is a strong signal that something behind nginx is too slow, so users and load balancers are abandoning requests.
How the server responds
- A rise in
499entries in the nginx access log, typically with0bytes sent. - The 499s correlate with slow endpoints (reports, exports, search, upstream-heavy APIs), not static assets.
- Browsers show a cancelled/stalled request; mobile clients or SDKs report a timeout on the same paths.
- A load balancer or CDN in front of nginx logs its own gateway timeout at roughly the same time the client cut off.
upstream_response_timefor the affected requests is high, approaching the client’s or LB’s idle timeout.- Retries pile up: clients that time out and retry generate bursts of 499 plus extra upstream load.
Testing the server configuration
Quantify the 499s and see which endpoints dominate:
# Count responses by status code in the access log
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
# Which request paths return 499 most often
awk '$9==499 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
Correlate 499s with upstream latency. Add $upstream_response_time and $request_time to your log format if they are not already there, then confirm the slow paths:
log_format timed '$remote_addr - $status - rt=$request_time '
'urt=$upstream_response_time "$request"';
sudo nginx -t && sudo systemctl reload nginx # after adding the log_format + access_log
# then, once traffic accrues, find slow 499s:
awk '$3 ~ /^499/' /var/log/nginx/access.log | tail
Check the timeout budgets at each layer so you know where the cutoff happens first:
sudo nginx -T | grep -E "proxy_read_timeout|proxy_send_timeout|proxy_connect_timeout|keepalive_timeout"
Compare that to your front load balancer’s idle timeout and your client/SDK timeout. The layer with the smallest timeout is the one that cancels the request and produces the 499 nginx records.
Server configuration causes
- Slow upstream — the backend takes longer than the client’s timeout to respond, so the client disconnects before nginx can return anything; the single most common cause.
- Client/LB timeout shorter than the work — a browser fetch timeout, SDK deadline, or load-balancer idle timeout is set below the real response time of a legitimately slow endpoint.
- Mismatched timeouts across layers — the front LB idle timeout is shorter than nginx’s
proxy_read_timeout, so the LB gives up while nginx is still patiently waiting on the upstream. - User-initiated cancellation — users navigate away, refresh, or cancel a long-running request; benign in small numbers.
- Aggressive client retries — a client that retries on timeout multiplies both 499s and upstream load, compounding the slowness that caused the cancellation.
The fix
The durable fix is to make the slow work faster, or to align the timeout budgets so the client is not giving up on a request that would have succeeded. Start by speeding up or bounding the upstream: optimize the slow query/handler, add caching, or move long jobs to an async pattern (return 202 and a status URL) so no single request blocks for tens of seconds.
Where the endpoint is legitimately slow, align timeouts from the outside in. The client/LB timeout should be greater than or equal to nginx’s upstream timeouts, which should cover the real work:
location /api/report {
proxy_pass http://backend;
proxy_connect_timeout 5s;
proxy_read_timeout 120s; # allow the slow report to complete
proxy_send_timeout 120s;
}
Then raise the front load balancer’s idle timeout to at least the same 120s, and raise the client/SDK timeout to match, so the client waits for the response nginx is prepared to deliver rather than abandoning it.
If clients retry on timeout, make retries safe and bounded (idempotency keys, backoff) so a slow window does not amplify into a retry storm that worsens upstream latency. You can also let nginx keep processing a cancelled request rather than aborting mid-work when the result is cacheable:
location /api/report {
proxy_pass http://backend;
proxy_ignore_client_abort on; # finish + cache even if the client left (use judiciously)
}
Use proxy_ignore_client_abort on; sparingly — it makes nginx continue work for a client that is gone, which wastes resources unless the response is being cached for the next caller.
Validate and reload after these changes (no restart needed):
sudo nginx -t && sudo systemctl reload nginx
Safe configuration practice
- Treat 499 as a latency signal, not a server bug: chase the slow upstream first, then align timeouts; raising timeouts alone just makes users wait longer.
- Keep timeout budgets ordered from the outside in — client timeout >= LB idle timeout >= nginx
proxy_read_timeout>= real work — so the client does not cut off a request that would have succeeded. - A small baseline of 499s is normal (users cancel and navigate away); alert on rate and on correlation with high
upstream_response_time, not on any single occurrence. proxy_ignore_client_abort on;can waste backend capacity by finishing work no one is waiting for; only use it when the result is cached or otherwise reused.- Watch for retry storms — a client that retries on timeout multiplies 499s and upstream load; fix the retry policy, not just nginx.
- Include
$request_timeand$upstream_response_timein your log format and feed them to your monitoring dashboard so slow-endpoint regressions are visible before users abandon them.
Related server errors
- Nginx Error: 504 Gateway Timeout — the server-side counterpart, where nginx itself times out on the upstream instead of the client giving up first.
- Nginx Error: Upstream Timed Out — the underlying upstream latency that most often drives 499s and 504s alike.
- Nginx Error: Upstream Prematurely Closed Connection — a related mid-request disconnect, but originating at the backend rather than the client.
See the NGINX category for more guides.
Fixed it? Get 500 NGINX & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.