502 Bad Gateway means a server acting as a go-between, usually NginxNginx A fast, popular program that sends web pages to visitors and can pass requests on to apps behind it. More about Nginx →, Apache, a load balancer or a CDN, passed your request to the program behind it and got an invalid answer or none at all. The go-between is working; the thing behind it is not. On most sites that is PHP-FPM, a Node or Python app, or a Docker container.
If you are visiting the site
The problem is on the website’s side, not your device, so clearing your cacheCache A saved copy of something, kept so it does not have to be made or fetched again. Like keeping a printed copy instead of reprinting it for every person who asks. More about Cache → rarely helps. Reload after a minute, since a 502 often lasts only while a service restarts. If it persists for more than a few minutes, the site owner needs to fix it; try again later.
Which server sent the 502?
Look at the error page before touching the server. It tells you where to start.
| Error page looks like | Who sent it | Start at |
|---|---|---|
Plain white page, 502 Bad Gateway and nginx underneath | Your own Nginx | Step 1 |
Proxy Error or Bad Gateway with an Apache footer | Your own Apache | Step 1, then the Apache section |
| Cloudflare page with a Ray ID and “Host Error” | Cloudflare could not get a valid answer from your server | The CDN section |
| A load balancer page (AWS, DigitalOcean, your host) | The balancer found no healthy server behind it | Step 1 on each server |
If you run the site
Work backwards from the go-between. These checks are in the order that finds the cause fastest.
1. Is the backend running?
For PHP sites the backend is PHP-FPMPHP-FPM The part of the server that runs PHP code, the language WordPress and many sites are written in, and hands the finished page to the web server. More about PHP-FPM →. For apps it is the app’s own process.
systemctl status php8.3-fpm
systemctl status php-fpm
The first name is used on Ubuntu and Debian (your version number may differ), the second on AlmaLinux, Rocky and RHEL. If it is stopped or failing, check its configuration, start it, and read why it stopped:
sudo php-fpm8.3 -t
sudo systemctl restart php8.3-fpm
sudo journalctl -u php8.3-fpm -n 50 --no-pager
For a Node, Python or Go app, check its service and confirm something is listening on the portPort A numbered door on a server. Each service listens behind its own door: web pages on 443, email on others, remote login on 22. More about Port → Nginx expects:
sudo systemctl status myapp
sudo ss -ltnp | grep 3000
No output from ss means nothing is listening on port 3000, so Nginx has nowhere to send the request.
2. Read the web server’s error log
The log names the exact failure. Reproduce the error in your browser, then read the last lines:
sudo tail -n 50 /var/log/nginx/error.log
| Log says | Meaning | Fix |
|---|---|---|
connect() to unix:/run/php/... failed (2: No such file or directory) | The socket path in Nginx does not match PHP-FPM’s, or PHP-FPM is stopped | Step 3 |
connect() failed (111: Connection refused) | Nothing is listening on that port | Step 1 |
connect() ... failed (13: Permission denied) | Socket permissions, or SELinux blocking the connection | Steps 4 and 7 |
upstream prematurely closed connection | The backend crashed while handling the request | Steps 5 and 6 |
upstream sent too big header | The response headers did not fit in Nginx’s buffer | Step 8 |
no live upstreams | Every server in an upstream group is marked as down | Step 1 on each server |
3. Check the socket or port matches
Nginx and PHP-FPM must agree on where they meet. Find both sides:
grep -R "fastcgi_pass\|proxy_pass" /etc/nginx/
grep -R "^listen" /etc/php/8.3/fpm/pool.d/ /etc/php-fpm.d/ 2>/dev/null
If Nginx says fastcgi_pass unix:/run/php/php8.3-fpm.sock; then the pool must say listen = /run/php/php8.3-fpm.sock. A common cause is upgrading PHP: the new version creates a socket with a new name, and Nginx still points at the old one. After changing either file, test and reload:
sudo nginx -t && sudo systemctl reload nginx
4. Check socket permissions
If the log says Permission denied on a socket, Nginx’s user cannot open it. In the PHP-FPM pool file, set the owner to the user Nginx runs as (www-data on Ubuntu, nginx on RHEL-based systems):
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
Restart PHP-FPM afterwards so it recreates the socket.
5. Has PHP-FPM run out of workers?
Each request needs a free PHP-FPM worker. When all are busy, new requests queue and may fail. The PHP-FPM log shows it:
sudo grep "max_children" /var/log/php8.3-fpm.log /var/log/php-fpm/error.log 2>/dev/null
A line like server reached pm.max_children setting (5), consider raising it confirms it. Raise pm.max_children in the pool file, but only as far as memory allows. A typical WordPress worker uses 40 to 80 MB, so a 2 GB server with the database on the same machine should stay around 10 to 15.
6. Check memory
When a server runs out of memory, the kernel kills processes, and PHP or the app is often the first to go.
free -h
sudo dmesg -T | grep -i "killed process"
If you see killed processes, the fix is one of: add memory, add swap, lower pm.max_children, or find the plugin or job using the memory.
7. SELinux on RHEL-based systems
On AlmaLinux, Rocky and RHEL, SELinux stops Nginx from connecting to network ports by default, so an app on port 3000 returns 502 even though it is running. Check for a denial, then allow it:
sudo ausearch -m avc -ts recent
sudo setsebool -P httpd_can_network_connect 1
8. Headers too big for Nginx’s buffer
Sites that set many cookies, or plugins that send large headers, can overflow Nginx’s default buffer. The log says upstream sent too big header. Raise the buffers in the location block that passes to PHP:
fastcgi_buffer_size 32k;
fastcgi_buffers 16 16k;
For apps behind proxy_pass, the equivalents are proxy_buffer_size and proxy_buffers.
On Apache
With ApacheApache One of the most common programs that sends web pages to visitors. It is the waiter between your website’s files and the people asking for them. More about Apache → and PHP-FPM (mod_proxy_fcgi), the same failures show in Apache’s error log as AH01079: failed to make connection to backend or AH01067: Failed to read FastCGI header.
sudo tail -n 50 /var/log/apache2/error.log
sudo tail -n 50 /var/log/httpd/error_log
The first path is Ubuntu and Debian, the second RHEL-based. The checks above apply unchanged: is PHP-FPM running, and does the SetHandler "proxy:unix:..." path match its socket.
In Docker
When Nginx proxies to a container, proxy_pass http://app:3000; only works while that container is running and on the same Docker network.
docker ps -a
docker logs --tail 50 app
A container in a restart loop shows Restarting in docker ps. Its logs say why. Nginx also resolves container names only when it starts, so after recreating a container you may need to restart the Nginx container too.
Behind a CDN or load balancer
A Cloudflare-branded 502 or 504 means Cloudflare reached your server but did not get a valid answer, or could not connect at all. Check, in order:
- The server is up and the site loads when you bypass the CDNCDN A network of servers around the world that keep copies of your site’s files, so each visitor gets them from somewhere nearby. Faster pages, less work for your server. More about CDN →:
curl -I --resolve example.com:443:YOUR_SERVER_IP https://example.com - The DNSDNS The internet’s phone book. It turns a name people can remember, like example.com, into the number address computers use to find the server. More about DNS → record in the CDN points at the right IP address.
- The server firewallFirewall A gatekeeper that decides which connections are allowed into a server and blocks the rest. More about Firewall → allows the CDN’s IP ranges on ports 80 and 443.
- The CDN’s SSL mode matches your server. “Full (strict)” needs a valid certificate on the server.
For a load balancerLoad balancer A traffic controller that shares visitors between several servers, so no single one gets overwhelmed and the site stays up if one fails. More about Load balancer →, its health check page must return 200. If the check path is broken, the balancer marks every server as down and returns 502 for all of them.
On shared hosting
You cannot restart services yourself on shared hostingShared hosting Hosting where your website shares one server with many others. Cheap and simple; the host looks after the server. More about Shared hosting →. A 502 there is usually a PHP process killed for using too much memory or CPU. Check your control panel’s resource usage page (in cPanel, Resource Usage). If the limits are being hit, disable recently added plugins, then contact your host with the time the error happened.
Confirm it is fixed
Request the page headers from the command line, which skips your browser cache:
curl -I https://example.com
HTTP/2 200 means it works. Then watch the error log for a few minutes while you load pages, to make sure the cause has really gone:
sudo tail -f /var/log/nginx/error.log
Stop it happening again
- Set services to restart on failure. In a systemd unit,
Restart=on-failurebrings a crashed app back in seconds. - Watch memory. Most repeat 502s are a server that is too small for its traffic.
- Add an uptimeUptime How much of the time a website or server is working and reachable, usually shown as a percentage like 99.9%. More about Uptime → check that alerts you, so you hear about a 502 before your visitors do.
502, 503 or 504?
| Code | What happened |
|---|---|
| 502 Bad Gateway | The backend gave no answer or a broken one |
| 503 Service Unavailable | The server is up but refusing work: maintenance or overload |
| 504 Gateway Timeout | The backend answered, but too late |
Official documentation
- 502 Bad Gateway, MDN Web Docs.
- ngx_http_proxy_module, nginx documentation.
Related
- What a reverse proxy is, the setup that makes 502s possible.
Something out of date? Software changes. If a step no longer works, tell us and we will check it and update the page.



