Website down: fix 502, 503 and 504 errors fast
A 502, 503 or 504 page means the web server is up but cannot get an answer from the application behind it. The error log almost always says why. Read it before you restart anything, then fix the cause: a stopped service, a wrong socket, a full pool, a slow database, a full disk or too little memory.
Do this first.
- Read the last 50 lines of the web server error log:
sudo tail -n 50 /var/log/nginx/error.log. - Check that nginx and PHP-FPM (or your app) are running:
systemctl status nginx 'php*-fpm*'. - Check disk and memory:
df -handfree -h. - If a restart is needed, test the configuration first with
sudo nginx -t.
What each code means
| Code | Meaning | Usual cause |
|---|---|---|
| 502 Bad Gateway | The upstream refused, closed or does not exist | PHP-FPM or app stopped, wrong socket path, socket permissions, app crashed, all upstream servers marked down |
| 503 Service Unavailable | Nginx itself refused the request | Rate or connection limits (limit_req, limit_conn), or an application in maintenance mode passing on its own 503 |
| 504 Gateway Timeout | The upstream accepted but did not answer in time | Slow PHP or database query, full PHP-FPM pool, timeout too short for a known long task |
| 429 Too Many Requests | A rate limit, if configured to use 429 | The same limits as 503, with limit_req_status 429 |
Step 1: are the services running?
systemctl status nginx
# Debian/Ubuntu: one service per PHP version (php8.3-fpm, php8.4-fpm, ...)
systemctl list-units 'php*-fpm*'
# RHEL, Rocky, AlmaLinux, Fedora
systemctl status php-fpm
# Why did it stop? Last log lines of the unit
sudo journalctl -u php8.4-fpm --since "-1 hour" # use your unit name
If a service is stopped, test its configuration before you start it, so a typo does not take down the rest:
sudo nginx -t
sudo php-fpm8.4 -t # Debian/Ubuntu (match your version)
sudo php-fpm -t # RHEL family
sudo systemctl restart php8.4-fpm # restarts PHP-FPM: running requests are cut off
sudo systemctl reload nginx # graceful, no dropped connections
Step 2: read the error log
sudo tail -n 100 /var/log/nginx/error.log
sudo grep -E 'upstream|connect\(\)' /var/log/nginx/error.log | tail -n 20
Match the message to its fix:
- connect() to unix:/run/php/... failed (2: No such file or directory)
- The socket in
fastcgi_passdoes not exist. PHP-FPM is stopped, or nginx points at an old PHP version’s socket after an upgrade. Comparels /run/php/(Debian/Ubuntu) orls /run/php-fpm/(RHEL) withsudo grep -rn fastcgi_pass /etc/nginx/, and with thelisten =line in the pool file (/etc/php/8.x/fpm/pool.d/www.confor/etc/php-fpm.d/www.conf). - (13: Permission denied) while connecting to upstream
- The nginx user cannot open the socket. Set
listen.ownerandlisten.groupin the pool to the nginx user (www-dataon Debian/Ubuntu,nginxon RHEL) withlisten.mode = 0660, then restart PHP-FPM. On RHEL with SELinux, a proxy to a TCP port can be denied by policy; checksudo ausearch -m avc -ts recent. If the denial is forname_connect, the usual fix issudo setsebool -P httpd_can_network_connect 1. Do not disable SELinux. - (111: Connection refused) while connecting to upstream
- Nothing listens on that address and port. The app (Node, Python, Java, Docker container) is down or listens elsewhere. Check with
sudo ss -ltnp. - upstream timed out (110: Connection timed out) while reading response header
- The 504 case. The upstream took longer than
fastcgi_read_timeoutorproxy_read_timeout(both default to 60 seconds) without sending anything. Find the slow part (step 3) rather than raising the timeout first. - limiting requests, excess: ... by zone
- The 503 (or 429) case. A
limit_reqrule rejected the request. See step 5.
Step 3: full pool or slow backend
Each PHP-FPM child handles one request at a time, and pm.max_children caps how many run at once. When all are busy, new requests wait and then time out. PHP-FPM logs a warning when it hits the cap:
# Debian/Ubuntu log; on RHEL use /var/log/php-fpm/error.log
sudo grep -i 'max_children' /var/log/php8.4-fpm.log | tail
# How many PHP-FPM processes, and how much memory each uses (RSS in KB)
ps -C php-fpm8.4 -o pid,rss,etime,args --sort=-rss | head # RHEL: ps -C php-fpm ...
Do not just raise pm.max_children. Multiply it by the memory per child: 20 children at 80 MB each need 1,600 MB. If that does not fit in free RAM, the server will swap or the kernel will kill processes. Find out why requests are slow instead. Set request_slowlog_timeout = 5s and a slowlog path in the pool to get a backtrace of every request slower than five seconds. Check the database for long-running queries (SHOW FULL PROCESSLIST; in MySQL/MariaDB, pg_stat_activity in PostgreSQL). A sudden flood of requests is covered in High load or out of memory and DDoS and floods.
Raise timeouts only for a known slow task, such as an export, and only in that location:
location = /admin/export.php {
fastcgi_read_timeout 300s;
include fastcgi_params; # plus your usual fastcgi settings
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
}
Step 4: disk and memory
df -h # a full disk stops logs, sessions, caches and databases
df -i # out of inodes looks like a full disk
free -h
sudo journalctl -k --since "-6 hours" | grep -i -E 'out of memory|killed process'
If the kernel’s out-of-memory killer ended PHP-FPM, MySQL or your app, that explains a sudden 502. Reduce pm.max_children, find the memory-hungry process, or add RAM. For a full disk, see Disk full. If the database itself will not start, see Database won't start.
Step 5: rate limits returning 503 or 429
The nginx limit_req and limit_conn modules return 503 by default when a client goes over the limit. A limit that is too strict blocks real users, especially behind a proxy or CDN where all visitors seem to come from a few IP addresses.
sudo grep -rn -E 'limit_req|limit_conn' /etc/nginx/
- Use
limit_req_dry_run on;to count excess requests in the log without rejecting them while you tune the rate. - Add
burst=so short peaks are queued rather than refused, as inlimit_req zone=one burst=5;. - Set
limit_req_status 429;so clients and your monitoring can tell a limit from a real outage. - Behind a CDN or load balancer, make sure the key uses the real client address (the realip module), not the proxy’s.
A 503 can also come from a maintenance-mode file left behind by an application update. Check the application’s own docs.
Confirm it is fixed
curl -sS -o /dev/null -w '%{http_code} %{time_total}s\n' https://example.com/
sudo tail -f /var/log/nginx/error.log # should stay quiet
- The site returns 200 for the home page and a PHP or app page.
- No new upstream errors in the error log for 15 minutes.
- Memory and disk have headroom.
Prevent it next time
- Monitor the site from outside and alert on 5xx errors, not only on “host is up”.
- Enable the PHP-FPM
pm.status_pathandping.pathpages (restricted to localhost) to watch active and idle workers. - After a PHP upgrade, check every
fastcgi_passpath before you remove the old version. - Always run
nginx -tbefore a reload, and keep a copy of the working configuration.
When to get help
Call your host if the server itself is unreachable, if the network or load balancer is theirs, or if a hardware fault appears in the kernel log. If you find unknown processes or files while you investigate, stop and follow Signs of compromise.
Sources
- Command-line parameters (nginx -t, -s reload), nginx documentation
- Module ngx_http_fastcgi_module, nginx documentation
- Module ngx_http_proxy_module, nginx documentation
- Module ngx_http_limit_req_module, nginx documentation
- Module ngx_http_limit_conn_module, nginx documentation
- FPM configuration, PHP Manual