ShortIQ

ShortIQ

Deployment

How to Fix 502 Bad Gateway in Node.js and Nginx (Step-by-Step)

A step-by-step troubleshooting guide for 502 Bad Gateway in Node.js and Nginx. Covers PM2 crashes, port mismatches, every Nginx error log message explained, correct proxy headers, firewall issues, and post-reboot 502 fixes.

April 20, 2026ShortIQ Editorial Team

What a 502 Bad Gateway Actually Means

A 502 Bad Gateway error means Nginx is running and accepting your request, but when it tries to forward that request to the upstream application (your Node.js server), it gets back a bad response or no response at all. Nginx itself is fine. The problem is between Nginx and your app.

This is different from a 503 Service Unavailable (the service is explicitly down) and a 504 Gateway Timeout (the upstream responded too slowly). A 502 means the connection was refused, reset, or the upstream sent a response that Nginx could not parse as valid HTTP.

In a typical Node.js stack, Nginx listens on port 80 or 443, receives your request, and forwards it to localhost on whatever port PM2 is running your app on. If that forwarding step fails for any reason, you get a 502. The error always comes from Nginx, not from Node, because Node never got the chance to respond.

  • 502 comes from Nginx — Node never responded to this request
  • Nginx is healthy; the problem is the upstream app
  • Always check the app process before touching the Nginx config
  • Read /var/log/nginx/error.log for the exact failure reason

Step 1 — Check the App Process with PM2

The most common cause of 502 is a crashed or stopped Node.js process. Run pm2 list first. If the process status shows errored or stopped, or if the restart count is climbing rapidly, the app has a problem at the Node level and nothing in Nginx can fix it until the app itself is healthy.

Use pm2 logs to see the last output from the process before it crashed. Common things to look for: a missing environment variable causing an undefined reference, a database connection failure at startup, or an EADDRINUSE error meaning another process is already on that port.

After fixing the root cause, restart the app and watch the log output for a few seconds to confirm it stays up before testing the public URL. A process that restarts successfully but then immediately errors again has a startup crash, not a proxy problem.

bash
# Check the status of all PM2 processes
pm2 list

# Stream the last 50 lines of logs for your app
pm2 logs my-app --lines 50

# Restart the app after fixing the crash reason
pm2 restart my-app

# If the process entry is broken, delete it and re-add
pm2 delete my-app
pm2 start ecosystem.config.js
  • Status "errored" with high restart count: app keeps crashing on startup — read pm2 logs
  • Status "stopped": process was manually stopped — restart it
  • EADDRINUSE in logs: another process owns that port — kill it first
  • Database connection error at startup: fix the connection string or env var, then restart

Step 2 — Verify Which Port the App Is Actually Using

A very common cause of 502 is a port mismatch: Nginx is configured to proxy to port 3000, but the Node.js app is actually listening on port 3001, or not listening at all. This is straightforward to verify with ss and curl from inside the server.

Run ss -tulpn to list all listening sockets and which process owns them. If nothing is on the expected port, the app is either not running or using a different port. Then run curl directly against that port from the server itself. If curl returns a valid response, the problem is purely in the Nginx proxy_pass — not in the app. If curl returns connection refused, the app is the problem and you should go back to PM2.

bash
# List all listening ports and which process owns them
ss -tulpn | grep LISTEN

# Check specifically for port 3000
ss -tulpn | grep 3000

# Test the app directly from the server (bypasses Nginx completely)
curl -I http://127.0.0.1:3000

# If your app has a health endpoint
curl http://127.0.0.1:3000/api/health
  • curl returns 200 or HTML: app is running correctly — the Nginx proxy_pass port is likely wrong
  • curl returns "Connection refused": app is not on that port — check PM2 and .env PORT value
  • Next.js defaults to port 3000, Express apps often use 3000 or 5000
  • Check your .env file for PORT= and confirm it matches proxy_pass in Nginx

Step 3 — Read the Nginx Error Log and Understand What Each Message Means

The Nginx error log at /var/log/nginx/error.log contains the exact reason for every 502. Do not guess when the log tells you precisely what failed. This is the most valuable diagnostic step and most developers skip it.

The most common 502 message is: connect() failed (111: Connection refused) while connecting to upstream. This means Nginx tried to open a connection to the proxy_pass address and was refused — nothing was listening on that port. Error 111 is the Linux ECONNREFUSED code. Fix: start the app process or correct the proxy_pass port number.

The second most common is: upstream timed out (110: Connection timed out) while reading response header from upstream. This means the app is running and accepting connections but not returning a response within the allowed time window (default 60 seconds). The request is hanging inside Node.js — usually from a slow or blocked database query, an unresolved promise, or a CPU-heavy operation blocking the event loop. Increase proxy_read_timeout as a short-term fix while you find and fix the slow code path.

