Full Stack Developer specializing in React, Node.js, and scalable web solutions.

I am Ujjawal Solanki, a dedicated software developer with expertise in full-stack development, React applications, and Node.js backend services. I create innovative, scalable, and user-friendly web applications.

How to Deploy a MERN App on a VPS

My MERN App Worked on Localhost. Putting It Online Almost Broke Me

The first time my MERN app worked end to end on localhost, I felt unstoppable. Then someone asked for the link, and I realized I had built a private miracle. It lived only on my laptop.

That moment hurt in a useful way. Until then, deployment felt like a future problem. I was obsessed with routes, forms, and Mongo queries. I had not built the skill of making software reachable.

So I rented a small VPS, opened an SSH session, and immediately entered the most humbling phase of my early developer life. Every command felt serious. Every mistake felt expensive.

The part nobody tells beginners

Most deployment tutorials are only scary because they skip the mental model. They throw ten commands at you and expect confidence to appear on its own.

What finally helped me was seeing the server as a normal computer with a few jobs. One process serves the API. One reverse proxy accepts public traffic. One build folder serves React. One database connection points to MongoDB. That is the story.

If you feel overwhelmed on your first VPS, that is not proof you are bad at deployment. It is proof that deployment is a separate skill from coding.

What I deploy now before touching production

  • A React frontend that already builds locally without warnings
  • A backend that runs from environment variables only
  • A MongoDB connection string stored outside Git
  • One start command for the server
  • A short checklist for ports, domains, and SSL

That checklist sounds basic, but it saved me from the most embarrassing class of deploy bugs: the ones caused by panic and memory.

The practical path that finally worked for me

Step 1

Prepare the server like you are moving into a new room

I start by updating packages and installing only the essentials. When I used to install random extras because a tutorial mentioned them, I created more confusion than value.

You want a clean base: Node, npm, Git, Nginx, and a process manager. That is enough to get a real MERN app online.

ssh root@your_server_ip
sudo apt update && sudo apt upgrade -y
sudo apt install -y nodejs npm git nginx
node -v
npm -v
Step 2

Clone the backend and prove the API can run alone

Before I even think about Nginx, I make sure the API can boot with its own environment file. This is where hidden assumptions show up fast.

If the app crashes here, that is good news. It means the problem is still small. Fix missing environment variables, bad ports, or hardcoded localhost references now.

git clone https://github.com/your-username/your-mern-app.git
cd your-mern-app/server
npm install
printf 'PORT=5000
MONGO_URI=your_atlas_uri
JWT_SECRET=super_secret
' > .env
node index.js
Step 3

Use PM2 so your server keeps running after you disconnect

My early mistake was believing that if a Node process was running in my SSH tab, the app was deployed. Then I closed the terminal and the app disappeared.

PM2 solves that beginner pain immediately. It also gives you logs, restart control, and a calmer recovery story when something fails.

sudo npm install -g pm2
cd /root/your-mern-app/server
pm2 start index.js --name mern-api
pm2 save
pm2 status
pm2 logs mern-api
Step 4

Build React and let Nginx handle the public traffic

This was the turning point for me. Once I understood that React becomes static files after build, Nginx stopped feeling mysterious.

Serve the frontend from Nginx and proxy `/api` to the Node process. That way the browser talks to one domain, and your server routes traffic intelligently.

cd /root/your-mern-app/client
npm install
npm run build
sudo rm -rf /var/www/mern-app
sudo mkdir -p /var/www/mern-app
sudo cp -r dist/* /var/www/mern-app/
server {
  listen 80;
  server_name yourdomain.com;

  root /var/www/mern-app;
  index index.html;

  location / {
    try_files $uri /index.html;
  }

  location /api/ {
    proxy_pass http://127.0.0.1:5000/;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }
}
Step 5

Add HTTPS last, not first

I used to rush to SSL because it felt like the final badge of a real deploy. But if the site does not serve correctly on plain HTTP first, SSL only hides the actual problem.

Once the domain and Nginx config are confirmed, Certbot is usually the easy part.

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Mistakes that wasted most of my time

The invisible env file problem

Half my early deploy failures came from missing or mismatched environment variables. The app did not fail loudly enough. PM2 kept restarting it, and I kept pretending the issue was Nginx.

The build folder mismatch

Sometimes React created `build/`. Sometimes Vite created `dist/`. I copied the wrong folder more than once and stared at a broken landing page like it had betrayed me.

The localhost trap

If your frontend still calls `http://localhost:5000`, the app may look fine in development and fail beautifully in production. Always review API base URLs before deploying.

When deployment feels confusing, read logs before changing architecture. `pm2 logs` and `sudo nginx -t` tell the truth faster than guessing.

My debugging checklist now

  1. Can I SSH into the server and see the project files where I expect them?
  2. Does `node index.js` or `pm2 logs` show a real app boot without env errors?
  3. Is the backend port listening locally on the VPS?
  4. Does `sudo nginx -t` pass before reloading?
  5. Does the browser request the right domain-relative API path?

What made deployment finally click for me was realizing I did not need to be clever. I needed to be methodical. The calmer I became, the faster I fixed things.

Lessons I carry into every release

  • Deploy in layers and prove each layer works before moving on.
  • Treat environment variables like part of the app, not an afterthought.
  • Keep the first server small and boring. Fancy setups can wait.
  • Write the exact steps down once so future deploys are repeatable.

The first live link I shipped did not look glamorous. But it taught me something bigger than commands. Software is only real when people can reach it.

If this helped you, save it for later. And if you are stuck on a MERN feature, you can always reach out to me.

Let's chat with me? - Online
Please fill out the form below to start chatting with me directly.