Nginx returns 499 on slow client uploads because client_body_timeout fires before the proxy reads the body

2026-11-13 · 3 min read

Nginx returns 499 on slow client uploads because client_body_timeout fires before the proxy reads the body

A phone upload of a 40 MB video stalls at 70%. Nginx writes 499 to the access log, the app shows a generic failure, and the upstream service never receives a complete body. The user retries on the same weak connection, the support ticket grows, and the backend gets another partial request.

That pattern usually starts with client_body_timeout, not with the upstream app.

499 is a symptom, not the upload result

Nginx logs 499 when the client closes the connection before Nginx sends a response [link needed]. On uploads, that often means the client gave up while Nginx was still waiting for the next chunk of the request body.

client_body_timeout applies between successive reads from the client body; it is not a total upload deadline [link needed]. A slow but steady client can take minutes without tripping it. A client that goes quiet for longer than the configured gap can trip it.

With proxy_request_buffering on, Nginx buffers the request body before forwarding it to the proxied server [link needed]. If the body read times out first, the proxy never gets a complete body. You see a failed upload and a 499, while the upstream may see nothing or a truncated request.

Diagnose nginx 499 client_body_timeout before you raise limits

Treat nginx 499 client_body_timeout as a per-read gap problem, not a “make uploads faster” problem. Run this triage:

1. Reproduce with a throttled upload and capture logs:

```bash

curl --limit-rate 20k -T large.bin https://example.com/upload -v

```

Confirm the access log shows 499 and the error log shows a timeout while reading the client request body [link needed].

2. Inspect the effective config:

```bash

nginx -T | grep -E 'client_body_timeout|proxy_request_buffering|client_max_body_size'

```

Do not assume the value in one file is the value Nginx uses.

3. Compare the timeout to the worst-case gap between reads, not the total upload time. If buffering is on, Nginx must finish reading the body before proxy_pass [link needed].

4. In staging, set a location-specific value:

```nginx

location /upload/ {

client_body_timeout 120s;

proxy_request_buffering off; # only if the upstream can stream

proxy_pass http://app;

}

```

5. Re-run the throttled upload. Verify the upstream receives the full body, then reload:

```bash

nginx -t && nginx -s reload

```

Fix the timeout and buffering pair

Raise client_body_timeout only where large or slow uploads land. A global jump from the default 60s to several minutes broadens exposure to slow clients holding connections [link needed]. A location-scoped value is easier to reason about.

If the upstream can consume a streamed body, proxy_request_buffering off lets Nginx forward bytes as they arrive instead of waiting for the full body [link needed]. It does not remove client_body_timeout; it changes when the upstream sees data. Keep buffering on if the upstream is slow, needs a complete body, or cannot handle chunked requests well.

Check adjacent limits too: client_max_body_size controls size, not time; proxy_read_timeout controls upstream response reads, not client uploads; send_timeout controls writes back to the client.

When NOT to do this

Do not disable proxy_request_buffering globally just because one upload route is slow. Do not set client_body_timeout to hours unless your slow-client risk is acceptable. Do not tune timeouts when the 499 is actually a user cancel, a mobile network drop, or a client-side timeout that happens before Nginx’s body timeout. And do not raise the timeout if the real failure is an upstream that reads too slowly and creates backpressure.

Checklist