Fix Nginx Error: upstream timed out (110: Connection timed out) while reading response header

intermediate Nginx2026-05-02| Nginx acting as a reverse proxy or FastCGI gateway on Linux servers (Ubuntu, Debian, RHEL) with backends like PHP-FPM, Gunicorn, or Node.js.

Error Message

upstream timed out (110: Connection timed out) while reading response header from upstream
#nginx#upstream#timeout#proxy#php-fpm#gunicorn

The Scenario: When 60 Seconds Isn't EnoughA 504 Gateway Timeout in the browser is just a symptom. The real story lives in your Nginx error logs. I recently ran into this while building a reporting feature that exports 50MB PDF files. Small reports worked instantly, but as soon as the dataset grew to 5,000+ rows, the request would hang for exactly 60 seconds and then die.

A quick look at /var/log/nginx/error.log showed the problem:

[error] 1234#0: *567 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 1.2.3.4, server: example.com, request: "POST /api/export HTTP/1.1", upstream: "http://127.0.0.1:8000/api/export"

This means Nginx connected to your backend—whether that's PHP-FPM, Gunicorn, or Node—and sent the request. However, the backend didn't send a single byte of the response headers back before the timer ran out.

Analysis: The 60-Second WallNginx is patient, but only to a point. By default, it waits 60 seconds for an upstream response. If your app is busy processing a massive database query or fetching data from a sluggish third-party API, it will hit this wall. The "110: Connection timed out" error is a specific network signal. It tells you the internal connection between Nginx and your application stayed open but silent for too long.

The Immediate Fix: Extending TimeoutsYou need to give Nginx more breathing room. The specific directive you need depends on how Nginx talks to your app. I usually set these to 300 seconds (5 minutes) for heavy-duty endpoints.

For Reverse Proxy (Node.js, Gunicorn, Go)If you use proxy_pass, update your location block. Increasing all three timeout values ensures the connection doesn't drop during any phase of the request.

location /api/ {
    proxy_pass http://localhost:8000;
    proxy_read_timeout 300s;
    proxy_connect_timeout 300s;
    proxy_send_timeout 300s;
}

For PHP-FPM (FastCGI)PHP setups use the FastCGI protocol. You'll need the fastcgi_read_timeout directive here. This is common for long-running Laravel or WordPress scripts.

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
    fastcgi_read_timeout 300s;
}

For Python/UWSGI```

location / { include uwsgi_params; uwsgi_pass unix:/tmp/app.sock; uwsgi_read_timeout 300s; }


Reload Nginx to apply the changes:

sudo nginx -t && sudo systemctl reload nginx


## The Missing Link: Backend ConfigChanging Nginx is only one side of the coin. If your backend has its own 30-second timeout, it will kill the process before Nginx even finishes waiting. You must sync these limits.
### Updating PHP-FPMEdit your `php.ini` to allow longer scripts:

max_execution_time = 300


Also, check `/etc/php/8.2/fpm/pool.d/www.conf` for this line:

request_terminate_timeout = 300


### Updating Gunicorn (Python)Gunicorn defaults to a tiny 30-second timeout. If you don't change this, Nginx will report a 'Bad Gateway' or timeout when Gunicorn kills the worker. Start it like this:

gunicorn --timeout 300 myapp.wsgi:application


## Verification: Test the New LimitDon't just trust the config. Use `curl` to see exactly how long your server holds the connection:

time curl -I http://example.com/api/heavy-task


Watch the 'real' time output. If it goes past 60 seconds and returns a `200 OK`, you've fixed it. If it still fails at the 300-second mark, your task is simply too slow for a standard web request.
## Best PracticesBumping timeouts is a band-aid. Users hate staring at a loading spinner for five minutes. For heavy tasks, move the logic to a background worker like Celery or Redis Queue. Let the user trigger the job, receive a 'Job Started' message, and poll for the result later. It’s more complex to build but much more reliable.

Related Error Notes