// Engineering Log

HTTPS and the web server in practice: Part 1 — 502, 504, 499, and 413 errors in Nginx: causes and timeouts

Published on 2026-10-28

// Fast route

This article belongs to the topic Servers and infrastructure.

Codes 502, 504, 499 and 413 are almost never returned by the application itself. They are returned by Nginx that sits in front of it: it couldn’t connect to the application, didn’t wait for a response, received a broken connection, or refused to accept an overly large request. Therefore, the cause is searched not in the application code, but at the junction between Nginx and the application — in Nginx’s error log and in timeout coordination. Below — what each code means, which log lines correspond to it, and what to change in the configuration. General information about status codes — in the article “What is HTTP: request, response, methods and status codes”.

First — the logs

In access.log you only see the code; the reason is in error.log (usually /var/log/nginx/error.log). The error line contains the application address (upstream), the request and the system reason:

2026/09/24 10:15:02 [error] 1234#1234: *5678 connect() failed (111: Connection refused)
while connecting to upstream, client: 192.0.2.7, server: example.ru,
request: "GET /api/orders HTTP/2.0", upstream: "http://127.0.0.1:8080/api/orders"

To see the application response time directly in the access log, extend the format:

nginx
log_format upstream '$remote_addr "$request" $status '
                    'rt=$request_time uct=$upstream_connect_time '
                    'urt=$upstream_response_time us=$upstream_status ua=$upstream_addr';
access_log /var/log/nginx/access.log upstream;
  • $request_time — how long Nginx handled the request in total, including sending to the client;
  • $upstream_connect_time — time to connect to the application;
  • $upstream_response_time — how long the application responded;
  • $upstream_status and $upstream_addr — what the application returned and which server. If there are multiple servers and Nginx tried the next one, the values are comma-separated: 502, 200.

502 Bad Gateway

Nginx did not receive a valid response from the application. The reason is determined by the text in error.log.

connect() failed (111: Connection refused) — nothing is listening on the specified address and port. The application is not running, crashed, listening on a different port, or only on 127.0.0.1 while Nginx connects via an external address (a frequent case in Docker: the app listens on 127.0.0.1 inside the container and is unavailable from a neighbor). Checks:

bash
ss -ltnp | grep 8080
curl -v http://127.0.0.1:8080/

connect() to unix:/run/php/php8.3-fpm.sock failed (2: No such file or directory) — the socket is missing: PHP-FPM is not running or a different PHP version is specified in Nginx.

(13: Permission denied) while connecting to upstream — Nginx doesn’t have rights to the socket (check listen.owner and listen.group in the PHP-FPM pool) or, on CentOS, Rocky and RHEL, SELinux forbids the connection: setsebool -P httpd_can_network_connect 1.

(11: Resource temporarily unavailable) while connecting to upstream — the connection queue to the socket is full: all PHP-FPM processes are busy. Look for the PHP-FPM log message server reached pm.max_children setting and increase pm.max_children if memory allows, or look for slow requests.

upstream prematurely closed connection while reading response header from upstream — the application closed the connection without responding. Causes: the process crashed on that request (check the application log, dmesg for OOM killer), the application itself aborted a long request due to its own timeout, or a keepalive race (below).

recv() failed (104: Connection reset by peer) — the application reset the connection. Causes are the same.

upstream sent too big header while reading response header from upstream — response headers don’t fit in the buffer: usually many or large cookies, a long Location header. Increase the buffer:

nginx
proxy_buffer_size 16k;
proxy_buffers 8 16k;
# for PHP-FPM — fastcgi_buffer_size and fastcgi_buffers

no live upstreams while connecting to upstream — all servers in the upstream block are marked unavailable after errors (parameters max_fails and fail_timeout). This is a consequence: look in the log for the first errors before this line.

Keepalive race: 502 every few minutes

If 502s appear rarely, without load and without errors in the application log, it is almost certainly a keepalive race between Nginx and the application.

When keepalive is enabled in an upstream, Nginx keeps connections to the application open and reuses them. The application also closes idle connections — but according to its own timeout. If the application closed the connection at the moment Nginx sends a new request over it, the request gets a broken connection and a 502.

Example: Node.js has server.keepAliveTimeout default of 5 seconds, while Nginx keeps an idle connection to the application up to 60 seconds (keepalive_timeout in the upstream block). After 5 seconds of idle time Node.js closes the connection, Nginx tries to use it — and gets a 502.

Rule: the idle timeout of whoever is closer to the application should be greater than the one in front of it. Either decrease the timeout in Nginx or increase it in the application:

nginx
upstream app {
    server 127.0.0.1:3000;
    keepalive 32;
    keepalive_timeout 4s;    # less than Node.js keepAliveTimeout (5 s)
}

server {
    location / {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Connection "";   # without this, keepalive to the app doesn't work
    }
}
javascript
// or in Node.js: larger than the timeout of whoever is in front of the application
server.keepAliveTimeout = 65000;
server.headersTimeout = 66000;

The same rule applies to the whole chain: cloud load balancer → Nginx → application. AWS ALB has a default idle timeout of 60 seconds, so Nginx’s keepalive_timeout for clients behind it should be greater than 60 seconds.

504 Gateway Timeout

Nginx connected to the application but did not wait for a response within the allotted time:

upstream timed out (110: Connection timed out) while reading response header from upstream

Nginx proxying timeouts:

DirectiveDefaultWhat it limits
proxy_connect_timeout60 sestablishing connection to the application (cannot set more than 75 s)
proxy_send_timeout60 spause between two write operations of the request to the application
proxy_read_timeout60 spause between two read operations of the response — not the total response time
fastcgi_read_timeout60 sthe same for PHP-FPM

