Skip to content
RU

upstream prematurely closed connection while reading response header from upstream

What it means: The backend closed the connection before nginx received its complete response, so nginx returned 502 or cut the reply short.

  1. Note whether it says header or upstream
  2. Check backend logs for crashes and kills
  3. Compare app timeouts with nginx timeouts
  4. Fix keep-alive settings to the backend

Test your URL now

Requests the address and shows the status code and what the server sends back.

What the message means

nginx logs upstream prematurely closed connection when a read from the backend socket returns end-of-file before the response is complete (ngx_http_upstream.c). The backend did not send a reset; it closed the connection normally, just too early. The text after while is the phase nginx was in, and it decides what the client sees:

Log endingPhaseClient sees
while reading response header from upstreamNothing received yet, or headers incomplete502 Bad Gateway, or the response of the next server if proxy_next_upstream allows a retry
while reading upstreamHeaders already forwarded, body in progressA truncated response; the status was already sent, so nginx can only close the connection

The second case is why browsers sometimes show ERR_INCOMPLETE_CHUNKED_ENCODING or an empty download instead of a 502. The nginx documentation is explicit: "passing a request to the next server is only possible if nothing has been sent to a client yet" (proxy_next_upstream).

Do not confuse it with recv() failed (104: Connection reset by peer): that is a TCP RST. Here the backend sent a FIN.

Common causes

CauseHow to recognise itFix
Worker killed by its own timeoutgunicorn logs WORKER TIMEOUT; its default timeout is 30 s, after which a silent worker is killed and restartedSpeed up the request or raise the app server timeout above the real duration
Process crashed or OOM-killeddmesg -T | grep -i 'killed process', segfaults, container restartsFix the crash or give the process more memory
PHP-FPM request terminatedFPM log shows a child exited after request_terminate_timeoutAlign the FPM limit with fastcgi_read_timeout and the script runtime
Keep-alive raceSporadic, low traffic, with keepalive enabled in the upstream block; the backend closes idle connections just as nginx reuses oneMake the backend's keep-alive timeout longer than nginx's idle reuse, or shorten keepalive_timeout in the upstream block
Protocol mismatchAlways fails: proxy_pass http:// to a TLS port, or HTTP to a non-HTTP serviceUse the right scheme and port
Backend limit on request size or headersFails only for large uploads or long cookiesRaise the limit in the app server

Behind PHP-FPM, Node.js, gunicorn or uWSGI

The same log line appears for FastCGI and uwsgi upstreams, because they share the upstream core. A useful habit: the app server's timeout should be longer than the slowest legitimate request, and nginx's proxy_read_timeout (60 s by default, measured between two reads, not for the whole response) should be longer still, so the proxy times out last and logs a clear upstream timed out instead of a confusing early close.

For Node.js, an HTTP server closes idle keep-alive sockets after server.keepAliveTimeout; current releases use 5 seconds, with a change to 65 seconds pending upstream (Node.js http docs). If nginx keeps idle upstream connections longer than the app does, the race above is likely. Set the app's keep-alive timeout explicitly rather than relying on the default of whatever version you run.

How to fix it

  1. Correlate timestamps.
    grep 'prematurely closed' /var/log/nginx/error.log | tail -5
    journalctl -u myapp --since '-10 min' | grep -iE 'timeout|killed|error|exit'
    A worker timeout or restart at the same second is the answer.
  2. Reproduce without nginx against the backend port: curl -sv http://127.0.0.1:8000/slow-endpoint -o /dev/null. curl reports Empty reply from server when the backend closes without answering.
  3. Measure the request duration and compare with the app server timeout. Requests dying at exactly 30 s on gunicorn are the default worker timeout.
  4. Check keep-alive. If the upstream block has keepalive, make sure the location sets proxy_http_version 1.1; and proxy_set_header Connection "";, and that the backend's idle timeout exceeds nginx's.
  5. Let nginx retry idempotent requests on another server: proxy_next_upstream error timeout; is the default; POST requests are not retried unless you add non_idempotent.

A general walkthrough of nginx error logs: how to debug nginx errors.

How to confirm the fix

for i in $(seq 1 30); do curl -s -o /dev/null -w '%{http_code} %{size_download}\n' https://example.com/slow-endpoint; done | sort | uniq -c
#      30 200 48213

Every response must have the same status and full size; varying sizes mean truncation is still happening. The error log should stay free of prematurely closed. To see what your public URL returns from outside, use the HTTP status checker.

Related errors

60+ Status CodesComplete HTTP status code reference
Search by CodeQuick search for 200, 301, 404, 500
Categories 1xx–5xxInformational, success, redirects, errors
Examples & CausesWhen it occurs and how to fix it

Why teams trust us

60+
status codes
1xx-5xx
all categories
SEO
ranking impact
Free
online reference

How to use the reference

1

Enter code or keyword

2

Find description and causes

3

Apply recommendations

HTTP Status Codes Reference

HTTP status codes are three-digit numbers that the server returns for every request. They tell the browser and search crawlers what happened: success, redirect, or error.

Quick Search

Enter a code or keyword — instant result with description.

All Categories

1xx Informational, 2xx Success, 3xx Redirect, 4xx Client Error, 5xx Server Error.

Causes & Fixes

For each code: typical causes and concrete steps to resolve.

SEO Impact

How each code affects indexing and link juice transfer in Google.

Who uses this

Developers

choosing correct status

SEO

redirect optimization

DevOps

5xx error diagnostics

Marketers

landing page check

Common Mistakes

Confusing 301 and 302301 is permanent (passes SEO equity), 302 is temporary (does not).
Returning 200 on error"Soft 404s" — a 404 page with status 200 — confuse search engines and stay indexed.
Ignoring 5xx errors500, 503 are server failures. Prolonged episodes remove pages from the index.
Redirect chainsA→B→C instead of A→C slows loading and dilutes SEO equity. Simplify chains.

Best Practices

Use 301 for permanent redirectsThis passes 90–99% of link equity to the new URL.
Return 404 for missing pagesDon't mask 404 as 200 — it hurts SEO and UX.
Monitor 5xx in real timeSet up an HTTP monitor that alerts on 5xx.
Use 410 instead of 404410 Gone means "page permanently removed" — Google de-indexes faster.

Monitor HTTP statuses automatically

HTTP monitor alerts when response code changes or 5xx appears.

Sign up free

Learn more

Sources

Frequently Asked Questions

What is the difference between "while reading response header from upstream" and "while reading upstream"?

The first means the backend closed before nginx had the response headers, so the client gets 502. The second means headers were already sent, so the client gets a truncated response instead of an error page.

Is this an nginx bug?

No. nginx reports that the backend closed its side of the connection. The cause is in the application, its server or the keep-alive settings between them.

Why does it happen only on long requests?

A timeout inside the application stack, such as gunicorn's 30-second worker timeout or PHP-FPM's request_terminate_timeout, kills the worker before it finishes, and nginx sees the connection close.

Why does it happen at random under low traffic?

That pattern points to a keep-alive race: the backend closes an idle connection at the same moment nginx reuses it for a new request.

Will raising proxy_read_timeout fix it?

Only if nginx itself was the one timing out, which logs "upstream timed out" instead. A premature close comes from the backend, so its own timeout or crash has to be fixed.

Try the live tool that powered this guide

Free plan — 10 monitors, checks every 5 min, no card required. Upgrade for 1-minute interval and multi-region monitoring.