
A 502 Bad Gateway means Nginx did its job correctly: it received the request, opened a connection to its upstream, and got a response back — but that response was invalid, empty, or the connection was refused or reset mid-way. Nginx is only the messenger here. The actual failure happened one layer behind it.
On a CloudStick server, that upstream is one of two things depending on the site type. For PHP sites, CloudStick's namespaced Nginx (nginx-cs) proxies requests to apache2-cs listening internally on 127.0.0.1:81, which in turn talks to the correct PHP-FPM pool (e.g. php83cs-fpm) over a Unix socket. For Node.js or other app-server sites, nginx-cs proxies directly to the app's listening port. A 502 means something in that chain — Apache, PHP-FPM, or the app process — either wasn't there to answer, or answered with garbage.
These three errors look similar but point to completely different layers of the stack, and mixing them up wastes debugging time. A 500 Internal Server Error comes from the application itself — PHP-FPM or your app process is alive and responding, but the code threw an uncaught exception or fatal error. A 504 Gateway Timeout means the upstream was reached and was working, it just took longer than Nginx's configured timeout to respond — a slow query or a stuck script, not a crash.
A 502, by contrast, means Nginx couldn't get any usable response at all: the upstream process wasn't running, the socket or port it tried to connect to didn't exist, or the connection dropped before a full response came back. If you're seeing intermittent slowness followed by errors rather than an outright crash, you likely want our guide on 504 timeouts instead — the fix path is different.
Almost every 502 on a CloudStick-managed site traces back to one of five things. First, PHP-FPM or Apache (apache2-cs) has crashed or was never started after a server reboot or package update — Nginx has nothing to connect to. Second, the pool's pm.max_children limit has been hit: every worker is busy handling existing requests, new connections queue up and time out, and Nginx reports it as a failed upstream.
Third, the socket path in the vhost config no longer matches reality — this happens after a PHP version switch if the old fastcgi_pass or proxy_pass target still points at /run/oldsite.sock while the pool now listens on a different socket. Fourth, the upstream application itself has crashed — a Node process that exited on an unhandled promise rejection leaves nothing listening on its port. Fifth, and less common on a fresh Ubuntu 22.04 CloudStick install, file permission issues on the socket file itself can block the connection, though this is far more of an SELinux concern than an Ubuntu one and usually isn't the culprit here.
The nginx-cs error log tells you exactly which upstream failed and how. Per-site logs live at /home/<username>/logs/<sitename>/nginx-cs/error.log, and inside CloudStick you can read the same thing without SSH access at all — the Web Application Logs section on the site's dashboard streams this exact file, which is often the fastest way to confirm a 502 without opening a terminal.
Two log messages tell two different stories. A connect() failed (111: Connection refused) line means nothing was listening at all — the process is down. A recv() failed (104: Connection reset by peer) or an empty response means the process was there but died mid-request, which usually points to a crash, an out-of-memory kill, or a segfault in the app itself.
Start by confirming the services in the chain are actually up. If apache2-cs or PHP-FPM shows failed or inactive, restart it and check the exit reason in the same command before assuming the restart alone fixes it — a repeatedly crashing service usually has a config error worth reading first.
If the log shows repeated connection refusals under real traffic rather than after a reboot, the pool is likely exhausted. Open the site's pool config at /etc/php83cs/fpm-pools.d/demosite.conf and check pm.max_children against actual memory available — each child process reserves real RAM, so raising the number without checking headroom just trades a 502 for an OOM kill.
Before raising pm.max_children, check the Server Stats graph on the CloudStick dashboard for actual free RAM. Each PHP-FPM child process typically uses 30-80MB depending on the app, so a pool sized past available memory just moves the failure from a 502 to an out-of-memory kill.
For Node.js or other proxied apps, confirm the process is actually bound to the port nginx-cs expects with ss -tlnp | grep <port>. If it's not listed, the process manager (PM2, Supervisor) needs restarting — check its own logs for the crash reason rather than just bouncing it blindly, since an app that keeps dying will keep producing 502s no matter how many times you restart Nginx.
Most 502s are preventable with two habits: watching resource headroom before it runs out, and keeping crash-prone processes supervised so they restart automatically instead of leaving Nginx with nothing to talk to. Size pm.max_children against real available memory rather than a guessed default, and if you run Node or Python apps behind CloudStick, use its Supervisor management to keep the process auto-restarting on crash instead of relying on manual intervention after each outage.
It's also worth checking memory pressure specifically, since an overloaded server killing PHP-FPM workers via the OOM killer is one of the single most common root causes behind a sudden burst of 502s on a site that was working fine minutes earlier — see our guide on server memory exhaustion for how to spot and fix that pattern. Once services are stable and properly sized, a 502 on a CloudStick server becomes rare rather than routine.

