Step By Step on How I Setup NGINX Reverse Proxy to Hide Port 3000 on AWS EC2


Few days ago, I made a big mistake — I left Port 3000 open to the world on my EC2. Anyone could access http://my-ip:3000 directly. That is dangerous because hackers can attack your Node.js process directly.  Today, I fixed it properly with NGINX Reverse Proxy. After this fix, my MERN app now runs on clean; http://IP without any port number, and port 3000 is completely private.  

What is Reverse Proxy? (Simple beginner explanation)

Imagine your EC2 is a big office building.

Your Node.js app is a boss sitting in Room 3000 inside. Before, you allowed visitors to go straight to Room 3000. That is risky.

Reverse Proxy is like putting a professional receptionist (NGINX) at the main entrance (Port 80). All visitors must come to reception. Receptionist will go inside, collect what you need from Room 3000, and bring it to the visitor. No visitor ever enters Room 3000 directly. 

That is what the diagram above shows. 

User -> NGINX (Port 80/443) -> Node.js (Private Port 3000). Direct access to 3000 is BLOCKED by Security Group.

Why Every Production App Uses This?

  1. Security: Node.js is not built to face internet directly. NGINX handles attacks better.
  2. Clean URL: Users don't need to remember :3000. Just your IP or domain.
  3. HTTPS Ready: You can easily add free SSL Certificate (Let's Encrypt) to NGINX later for https://
  4. Performance: NGINX serves frontend static files (React build) faster than Node.

Step-by-Step Implementation

Step 1: Install NGINX on EC2; ssh into your EC2 and run

sudo apt update

sudo apt install nginx -y

sudo systemctl start nginx

check if it works: open http://Your_EC2_IP, you should see ''Welcome to nginx'' page.


Step 2: Configure Reverse Proxy

This is the main step. We tell NGINX: "Any request that comes to Port 80, forward it to localhost:3000".

Open the default config:

sudo nano /etc/nginx/sites-available/default


Select all (Ctrl+K) and delete, then paste this clean config:

server {

    listen 80;

    server_name your_domain_or_EC2_IP;


    location / {

        proxy_pass http://localhost:3000;

        proxy_http_version 1.1;

        proxy_set_header Upgrade $http_upgrade;

        proxy_set_header Connection 'upgrade';

        proxy_set_header Host $host;

        proxy_cache_bypass $http_upgrade;

    }

}

Save; Ctrl+0, Enter, Ctrl+X


Step 3: Test and Restart NGINX

Always test before restarting to aviod crashing

sudo nginx -t

If it says ''syntax is ok, test is successful'', then;

sudo systemctl restart nginx


Step 4: The Security Fix (Most Important)-- Close Port 3000 in AWS

This is what i forgot yesterday, now fix it:

Go to AWS Console > EC2 > Security Groups >Select your SG> Edit Inbound Rules: 

Delete the rule for Port 3000. Keep only:

  • SSH (22) from My IP
  • HTTP (80) from Anywhere
  • HTTPS (443) from Anywhere

Save rules

Now check: http://YOUR_IP:3000 should should not work, "Not reachable or connection refused" Good! (BLOCKED) ✅

But http://YOUR_IP should show your MERN app working ✅ Good! That means NGINX is protecting you.

If both work like that, you are now production-ready

Common Error I Faced

After setup, I got 502 Bad Gateway. Reason? My Node app had crashed and was not running on 3000. Fix: I used PM2 to keep it alive:

  • pm2 start server.js --name mern-app
  • pm2 save
  • pm2 startup

Then NGINX started working.

Why This Is Best Practice?

  1. Security — No direct attack on Node.js
  2. Clean URL — No need to type :3000
  3. Ready for HTTPS — You can add free SSL (Let's Encrypt) to NGINX later
  4. Faster — NGINX serves static files faster than Node


Conclusion

From yesterday's vulnerability to today's secure setup, this is growth. Don't expose your app ports directly. Always put NGINX in front. 

Read Previous Post: I explained the mistake that led to this fix here: My First EC2 Mistake: I Left Port 3000 Open and Almost Got Hacked — https://sirvick-deployments.blogspot.com/2026/09/my-first-ec2-mistake-i-left-port-3000.html



Comments

Popular posts from this blog

How I Fixed GitHub Error: failed to push some refs to github.com in 2026

How I Deployed Netflix Clone (NGold) to AWS EC2 - Live at 32.196.145.76:3000 [Docker + Nginx Guide]

Understanding Linux Environment — The Real Engine Behind My AWS Cloud Deployment