bash
# Stream the Nginx error log in real time
sudo tail -f /var/log/nginx/error.log

# Show the last 50 lines after a 502 occurs
sudo tail -50 /var/log/nginx/error.log

# Filter specifically for upstream errors
sudo grep 'upstream' /var/log/nginx/error.log | tail -20
  • "connect() failed (111: Connection refused)": nothing is listening on that port — start the app
  • "upstream timed out (110: Connection timed out)": app is not responding in time — increase proxy_read_timeout
  • "no live upstreams while connecting to upstream": all servers in an upstream block are down
  • "recv() failed (104: Connection reset by peer)": app crashed or closed the connection mid-request
  • "upstream sent invalid header": app returned a malformed HTTP response — check the app logs for exceptions

Step 4 — Check and Fix the Nginx Proxy Configuration

If the app is healthy and listening on the correct port but you still see 502, the Nginx configuration is the likely cause. The most common mistake is a wrong proxy_pass value — wrong port number, wrong address, or an unintended trailing slash.

A trailing slash on proxy_pass changes how Nginx rewrites the request URI. proxy_pass http://127.0.0.1:3000 passes the full request path to the app unchanged. proxy_pass http://127.0.0.1:3000/ strips the location prefix from the URI before forwarding. For Node.js apps, omit the trailing slash unless you specifically need URI rewriting.

The proxy headers matter too. Missing proxy_http_version 1.1 disables HTTP keep-alive and prevents WebSocket upgrades. Missing X-Forwarded-Proto causes apps that check for HTTPS to see HTTP even on a secure connection, which can trigger redirect loops or authentication errors that Nginx surfaces as 502. Use the complete config below as your starting point.

nginx
server {
    listen 80;
    server_name yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_read_timeout 300;
        proxy_connect_timeout 300;
    }
}
bash
# Test the Nginx configuration for syntax errors before applying
sudo nginx -t

# Reload Nginx to apply changes without dropping connections
sudo systemctl reload nginx

# If reload fails, do a full restart
sudo systemctl restart nginx

Step 5 — Firewall and Port Binding Issues

On a VPS with UFW enabled, port 80 and 443 must be open for external HTTP and HTTPS traffic. However, internal traffic between Nginx and Node on 127.0.0.1 is not subject to UFW rules, so the app port (3000, 5000, etc.) does not need to be open in the firewall — and should not be, since direct external access to your app port would bypass Nginx.

If your Node.js app binds to 127.0.0.1 explicitly, it is only reachable from within the server, which is the correct setup for a proxied deployment. If it binds to 0.0.0.0, it is reachable from any IP. Both work fine for the Nginx proxy — Nginx uses the loopback address regardless. If you are on AWS EC2, also check the security group inbound rules to confirm port 80 and 443 allow 0.0.0.0/0.

bash
# Check firewall status
sudo ufw status

# Allow HTTP and HTTPS if not already open
sudo ufw allow 'Nginx Full'

# Confirm what address your Node app is binding to
ss -tulpn | grep 3000
# 127.0.0.1:3000 = local only (correct for proxied setup)
# 0.0.0.0:3000  = all interfaces (also works)

Step 6 — Fixing 502 After Server Reboot or After Deployment

Two scenarios produce a 502 that seems mysterious because nothing in the config has changed: immediately after a server reboot, and immediately after a new deployment. Both have specific fixes.

After a reboot: PM2 does not auto-start unless you have run pm2 startup and pm2 save. Without those two commands, the server reboots, Nginx starts and begins accepting traffic, and immediately returns 502 because PM2 has not restarted your app yet. Run pm2 startup once — it prints a system command to run as sudo. Execute that command, then run pm2 save to write the current process list to disk. After the next reboot, PM2 starts your apps automatically before any requests arrive.

After a deployment: 502 usually means the app was restarted before the build completed, or the build failed silently. For Next.js specifically, if npm run build is interrupted or fails, the .next directory is incomplete. The app crashes immediately on startup with a production build error, and PM2 keeps cycling through failed restarts while Nginx returns 502 for every request. Always wait for the full build output to confirm success before running pm2 restart.

bash
# Fix 502 after reboot: make PM2 auto-start on boot
pm2 startup
# Run the sudo command that pm2 startup prints, then:
pm2 save

# Fix 502 after deployment: build completely before restarting
npm run build

# Verify the Next.js build output exists and is not empty
ls -la .next

# Only restart after confirming the build is complete
pm2 restart my-app

The Complete 502 Recovery Sequence

When you hit a 502 in production, run these checks in order. Each step eliminates one class of cause. Stop when you find the problem, fix it, and restart in the order shown.

Always reload Nginx with systemctl reload rather than restart when you only change the config — reload applies the new config without dropping existing connections. Use pm2 reload instead of pm2 restart in production to achieve zero-downtime process cycling.