What to do:

  1. Find the slow request. 504 is a symptom: the application takes too long. Look at urt= in the log, slow database queries, external APIs without timeouts.

  2. Increase the timeout only where a long response is normal, for example for report exports, not globally:

    nginx
    location /reports/ {
        proxy_pass http://app;
        proxy_read_timeout 300s;
    }
  3. Coordinate timeouts across the whole chain. Increasing proxy_read_timeout is useless if a timeout triggers earlier elsewhere: max_execution_time and request_terminate_timeout in PHP, worker timeout in Gunicorn (–timeout, default 30 s), CDN timeout (on Cloudflare the response must start within 100 seconds, otherwise a 524 error).

  4. Long operations should be run in the background. If a report takes five minutes to build, it’s better to accept the job, return 202 Accepted and provide the result when ready, rather than keeping the HTTP request open.

proxy_connect_timeout triggers only if the application doesn’t respond to connection attempts at all (address unreachable, packets lost) — then the log shows while connecting to upstream. A connection refusal (Connection refused) is immediate and gives 502, not 504.

499 Client Closed Request

A code invented by Nginx: the client closed the connection before waiting for the response. This code is not sent to the client — it is only written in the log.

Reasons:

  • the user closed the tab or navigated away while the page was loading. Isolated 499s are normal;
  • the client has its own timeout shorter than the response time: a mobile app waits 30 seconds, the API responds in 40. Massive 499s with identical $request_time indicate this;
  • a load balancer or CDN in front of Nginx has its own timeout and closes the connection earlier;
  • monitoring checks availability with a short timeout.

By default, when the client goes away, Nginx closes the connection to the application (proxy_ignore_client_abort off), but the application may continue to work and not know that the result is no longer needed. If 499s are widespread — it’s the same problem as 504: slow responses, but the timeout fired at the client.

413 Content Too Large

The request body is larger than allowed. Nginx default is 1 MB, so uploading a photo or document via the site often bumps into this limit first:

client intended to send too large body: 5242880 bytes
nginx
server {
    client_max_body_size 50m;   # for the whole site
    location /upload/ {
        client_max_body_size 500m;   # for uploads — larger
    }
}

There are limits further down the chain, and you need to raise it everywhere:

  • PHP — upload_max_filesize and post_max_size in php.ini;
  • application framework — for example, DATA_UPLOAD_MAX_MEMORY_SIZE in Django, bodyParser in Express;
  • CDN — on Cloudflare Free and Pro plans the request body is limited to 100 MB.

For large files it’s better to upload in parts (tus protocol) or directly upload to object storage via a signed URL, bypassing the application.

Other codes from Nginx

  • 400 Request Header Or Cookie Too Large — headers or cookies don’t fit in the buffer: large_client_header_buffers 4 16k;
  • 503 Service Temporarily Unavailable — often this is not a failure but a rate/connection limit kicking in (limit_req or limit_conn): by default they respond with 503. The log shows limiting requests, excess:. To return the proper 429 code use the directive limit_req_status 429;
  • 500 with rewrite or internal redirection cycle — an endless try_files or rewrite loop.
  • 403 with directory index of “/var/www/…/” is forbidden — a directory was requested, there’s no index.html/index.php inside it, and directory listing is disabled. With (13: Permission denied) — the Nginx user has no permissions to the files.

Cloudflare 52x errors

If the site is behind Cloudflare, errors at the junction of the CDN and your server have their own codes — these are analogues of 502 and 504, but between Cloudflare and Nginx:

CodeWhat it means
520the server returned an empty or unknown response
521the server refused the connection — Nginx is not running or the port is closed to Cloudflare addresses
522connection timed out to the server
523server is unreachable (wrong DNS address)
524connection exists, but the response did not start within 100 seconds
525TLS handshake with the server failed
526invalid certificate on the server in Full (strict) mode

Troubleshooting order

  1. Find the line in error.log by time and address — the error text almost always points to the cause.
  2. Check the application directly, bypassing Nginx: curl -v http://127.0.0.1:8080/path.
  3. Compare $request_time and $upstream_response_time: if the time is spent in the application — look there; if not — in the network or in Nginx.
  4. List timeouts for the whole chain — CDN, load balancer, Nginx, application, database — and check that each next one is coordinated with the previous.
  5. For rare 502s without an obvious cause — check keepalive between Nginx and the application.

// Similar task

If you are dealing with something similar

This article belongs to one of the main working topics. You can keep reading on the topic, go to the homepage to understand what I do, or open the service pages directly.

Article topic

Servers and infrastructure

VPS, Linux, web stack, migrations, hosting, databases, and core operations.

Typical tasks behind this topic

  • Move a site or service to a new server
  • Set up Linux, Nginx, databases, and backups
  • Figure out why the system behaves unstably

// Next step

If you need help with this topic, not just another article, it is better to go straight to the service page. The homepage and topic collection stay available as secondary routes.

Open services

// Reviews

Related reviews

apande

apande

Configuring Nginx and OpenCart

2024-09-07 · ★ 5/5

A very powerful buyer
kireevk

kireevk

Diagnosing Nginx Proxy Manager in a Docker container and resolving the issue

2024-03-15 · ★ 5/5

Settled-in customer

// Contact

Need help?

Get in touch with me and I'll help solve the problem

I reply within one business day (03:00-13:00 GMT)

Или оставьте заявку здесь:

Confirm that you are not a bot.

Write and get a quick reply