DEV Community

Shams Ali Shaikh
Shams Ali Shaikh

Posted on

When Your Next.js App Works on Port 3000 but Production Says Nope

When Your Next.js App Works on Port 3000 but Production Says Nope

If you've ever deployed a Next.js application, you've probably experienced this:

Localhost: Works perfectly. Production: 502 Bad Gateway.

Your application is running.

Your process manager says it's online.

The server looks healthy.

And somehow, the browser still says:

"Nope."

This is where understanding Nginx reverse proxying becomes extremely useful.

I'm Shams Ali Shaikh, and in this post, I'll walk through one of the most common patterns I use when deploying Next.js and Node.js applications to a production server.

The Basic Problem

When running a Next.js application locally, you might use:

npm run dev

and access it through:

http://localhost:3000

In production, you might instead run:

npm run build npm run start

Your application is now listening on a port such as:

127.0.0.1:3000

But your users aren't going to visit:

http://your-server-ip:3000

They expect:

https://example.com

That's where Nginx comes in.

What Is Nginx Doing Here?

Think of Nginx as the receptionist sitting in front of your application.

The user talks to Nginx.

Nginx talks to your application.

The application doesn't need to be directly exposed to the internet.

The architecture looks like this:

Internet | v https://example.com | v +---------+ | Nginx | +---------+ | Reverse Proxy | v 127.0.0.1:3000 | v Next.js

This simple architecture solves a lot of deployment problems.

Basic Nginx Configuration

A basic configuration can look like this:

server { listen 80; server_name example.com www.example.com; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; 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; } }

The important part is:

proxy_pass http://127.0.0.1:3000;

This tells Nginx:

"When someone requests this domain, forward the request to the application running on port 3000."

Testing the Configuration

After changing the Nginx configuration, don't immediately restart everything.

First test the configuration:

sudo nginx -t

If the configuration is valid, reload Nginx:

sudo systemctl reload nginx

This is a small habit that can save you from turning a configuration change into a production incident.

What If You Get a 502?

This is where things become interesting.

If Nginx returns:

502 Bad Gateway

don't immediately start changing random Nginx settings.

First check whether your application is actually running.

For example:

pm2 status

Then test the application directly:

curl http://127.0.0.1:3000

If this doesn't respond, the problem probably isn't Nginx.

Your application might have:

Crashed Failed to start Started on another port Failed because of environment variables Failed during the production build Been stopped by PM2 or another process manager

If curl works but the domain doesn't, then investigate the Nginx configuration, DNS, SSL, firewall, or networking.

Checking Nginx Logs

When things still aren't clear, logs become your best friend.

Check the Nginx error log:

sudo tail -f /var/log/nginx/error.log

You can also inspect the access log:

sudo tail -f /var/log/nginx/access.log

Logs usually give you a much better answer than repeatedly refreshing the browser and hoping for a miracle.

One Server, Multiple Applications

One of the reasons I like this architecture is that you can run multiple applications on the same server.

For example:

app.example.com | v Nginx | v 127.0.0.1:3000 Next.js api.example.com | v Nginx | v 127.0.0.1:5000 Node.js admin.example.com | v Nginx | v 127.0.0.1:4000 Next.js

Nginx becomes the traffic controller.

Different domains can point to different applications and ports.

Adding HTTPS

In a real production environment, you generally don't want users accessing your application through plain HTTP.

The production flow becomes:

User | | HTTPS :443 v Nginx | | HTTP v 127.0.0.1:3000 | v Next.js

Nginx can terminate the TLS connection while your application continues running internally.

This also means your application doesn't necessarily need to manage the public SSL connection itself.

My Production Debugging Checklist

When a deployed Next.js application isn't working, I usually go layer by layer:

  1. Is the application running? pm2 status 2. Is the application responding? curl http://127.0.0.1:3000 3. Is Nginx running? sudo systemctl status nginx 4. Is the configuration valid? sudo nginx -t 5. What does Nginx say? sudo tail -f /var/log/nginx/error.log 6. Is DNS pointing to the correct server?

Check the domain's DNS records.

  1. Is HTTPS configured correctly?

Check the certificate and TLS configuration.

  1. Is the required port accessible?

Check your firewall and server networking rules.

The important part is not to troubleshoot everything at once.

Start from the application and move outward.

Application ↓ Port ↓ Process ↓ Nginx ↓ HTTPS ↓ DNS ↓ Internet The Bigger Lesson

Deployment is not simply:

git push → website

A production application is a collection of interconnected layers.

Developer ↓ GitHub ↓ CI/CD ↓ Production Server ↓ Docker / PM2 ↓ Next.js / Node.js ↓ Nginx ↓ HTTPS ↓ Domain ↓ Users

When you understand each layer, debugging becomes much easier.

You stop asking:

"Why isn't my website working?"

And start asking:

"Which layer is failing?"

That change in thinking is one of the most useful things I've learned while working with production deployments.

Because "it works on my machine" might be a perfectly valid development statement.

It just isn't a very convincing production strategy.

I'm documenting what I learn while building and deploying real applications, from localhost to production.

More deployment and DevOps guides coming soon.

— Shams Ali Shaikh

Software Engineer | MERN Stack | Next.js | Node.js | DevOps | Deployment

Top comments (0)