bash
# 1. Check the app process
pm2 list
pm2 logs my-app --lines 50

# 2. Confirm the app is reachable on the expected port
ss -tulpn | grep LISTEN
curl -I http://127.0.0.1:3000

# 3. Check Nginx
sudo nginx -t
sudo systemctl status nginx
sudo tail -50 /var/log/nginx/error.log

# 4. Apply fixes and restart in order (app first, then Nginx)
pm2 restart my-app
sudo systemctl reload nginx

# 5. Confirm the site is back
curl -I https://yourdomain.com

FAQ

What causes 502 Bad Gateway in Node.js and Nginx?

The most common causes are: the Node.js process is not running or has crashed (check pm2 list), the app is listening on a different port than what is in proxy_pass, the Nginx proxy_pass URL is misconfigured, or the app crashes on startup due to a missing environment variable or failed database connection. Read pm2 logs and /var/log/nginx/error.log to identify the specific cause.

Should I check Nginx or PM2 first for a 502?

Always check PM2 first. If the Node.js process is not running or is in an errored state, Nginx has nothing to proxy to and changing the Nginx config will not help. Fix the app crash first, confirm the process is healthy with pm2 list, then check the proxy config if the 502 persists.

What does "connect() failed (111: Connection refused)" mean in the Nginx error log?

It means nothing is listening on the port Nginx is trying to connect to. The Node.js app process has crashed, been stopped, or was never started. Error 111 is the Linux ECONNREFUSED code. Check pm2 list to see the process status and pm2 logs to find the crash reason.

What does "upstream timed out (110: Connection timed out)" mean?

The app is running and accepting connections but is not returning a response within the proxy_read_timeout limit (default 60 seconds). The request is hanging inside Node.js — usually from a slow database query, a blocked event loop, or a promise that never resolves. Add proxy_read_timeout 300; to your Nginx location block as a short-term fix, and investigate the slow code path.

Can a missing environment variable cause 502 Bad Gateway?

Yes. If the app crashes on startup because a required environment variable is missing — a database URL, an API key, or a PORT value — PM2 keeps restarting the failed process. Nginx tries to proxy to the dead port and returns 502. Check pm2 logs for "cannot read properties of undefined", "process.env.X is not defined", or database connection errors.

What proxy headers should every Node.js Nginx config include?

Include these at minimum: proxy_http_version 1.1 (required for keep-alive and WebSocket), proxy_set_header Host $host (passes the original hostname to the app), X-Real-IP $remote_addr (the real client IP), X-Forwarded-For $proxy_add_x_forwarded_for (full IP chain for logging), and X-Forwarded-Proto $scheme (tells the app whether the original request was HTTP or HTTPS). Missing X-Forwarded-Proto causes redirect loops in HTTPS deployments.

What log should I read to diagnose a 502 error?

Read both logs in sequence. First run pm2 logs my-app to see the app-side crash reason or what the process was doing when the request arrived. Then run sudo tail -50 /var/log/nginx/error.log to see what the proxy layer experienced. The Nginx log gives you the specific error code — 111, 110, 104 — that tells you exactly which failure class you are dealing with.

How do I fix 502 Bad Gateway after the server reboots?

Run pm2 startup to generate an OS-level startup script. Execute the sudo command that pm2 startup prints, then run pm2 save to persist the current process list to disk. After the next reboot, PM2 will automatically restart all saved processes before Nginx starts proxying traffic to them. Without pm2 save, the process list is empty after every reboot.

How do I fix 502 under high traffic?

Add proxy_read_timeout 300; and proxy_connect_timeout 300; to your Nginx location block. Also check whether the Node.js process is pinned at 100% CPU using the top command. If it is, the bottleneck is the app code, not Nginx. Enable PM2 cluster mode by setting instances to max in ecosystem.config.js to use all available CPU cores and handle more concurrent requests.

Why does Next.js return 502 after deployment?

Usually because pm2 restart ran before npm run build completed, or the build failed silently. Next.js requires a complete .next directory to start. If the build is interrupted, the directory is partial and the app crashes on startup with a "Could not find a production build" error, which PM2 surfaces as repeated restart failures and Nginx returns as 502. Always let the build finish completely, verify with ls -la .next, then restart PM2.

Related free tools

If you want to turn this topic into action, use one of ShortIQ's free tools for campaign planning, UTM structure, or QR distribution.

Continue Reading

Explore more guides on link shortener SaaS strategy, Bitly alternatives, and white label link management.

Free newsletter

Get new guides in your inbox

We publish practical guides on dev tooling, prompt engineering, marketing workflows, and deployment. No fluff — straight to the point.

No spam. Unsubscribe any time.

Was this article helpful?

Tell us if this guide solved the problem or what was still missing. We use this to improve the blog and only follow up if you explicitly allow it.

We use this to improve tutorials, examples, and technical depth.