You deploy, refresh the page and see 502 Bad Gateway. Users are hitting it too. The worst thing you can do now is start changing things at random over SSH.
This post is a playbook: first get the site back (rollback), then find the cause (debugging), then prevent it next time. Along the way I'll show how ShipOwn handles each step on servers you own.
What a 502 actually means
Nginx is up and received the request, but when it forwarded the request to your app (the "upstream"), it got no valid response. So the problem is almost always between Nginx and the app, not DNS or HTTPS.
Typical causes:
- The app process crashed or never started.
- The app listens on a different port than Nginx expects.
- The app is still starting (slow boot) or stuck.
- The server ran out of memory or disk.
- A Docker container is bound to a different interface or port.
Step 0: Restore service first
If the site worked before this deploy, roll back before you debug. Users don't care why; they care that it works.
Manual rollback
If you deploy into release folders with a current symlink:
cd /var/www/app
ls releases/ # find the previous release
ln -sfn releases/20261027-1830 current
pm2 reload app
If you deploy in place with git pull, rollback means checking out the previous commit and rebuilding, which is slow and stressful mid-incident. That alone is a good reason to switch to release folders.
Rollback with ShipOwn
ShipOwn puts every deploy in its own release directory and only moves the live pointer after the build and health check pass. If a deploy fails those checks, the previous release is restored automatically. If the problem appears later, you can switch back to a retained release from the app's operate page (or to an earlier image for Docker apps), while its files are still on the server.
Step 1: Is the app running?
pm2 status
pm2 logs app --lines 100
Look for errored, a climbing restart count, or a stack trace. Common culprits after a deploy: a missing environment variable, a failed build that left no output, or a dependency that didn't install.
For Docker:
docker ps -a
docker logs --tail 100 <container>
Step 2: Is it on the right port?
sudo ss -ltnp | grep -E '3000|8000'
grep -R proxy_pass /etc/nginx/sites-enabled/
The port in proxy_pass must match what the app actually listens on. A changed PORT variable is a classic cause.
Test the app directly, bypassing Nginx:
curl -I http://127.0.0.1:3000
If that works, the problem is Nginx config. If it fails, it's the app.
Step 3: Check the Nginx error log
sudo tail -n 50 /var/log/nginx/error.log
connect() failed (111: Connection refused) means nothing is listening. upstream timed out means the app is too slow or stuck.
Step 4: Check resources
free -h
df -h
uptime
dmesg | grep -i -E 'killed process|oom'
A full disk breaks builds and logs. An out-of-memory kill silently stops your app. Both are common on small VPS plans.
Step 5: Fix, then redeploy
Once you know the cause, fix it in the code or config and deploy again. Not by editing files on the live server, or your next deploy will overwrite the fix.
Debugging with an approval-based AI terminal
Mid-incident, remembering the right command is the hard part. ShipOwn includes an AI terminal for exactly this moment:
- You describe the problem in plain language: "the site returns 502 since the last deploy".
- Using your own Anthropic, OpenAI or DeepSeek API key, the model inspects the server and proposes one shell command at a time, with its reason.
- You approve each command before it runs.
- Destructive commands (recursive deletes, reboots, firewall flushes, stopping SSH) are always held for confirmation.
The AI never runs on ShipOwn's servers. Requests go from your computer to the provider you chose, and your key is encrypted by the OS. Prompts and command output do go to that provider, so check output for secrets before sharing.
Next to it, ShipOwn's server view shows CPU, RAM, disk, load average and PM2 processes, read directly from the server, plus an embedded terminal and logs. You can usually spot the out-of-memory or full-disk case at a glance.
Prevent the next 502
- Release directories + health checks. Don't switch traffic until the new version answers.
- Test Nginx before reload. ShipOwn tests every config it writes and restores it if Nginx rejects it.
- Build where there's memory. ShipOwn's PC build mode builds on your machine and uploads the verified output.
-
Set restart policies. PM2 or
restart: unless-stoppedfor containers. - Get told when it's down. ShipOwn's tray heartbeats report an "unreachable" incident after two consecutive failures, and report recovery once (while the app is running and your PC is awake).
- Know what's live. ShipOwn shows the commit each app runs and how far behind its branch it is.
One honest limit: no deploy tool rolls back your database. If a release ran a migration, you need a separate plan for migrations and backups.
Quick reference
| Symptom | First command |
|---|---|
| 502 right after deploy | Roll back, then pm2 logs
|
Connection refused in Nginx log |
ss -ltnp to check the port |
upstream timed out |
App logs, uptime, slow queries |
| App randomly dies |
dmesg for OOM kills |
| Build fails |
df -h, free -h
|
Conclusion
A 502 is stressful but predictable. Roll back first, then check the process, the port, the Nginx log and the server's resources. If you want release-based rollback and approval-gated AI debugging on your own VPS, try ShipOwn free for 30 days, no card required.
Written by Vishvajeet Shukla, full stack developer and creator of ShipOwn.
Top comments (0)