How to fix "502 Bad Gateway" and "504 Gateway Time-out" on Nginx with PHP-FPM
Nginx is fine, but it can't get an answer from PHP. One line in the Nginx error log tells you why. This guide shows how to read it and what to do for each case.
8.4 to your PHP version.What the error means
Nginx doesn't run PHP itself. It passes each PHP request to PHP-FPM and waits for the answer.
- 502 Bad Gateway: Nginx couldn't reach PHP-FPM, or PHP-FPM hung up without a proper answer.
- 504 Gateway Time-out: PHP-FPM took too long. By default, Nginx waits 60 seconds.
A 500 error is different: PHP answered, but your code failed. In our test, a PHP "out of memory" error gave a 500, not a 502.
1. Read the Nginx error log
Load the broken page once, then look at the last lines of the log:
sudo tail -n 20 /var/log/nginx/error.log
If the site has its own log (an error_log line in its Nginx file), look there instead. Find the line with upstream in it. These are the lines we saw, and what each one means:
connect() to unix:/run/php/php8.4-fpm.sock failed (2: No such file or directory): PHP-FPM isn't running, or Nginx points at the wrong version. See step 2.connect() to unix:/run/php/php8.4-fpm.sock failed (13: Permission denied): Nginx isn't allowed to use the socket. See step 3.connect() failed (111: Connection refused): you use a port like127.0.0.1:9000, and nothing listens there. See step 2.upstream sent too big header: the page sends more headers or cookies than Nginx has room for. See step 4.recv() failed (104: Connection reset by peer): PHP-FPM stopped in the middle of the request. See step 5.upstream timed out (110: Connection timed out): this is the 504. See step 6.
Also check the PHP-FPM log. It often shows the other half of the story:
sudo tail -n 20 /var/log/php8.4-fpm.log
2. PHP-FPM isn't running, or Nginx points at the wrong one
First check if PHP-FPM runs, and which versions you have:
systemctl list-units 'php*-fpm*'
ls /run/php/
If it's stopped, start it and look at the answer:
sudo systemctl start php8.4-fpm
If it won't start, the settings have a mistake. Ask PHP-FPM to check them. It prints the exact problem:
sudo php-fpm8.4 -t
In our test, we set pm.max_children lower than pm.max_spare_servers. PHP-FPM refused to start, every page showed 502, and the check said: pm.min_spare_servers(1) and pm.max_spare_servers(3) cannot be greater than pm.max_children(1). journalctl -u php8.4-fpm shows the same line. Run php-fpm8.4 -t after every change, before you restart.
If PHP-FPM runs, compare the socket in your Nginx files with the ones in /run/php/:
sudo grep -Rn fastcgi_pass /etc/nginx/sites-enabled/
Use -R with a capital R. The files in sites-enabled are usually links, and grep -r skips them. In our test, -r found nothing.
This mismatch is the most common cause after an upgrade. The site says php8.2-fpm.sock, but only php8.4-fpm.sock exists. Fix the line, then test and reload Nginx:
sudo nginx -t && sudo systemctl reload nginx
Watch out for /run/php/php-fpm.sock (without a version). On Debian and Ubuntu it's a link to one of the versions, and it stays there even when that PHP is stopped. Point each site at a socket with a version number in it, so you always know which PHP it uses.
If you moved to a new PHP version, see our guide to moving a site to a newer PHP version. If you upgraded the whole server, see upgrading to the next Ubuntu or Debian release.
3. "Permission denied" on the socket
Nginx runs as the user www-data, so it must be allowed to use the socket. Look at who owns it:
ls -l /run/php/php8.4-fpm.sock
It should look like srw-rw---- 1 www-data www-data. The owner comes from these lines in the pool file (such as /etc/php/8.4/fpm/pool.d/www.conf, or your own pool file):
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
Fix them, then restart PHP-FPM. A reload isn't enough. In our test, the socket kept its old owner after a reload, and the 502 stayed until we restarted:
sudo php-fpm8.4 -t && sudo systemctl restart php8.4-fpm
If each site has its own pool, keep the pool's own user for the PHP code and only give the socket to www-data. See our guide to a PHP-FPM pool for each site.
4. "upstream sent too big header"
Some pages send a lot of headers or big cookies, for example with some WordPress plugins or debug tools. Nginx has 4 KB or 8 KB for them by default (it depends on the system). Give it more room in the PHP location block of the site:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_buffer_size 32k;
fastcgi_buffers 16 16k;
}
Then sudo nginx -t && sudo systemctl reload nginx. In our test, a page with 20 KB of headers gave a 502 before this change, and loaded fine after it. If you need much more than 32k, find out why the page sends so much. It's often a cookie that keeps growing.
5. PHP-FPM hung up in the middle
Connection reset by peer means a PHP-FPM worker ended while it was still working on the request. The PHP-FPM log says why. We saw two reasons:
execution timed out (3.98 sec), terminating: the pool has arequest_terminate_timeout, and the page took longer. Make the page faster, or raise the limit in the pool file.child 9534 exited on signal 11 (SIGSEGV): PHP itself crashed. This is usually a broken extension. Update your packages, then turn off extensions you added yourself, one at a time, until it stops.
If the server runs out of memory, Linux may also kill PHP workers. Check with sudo journalctl -k | grep -i "killed process", and see our guide to out-of-memory errors.
6. 504: the page is too slow
upstream timed out means PHP was still working when Nginx stopped waiting. There are two usual reasons.
The page itself is slow. A slow database query, an outside API that doesn't answer, or a big import. Fix the cause where you can: our guide to finding slow database queries helps with the first one. Long jobs, like imports or sending lots of emails, belong in a background worker, not a web request (see Laravel queue workers).
All PHP workers are busy. Each worker handles one request at a time. When all are busy, new visitors wait in line. In our test, with only two workers busy, a simple page took 4.5 seconds instead of a few milliseconds. When the wait was longer than Nginx's limit, it gave a 504. The PHP-FPM log shows it:
sudo grep max_children /var/log/php8.4-fpm.log
A line like server reached pm.max_children setting (2), consider raising it means requests had to wait. Raise pm.max_children only if you have the memory for it. Our OPcache and PHP-FPM guide shows how to work out the right number.
If one admin page really needs longer, raise the limit for that page only, not for the whole site. For example, for a long export:
location = /admin/export.php {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_read_timeout 300s;
}
PHP has its own time limits too (max_execution_time, and request_terminate_timeout in the pool). The shortest limit wins.
Don't hide it, find out about it
A 502 often starts at night, after an update or when a disk fills up. An outside check tells you within minutes, before your visitors do. See our guide to simple uptime monitoring. Also make sure the disk doesn't fill up with logs: see stop logs filling the disk.
Quick list
- No such file or directory: start PHP-FPM, or fix the socket name in the site's Nginx file.
- PHP-FPM won't start: run
php-fpm8.4 -tand fix the line it names. - Permission denied: set
listen.ownerandlisten.grouptowww-data, then restart (not reload). - Connection refused: the port in
fastcgi_passdoesn't match thelistenline in the pool. - Too big header: add
fastcgi_buffer_size 32k;andfastcgi_buffers 16 16k;. - Connection reset by peer: read the PHP-FPM log: a time limit or a crash.
- Timed out (504): make the page faster, or check for
pm.max_childrenin the PHP-FPM log.
Official sources
- Nginx FastCGI module (
fastcgi_pass,fastcgi_buffer_size,fastcgi_read_timeout) - PHP-FPM settings (php.net) (
listen.owner,pm.max_children,request_terminate_timeout) - php-fpm package in Debian 13 and Ubuntu 24.04
Next steps: run through the security checklist for a PHP server, and give each site its own PHP-FPM pool so one busy site can't take the others down.
ServerNest is being built to show when PHP-FPM is down or out of workers, and to point at the cause. Join early access to try it first.