Status 499 is an nginx-specific code. It is not part of the HTTP standard and the client never receives it. nginx writes 499 into the access log when the client closed the connection before nginx could send a response. The visitor already left, so nobody sees an error page; you only see the number in your logs.
A typical line looks like this:
203.0.113.7 - - [08/Oct/2026:10:15:42 +0000] "GET /api/report HTTP/1.1" 499 0 "-" "Mozilla/5.0" rt=30.002 urt=30.001
The 0 bytes sent and a response time close to a round number (30 seconds, 60 seconds) are the signature. The request was waiting for a slow backend and someone gave up.
Is it a problem?
A few 499s are normal: users close tabs, mobile clients lose signal, browsers cancel requests when a user clicks away. A steady, small share of traffic is background noise.
Act when:
- 499s jump suddenly or cluster on one URL.
- They accompany slow pages or a rising backend response time.
- A load balancer, monitor or API client reports timeouts at the same moments.
- Writes (POST, payments, order submission) show 499, because the backend may have completed the work while the client assumed failure.
Causes, most likely first
- Slow upstream. PHP-FPM, Node, a database query or an external API took longer than the client was willing to wait.
- A timeout in front of nginx. A CDN, cloud load balancer or the client library itself closes the connection at its own limit, for example 30 or 60 seconds. nginx then logs 499 for every request that exceeded it.
- Client-side code that cancels. JavaScript
AbortController, search-as-you-type that cancels the previous request, or a mobile app that drops a request when a screen changes. - Health checks and monitors with short timeouts. A probe that waits 2 seconds against an endpoint that sometimes takes 3.
- Impatient users on slow pages. Real users abandon pages that take too long.
Find which requests are affected
Add timings to your access log so you can compare how long each request ran:
log_format timed '$remote_addr "$request" $status $body_bytes_sent '
'rt=$request_time urt=$upstream_response_time';
access_log /var/log/nginx/access.log timed;
Then list the URLs with the most 499s:
awk '$0 ~ / 499 / {print $0}' /var/log/nginx/access.log \
| grep -o '"[A-Z]* [^ ]*' | sort | uniq -c | sort -rn | head -20
Look at rt for those lines. If they cluster around one value, a timeout upstream of nginx or in the client is closing the connection. If they are spread widely, they are likely real abandonment.
The default combined log format does not include timings, so you may need to add the variables above before you can reach a conclusion. Paths are /var/log/nginx/access.log on Debian, Ubuntu and RHEL-family systems alike.
Fix the slow backend
The proper fix is to make the endpoint faster or to move long jobs out of the request path:
- Profile the slow route. Check database queries, remote API calls and lock waits.
- For reports, exports and imports, return quickly with a job ID and let the client poll, or process in a queue.
- Compare with timeouts in nginx upstream timed out and nginx 502 and 504 timeout tuning; if nginx itself gives up before the client, you get 504 instead of 499.
- Check frontend limits. If a CDN or load balancer closes at 30 or 60 seconds, raise its idle timeout or shorten the backend work.
proxy_ignore_client_abort
By default nginx closes the connection to the upstream when the client disconnects. This is the directive proxy_ignore_client_abort, and its default is off. Turning it on makes nginx keep the upstream request running even if the client is gone:
location /api/payments {
proxy_pass http://app_backend;
proxy_ignore_client_abort on;
}
Use this carefully. It is appropriate when an operation must complete once started, such as an idempotent webhook receiver, but it also wastes backend capacity on requests nobody is waiting for. The FastCGI and uwsgi modules have matching directives (fastcgi_ignore_client_abort, uwsgi_ignore_client_abort). It does not remove 499 from logs; it only changes whether the backend continues working.
Rule out your own monitoring
If 499s match your probe's schedule, check the probe's timeout against actual response time. Measure real latency with the Website Speed Test and the HTTP Status Checker, and set monitor timeouts above normal peak latency. An Uptime Monitor that records response time per check helps you see slow drift before it becomes a stream of cancellations.
Next steps
- Diagnose the underlying slowness with Website slow? A diagnosis guide.
- Tune timeouts using nginx 502 and 504 timeout tuning.
- Reduce backend latency with reduce time to first byte.
- Use the Outage Root Cause Finder when slowness turns into failure.
FAQ
Does the client see a 499?
No. The client closed the connection, so it receives nothing. The code exists only in nginx's logs and variables such as $status.
Should I try to eliminate all 499s?
No. Some are natural. Investigate spikes, clusters on one URL, and any case where users report timeouts.
Is 499 an error on my server or theirs?
It is neither strictly. It records that the client left first, but the usual underlying reason is a response that took longer than the client or an intermediate proxy would wait.
Why do I see 499 behind a CDN?
The CDN is the client from nginx's point of view. If the CDN has a shorter timeout than your backend needs, it disconnects and nginx logs 499.
Get notified when this fix updates
Plus occasional practical tips on uptime and monitoring, nothing else. Unsubscribe anytime.
This page is general information written by our editorial team and updated when documentation changes. Servers and providers differ, so test changes safely first. See our editorial policy and disclaimer.