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:
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:
They expect:
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:
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:
- 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.
- Is HTTPS configured correctly?
Check the certificate and TLS configuration.
- 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)