What a 502 Bad Gateway error means
A 502 Bad Gateway means a server acting as a gateway or proxy passed your request to a server behind it and got back something it could not use. RFC 9110 defines 502 as an invalid response from the upstream server. If the proxy gets no answer at all within its time limit, the code is 504 Gateway Timeout instead.
So a 502 rarely means the front web server is broken. The thing behind it is down, crashed mid-request, listening somewhere else, or sent a reply the proxy rejected. Typical pairs are:
- Nginx in front of PHP-FPM, the usual WordPress or Laravel stack on a VPS.
- Nginx as a reverse proxy to Node.js, Gunicorn or Uvicorn on a local port.
- Apache or LiteSpeed handing PHP to a separate handler or proxying to a backend with
mod_proxy. - Cloudflare or another CDN in front of your origin server, which adds one more hop that can fail.
Quick-fix checklist for site owners
- Reload the page once or twice. A 502 that clears in seconds was usually a backend restart or deploy.
- Read the last lines of the proxy's error log. On Nginx:
sudo tail -n 50 /var/log/nginx/error.log. The message tells you which cause you have. - Check the backend is running:
systemctl status php8.3-fpm(match your PHP version; on AlmaLinux and Rocky the unit isphp-fpm) orsystemctl status yourapp. - Check it is listening where the proxy expects:
sudo ss -ltnpfor TCP ports,ls -l /run/php/for PHP-FPM sockets on Ubuntu and Debian (/run/php-fpm/on AlmaLinux and Rocky). - Look for out-of-memory kills:
sudo dmesg -T | grep -i -E "killed process|out of memory". - Restart the backend, then test the configuration and reload the proxy:
sudo nginx -t && sudo systemctl reload nginx.
Reading the Nginx error log
Each 502 leaves a line in error.log. The common messages and what they point to:
| Log message (shortened) | What it means | Where to look |
|---|---|---|
connect() failed (111: Connection refused) while connecting to upstream | Nothing is listening on that IP and port | Backend stopped, or wrong port in proxy_pass |
connect() to unix:/run/php/... failed (2: No such file or directory) | The socket file does not exist | PHP-FPM stopped, or socket path or PHP version mismatch |
connect() to unix:... failed (13: Permission denied) | Nginx cannot open the socket | listen.owner, listen.group, listen.mode, or SELinux |
upstream prematurely closed connection while reading response header | The backend accepted the request, then hung up | Crash, OOM kill, worker timeout, fatal error |
upstream sent too big header while reading response header | Response headers exceed the buffer | proxy_buffer_size or fastcgi_buffer_size |
no live upstreams while connecting to upstream | Every server in an upstream group is marked failed | All backends unhealthy; fix them first |
Cause 1: the backend is down
PHP-FPM, Node or Gunicorn has stopped, failed to start after a reboot, or is in a restart loop. Check and start it:
sudo systemctl status php8.3-fpm
sudo journalctl -u php8.3-fpm -n 50 --no-pager
sudo systemctl enable --now php8.3-fpm
For an app under systemd, use your unit name. If the service exits straight away, the journal shows why: a pool file syntax error, a missing environment variable or a port already in use.
Cause 2: wrong socket or port in the proxy config
The proxy and the backend must agree on one address. In PHP-FPM, the pool's listen setting (for example in /etc/php/8.3/fpm/pool.d/www.conf on Ubuntu) can be an ip:port, a bare port or a Unix socket path. Nginx must point at the same thing:
# PHP-FPM pool
listen = /run/php/php8.3-fpm.sock
# Nginx server block
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
A classic trigger is upgrading PHP: the new version creates php8.3-fpm.sock while Nginx still points at php8.1-fpm.sock. For a reverse proxy to an app, confirm the port with ss -ltnp and match it:
location / {
proxy_pass http://127.0.0.1:3000;
}
Watch for an app that listens only on IPv6 ::1 while Nginx connects to 127.0.0.1, or the reverse.
If the log says Permission denied on a socket, the Nginx user (www-data on Ubuntu, nginx on AlmaLinux) cannot read and write it. Set listen.owner and listen.group to that user in the pool file (with listen.mode = 0660) and restart PHP-FPM. If the pool sets listen.acl_users, as the AlmaLinux default does, owner and group are ignored and the Nginx user must be in that list instead.
Cause 3: PHP-FPM pool exhausted
pm.max_children caps how many requests a pool serves at once. When every worker is busy, new connections wait in the socket's listen backlog. Under a traffic spike or a slow database that backlog fills and Nginx logs a failed connect, often (11: Resource temporarily unavailable) on a Unix socket, which gives a 502; if requests wait too long instead, you get a 504. The PHP-FPM log says so plainly:
sudo grep max_children /var/log/php8.3-fpm.log
WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
Raise pm.max_children only as far as memory allows. Check a worker's size with ps -o rss= -C php-fpm8.3 (KiB per process; php-fpm on AlmaLinux), then divide the RAM you can spare for PHP by that figure. Going beyond what RAM holds pushes the server into swap and causes more 502s. Fixing slow queries frees workers sooner.
Cause 4: the backend crashes or is killed
upstream prematurely closed connection means the backend took the request and then disappeared. Common reasons:
- Out of memory. The kernel's OOM killer ends the biggest process.
sudo dmesg -TshowsKilled processlines. Add RAM or swap, or cut worker counts. - Worker timeouts. PHP-FPM's
request_terminate_timeoutor Gunicorn's worker timeout kills a long request; the proxy sees the connection drop. Move long jobs to a queue or a cron job rather than raising timeouts without limit. - Fatal errors or segfaults in an extension or app code. Check the PHP-FPM log and the app's own log for the same timestamp.
Cause 5: response headers too big
Nginx reads the first part of the upstream reply, which holds the headers, into a buffer of one memory page (4K or 8K) by default. If headers overflow it, Nginx treats the response as invalid and returns 502 with upstream sent too big header. Raise the buffer for the module you use:
# reverse proxy
proxy_buffer_size 16k;
proxy_buffers 8 16k;
# PHP-FPM
fastcgi_buffer_size 16k;
fastcgi_buffers 8 16k;
Cause 6: SELinux blocks the connection
On AlmaLinux, Rocky or other Red Hat-family systems with SELinux enforcing, Nginx runs in the httpd_t domain, which by default cannot connect to arbitrary backend ports such as 3000 or 8000. The log shows (13: Permission denied) while connecting to upstream even though the app is running. Confirm with sudo ausearch -m avc -ts recent, then allow it:
sudo setsebool -P httpd_can_network_connect 1
Do not disable SELinux to make the error go away; the boolean is the narrow fix.
Apache, LiteSpeed and shared hosting
Apache proxies to backends through mod_proxy, for example ProxyPass "/" "http://127.0.0.1:3000/". The same causes apply, with one difference: when Apache cannot connect to the backend at all, it usually returns 503 Service Unavailable rather than 502 and logs AH01114: HTTP: failed to make connection to backend. On shared cPanel hosting you do not control the web server, so a 502 there is usually a PHP process hitting the account's limits or a server-side fault; check the error log in cPanel and contact your host with the time and URL.
Cloudflare 502: which side failed?
With Cloudflare in front, look at the error page. Cloudflare's documentation says a Cloudflare-branded 502 or 504 page means your origin returned the error, so work through the causes above on the origin. A blank, unbranded 502 page is listed as coming from Cloudflare's side, though broken compression at the origin, such as gzip content with a wrong Content-Length, can also cause it, so try turning origin compression off. If you raise it with Cloudflare, include the time with time zone, the failing URL and the output of /cdn-cgi/trace on your domain.
Visitor side vs site owner side
As a visitor, a 502 is almost always the website's problem. Reload after a minute, try mobile data, and turn off any VPN or proxy. If other sites load and this one fails, the owner has to fix it.
If it is your site, suspect recent changes first. After migrating to a new host, check the PHP-FPM socket path and service names match the new server.
Fixing a 502 on Bigdomainhost
On a Bigdomainhost KVM VPS you have full root access, so every command above works as written on the Ubuntu, Debian and AlmaLinux images. A backend such as Node on port 3000 talks to Nginx over localhost, so you do not open that port in the server firewall; the firewall denies inbound traffic by default except SSH on port 22, and you add rules for 80 and 443 per instance in the panel. If a config change locks you out, the out-of-band console in the panel still gets you in. The VPS documentation covers networking and firewall rules.
Web & cPanel hosting and WordPress hosting run on LiteSpeed in Mumbai, with PHP 5.6 to 8.4 selectable per domain in cPanel, and the server software is looked after for you. If you see a 502 there, call or WhatsApp +91 75994 50220 or email support@bigdomainhost.com with the URL and the time it happened.
Sources (checked )
- IETF: RFC 9110: HTTP Semantics, section 15.6.3 502 Bad Gateway
- MDN Web Docs: 502 Bad Gateway
- nginx.org: Module ngx_http_proxy_module
- nginx.org: Module ngx_http_upstream_module
- PHP Manual: FPM configuration
- Cloudflare Docs: Error 502 or 504