// 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:
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:
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:
proxy_buffer_size 16k;
proxy_buffers 8 16k;
# for PHP-FPM — fastcgi_buffer_size and fastcgi_buffersno 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:
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
}
}// 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 upstreamNginx proxying timeouts:
| Directive | Default | What it limits |
|---|---|---|
| proxy_connect_timeout | 60 s | establishing connection to the application (cannot set more than 75 s) |
| proxy_send_timeout | 60 s | pause between two write operations of the request to the application |
| proxy_read_timeout | 60 s | pause between two read operations of the response — not the total response time |
| fastcgi_read_timeout | 60 s | the same for PHP-FPM |
What to do:
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.
Increase the timeout only where a long response is normal, for example for report exports, not globally:
nginxlocation /reports/ { proxy_pass http://app; proxy_read_timeout 300s; }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).
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 bytesserver {
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:
| Code | What it means |
|---|---|
| 520 | the server returned an empty or unknown response |
| 521 | the server refused the connection — Nginx is not running or the port is closed to Cloudflare addresses |
| 522 | connection timed out to the server |
| 523 | server is unreachable (wrong DNS address) |
| 524 | connection exists, but the response did not start within 100 seconds |
| 525 | TLS handshake with the server failed |
| 526 | invalid certificate on the server in Full (strict) mode |
Troubleshooting order
- Find the line in error.log by time and address — the error text almost always points to the cause.
- Check the application directly, bypassing Nginx: curl -v http://127.0.0.1:8080/path.
- 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.
- List timeouts for the whole chain — CDN, load balancer, Nginx, application, database — and check that each next one is coordinated with the previous.
- 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
Thanks to Mikhail for his responsiveness. We spoke by phone; he explained how I could do it myself. This is my second time reaching out — everything’s great and prompt.
Thanks to Mikhail for his responsiveness. We had a call; he explained how I could do it myself. This is my second time contacting him; everything is great and prompt.
Consultation on Nginx Proxy Manager and Portainer
2025-02-25 · ★ 5/5
I want to express my deep gratitude to the specialist who set up SEO-friendly URLs for me on OpenCart. Configuring them turned out to be easy and simple, and I'm glad I finally found a professional who did everything well and without unnecessary complications. Before this I went through four specialists, and each time there were issues with the setup, but this person handled the task perfectly.
I want to express my great gratitude to the specialist who configured SEO-friendly URLs for me on OpenCart. Setting up the URLs turned out to be easy and simple, and I'm glad that I finally found a professional who did …
Excellent work — this isn't the first time I've used them; they find solutions to complex problems. I recommend.
Excellent work, this isn't my first time contacting them; they find solutions to complex problems. I recommend.
Excellent work! He completed the assigned task on time and without errors. It was a pleasure to work with him; I recommend him.
Great job! Completed the assigned task on time and without errors. It was a pleasure to work together, I recommend.
Excellent specialist — he delved into the problem, figured it out, and fixed it. I recommend him.
Excellent specialist, delved into the problem, figured it out, and fixed it. I recommend.
I needed to resolve an SSL certificate issue on a server where the certificate had been issued through Nginx Proxy Manager. Mikhail clarified all the details of my setup and requested access to assess the feasibility of solving the issue, since he hadn't worked with that service before. He quickly figured it out and fixed my problem. Perfect cooperation)
I needed to solve an issue with an SSL certificate on the server that was issued through Ngnix Proxy manager. Mikhail clarified all the details of how everything is set up for me, asked for access to assess the …
Diagnosing Nginx Proxy Manager in a Docker container and resolving the issue
2024-03-15 · ★ 5/5
// 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)
Или оставьте заявку здесь:
// Related