Back to Blog

🚀 How to Host a MERN Stack App on VPS (Complete Guide)

VPS Deployment

Learn how to deploy your MERN (MongoDB, Express, React, Node.js) application on a VPS server with proper domain setup and SSL certificates.

📋 Before You Start

⚠️
Warning

Make sure you've completed these steps:

  • Use environment variables for sensitive data (API URLs, database connections)
  • Add node_modules to .gitignore
  • Use node for production (not nodemon)
  • Push your code to GitHub

🛒 Step 1: Get a VPS

I recommend Hostinger VPS for beginners:

  1. Visit Hostinger
  2. Select VPS Hosting
  3. Choose your location (closer to users = faster)
  4. Select Ubuntu 22.04 64bit with control panel
  5. Set admin password and SSH key
  6. Wait for VPS setup (5-10 minutes)
💡
Tip

After setup, verify your email for updates and save your VPS IP address!


🔌 Step 2: Connect to Your VPS

SSH (Secure Shell) allows you to remotely access and control your VPS through the command line. Think of it as remotely controlling another computer.

Open terminal and connect via SSH:

bash
ssh root@<your-vps-ip>
# Example: ssh root@62.72.59.218
# You'll be prompted for the password you set during VPS setup

What is sudo?

  • sudo = "Superuser Do" - runs commands with admin privileges
  • apt = Package manager for Ubuntu (like App Store for Linux)
  • update = Refreshes the list of available packages
  • upgrade = Installs the latest versions of all packages

Update system packages:

bash
sudo apt update
sudo apt upgrade
# Press 'Y' when prompted to confirm installation
💬
Info

This ensures your VPS has the latest security patches and software versions.


📦 Step 3: Install Node.js (Using NVM)

Why NVM? NVM (Node Version Manager) lets you install and switch between multiple Node.js versions easily. Perfect for managing different projects!

Install NVM (Node Version Manager):

bash
# curl downloads files from the internet
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

Reload shell configuration to activate NVM:

bash
# This reloads your shell settings without logging out
source ~/.bashrc
 
# Verify NVM is installed
nvm --version

What is LTS? Long Term Support - stable, reliable version recommended for production.

Install Node.js LTS:

bash
nvm install --lts
 
# Verify installation
node -v
# Output: v18.18.0 (or latest LTS version)
 
npm -v
# Output: 9.8.1 (npm comes with Node.js)
💡
Tip

If nvm command not found, close terminal and reconnect to your VPS.


🍃 Step 4: Install MongoDB

MongoDB is your database. These commands install the official MongoDB Community Edition on Ubuntu.

Step 4.1: Import MongoDB GPG key (for security verification):

bash
# This verifies that the MongoDB package is authentic
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | sudo gpg --dearmor -o /usr/share/keyrings/mongodb-server-7.0.gpg

Step 4.2: Add MongoDB repository to Ubuntu's package list:

bash
# "jammy" is the codename for Ubuntu 22.04
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list

Step 4.3: Install MongoDB:

bash
sudo apt update
sudo apt install -y mongodb-org
# This may take 2-3 minutes
# Press Enter if you see any prompts

Step 4.4: Start MongoDB service:

bash
# Start MongoDB now
sudo systemctl start mongod
 
# Enable MongoDB to start automatically on server reboot
sudo systemctl enable mongod
 
# Check if MongoDB is running
sudo systemctl status mongod
# Look for "active (running)" in green
# Press 'q' to exit status view
Success

If you see "active (running)", MongoDB is successfully installed and running!


🔧 Step 5: Install Git & Clone Your Project

Git helps you download and manage your code from GitHub.

Step 5.1: Install Git:

bash
sudo apt install git
 
# Verify installation
git --version
# Output: git version 2.34.1 (or similar)

Step 5.2: For private repositories, install GitHub CLI:

bash
# Install GitHub CLI
type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install gh -y
 
# Authenticate with GitHub
gh auth login
# Follow the prompts:
# - Choose "GitHub.com"
# - Choose "HTTPS"
# - Choose "Login with a web browser"
# - Copy the code and press Enter
# - Paste code in browser to authenticate

Step 5.3: Clone your repository:

bash
# For public repos:
git clone https://github.com/username/repo-name.git
 
# For private repos (after gh auth login):
git clone https://github.com/username/private-repo.git
 
# Navigate into your project
cd <your-project-folder>
 
# Verify you're in the right place
ls
# You should see your project files
💡
Tip

Replace username/repo-name with your actual GitHub username and repository name!


⚙️ Step 6: Setup Backend

Step 6.1: Navigate to your backend folder:

bash
# If your backend is in a folder called 'server' or 'backend'
cd server
# or
cd backend
 
# Check what's inside
ls
# You should see: package.json, index.js, etc.

Step 6.2: Install dependencies:

bash
# Using npm (comes with Node.js)
npm install
 
# OR use pnpm (faster alternative)
npm i -g pnpm
pnpm install

Step 6.3: Create environment variables:

bash
# nano is a text editor in terminal
nano .env

Add your environment variables (use arrow keys to navigate):

env
MONGODB_URI=mongodb://127.0.0.1/your-database-name
JWT_SECRET=your-secret-key-here-make-it-long-and-random
PORT=8000
NODE_ENV=production

Nano Editor Controls:

  • Ctrl + O then Enter to save
  • Ctrl + X to exit

Verify your .env file:

bash
# Display file contents
cat .env

Use PM2 for Production

Why PM2? PM2 keeps your app running 24/7, restarts it if it crashes, and manages logs.

Step 6.4: Install PM2 globally:

bash
npm i -g pm2
 
# Verify installation
pm2 -v
# Output: 5.3.0 (or similar)

Step 6.5: Start your backend with PM2:

bash
# Make sure you're in your backend folder first!
# The "--" is important - don't forget the space!
pm2 start npm --name "backend" -- start
 
# View all running apps
pm2 ls
 
# View logs in real-time (Ctrl+C to exit)
pm2 logs backend
 
# Save PM2 configuration
pm2 save
 
# Setup PM2 to start on server reboot
pm2 startup
# Copy and run the command it shows you

Step 6.6: Test if backend is working:

bash
# This sends a request to your backend
curl http://localhost:8000
# You should see your API response
 
# Or test a specific route
curl http://localhost:8000/api/health
Success

✅ PM2 automatically restarts your app if it crashes! ✅ Your backend will start automatically when server reboots!


🎨 Step 7: Setup Frontend

Step 7.1: Navigate to your frontend folder:

bash
# If you're in backend folder, go back and enter client folder
cd ../client
# or from project root:
cd client
 
# Verify you're in the right place
ls
# You should see: package.json, src/, public/, etc.

Step 7.2: Install frontend dependencies:

bash
pnpm install
# or
npm install
 
# This may take 2-5 minutes depending on project size

Step 7.3: Create environment variables:

bash
nano .env

Add your API URL (adjust based on your framework):

env
# For Vite (React/Vue)
VITE_API_URL=http://localhost:8000
 
# For Create React App
REACT_APP_API_URL=http://localhost:8000
 
# For Next.js
NEXT_PUBLIC_API_URL=http://localhost:8000

Press Ctrl + O, Enter to save, then Ctrl + X to exit.

Step 7.4: Build the production version:

bash
pnpm build
# or
npm run build
 
# This creates optimized files in 'dist' or 'build' folder
# Wait for build to complete (1-3 minutes)

Verify build was successful:

bash
# Check if build folder exists
ls dist  # for Vite
ls build # for Create React App
 
# You should see index.html, assets/, etc.
💬
Info

The build command creates optimized, minified files ready for production deployment.


🌐 Step 8: Setup Nginx

What is Nginx? Nginx is a web server that serves your frontend files to visitors. Think of it as the waiter that delivers your website to users.

Step 8.1: Install Nginx:

bash
sudo apt install nginx
 
# Verify installation
nginx -v
# Output: nginx version: nginx/1.18.0
 
# Check if Nginx is running
sudo systemctl status nginx

Step 8.2: Create Nginx configuration file:

bash
# Navigate to Nginx config directory
cd /etc/nginx/sites-available
 
# Create a config file named with your IP
nano <your-ip>.conf
# Example: nano 62.72.59.218.conf

Step 8.3: Add this configuration (replace <your-project> with your actual folder path):

nginx
server {
    listen 80;
    # Full path to your built frontend files
    root /root/<your-project>/client/dist;
    # For Create React App, use: /root/<your-project>/client/build
 
    location / {
        # This ensures React Router works properly
        try_files $uri $uri/ /index.html;
    }
}

Example: If your project is at /root/my-mern-app/client/dist:

nginx
root /root/my-mern-app/client/dist;

Save with Ctrl + O, Enter, then Ctrl + X.

Step 8.4: Enable your configuration:

bash
# Go to enabled sites folder
cd /etc/nginx/sites-enabled
 
# Remove default configuration
rm default
# or
rm default.conf
 
# Create symbolic link to your config
ln -s ../sites-available/<your-ip>.conf .
 
# Verify the link was created
ls -la

Step 8.5: Test and restart Nginx:

bash
# Test if configuration is valid
sudo nginx -t
# You should see: "syntax is ok" and "test is successful"
 
# If test passed, restart Nginx
sudo systemctl restart nginx
 
# Check Nginx status
sudo systemctl status nginx
# Should show "active (running)" in green

Step 8.6: Open firewall port:

bash
# Allow HTTP traffic (port 80)
sudo ufw allow 80
 
# Check firewall status
sudo ufw status

Step 8.7: Test your website:

Visit http://<your-vps-ip> in your browser! 🎉

Success

If you see your website, congratulations! Your frontend is now live on the internet!

⚠️
Warning

Not working? Check these:

  • Is the path in Nginx config correct? (ls /root/<your-project>/client/dist)
  • Did Nginx test pass? (sudo nginx -t)
  • Is Nginx running? (sudo systemctl status nginx)
  • Is port 80 open? (sudo ufw status)

🌍 Step 9: Connect Custom Domain

Why use a domain? Instead of using http://62.72.59.218, you can use https://yourdomain.com - much more professional!

Step 9.1: Configure DNS Settings

Go to your domain registrar (GoDaddy, Namecheap, Hostinger, etc.) and find DNS settings.

Add these A records:

NameTypePoints ToTTL
@Ayour-vps-ip3600
wwwAyour-vps-ip3600
apiAyour-vps-ip3600

What do these mean?

  • @ = Your root domain (yourdomain.com)
  • www = With www prefix (www.yourdomain.com)
  • api = Subdomain for backend (api.yourdomain.com)
  • TTL = Time To Live (how long DNS info is cached)
💬
Info

DNS Propagation: Wait 5-30 minutes for changes to take effect worldwide. Sometimes it can take up to 24 hours.

Check if DNS is working:

bash
# On your local computer terminal
ping yourdomain.com
# Should show your VPS IP address

Step 9.2: Frontend Domain Config

bash
cd /etc/nginx/sites-available
nano yourdomain.com.conf

Add configuration:

nginx
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    root /root/<your-project>/client/dist;
 
    location / {
        try_files $uri $uri/ /index.html;
    }
}

Backend API Config

bash
nano api.yourdomain.com.conf

Add configuration:

nginx
server {
    listen 80;
    server_name api.yourdomain.com www.api.yourdomain.com;
 
    location / {
        proxy_pass http://localhost:8000;
        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;
    }
}

Create symlinks:

bash
cd /etc/nginx/sites-enabled
ln -s ../sites-available/yourdomain.com.conf .
ln -s ../sites-available/api.yourdomain.com.conf .
systemctl restart nginx

Update frontend API URL:

bash
cd /root/<your-project>/client
nano .env
# Change to: VITE_API_URL=http://api.yourdomain.com
pnpm build

🔒 Step 10: Install SSL Certificate (HTTPS)

Install Certbot:

bash
sudo apt-get install certbot python3-certbot-nginx

Install SSL for frontend:

bash
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Install SSL for API:

bash
sudo certbot --nginx -d api.yourdomain.com -d www.api.yourdomain.com
💬
Info

First time? Enter your email, agree to terms, and choose 'N' for sharing email.

Update API URL to HTTPS:

bash
cd /root/<your-project>/client
nano .env
# Change to: VITE_API_URL=https://api.yourdomain.com
pnpm build

🎯 Useful PM2 Commands

bash
pm2 list              # List all apps
pm2 logs backend      # View logs
pm2 restart backend   # Restart app
pm2 stop backend      # Stop app
pm2 delete backend    # Delete app
pm2 save              # Save current list
pm2 startup           # Auto-start on reboot

🐛 Common Issues

MongoDB Not Starting?

bash
sudo systemctl status mongod
sudo systemctl restart mongod

Nginx Not Working?

bash
sudo nginx -t
sudo systemctl restart nginx

Frontend Not Loading?

Check build folder path in Nginx config:

bash
ls /root/<your-project>/client/dist

API Not Connecting?

Check if PM2 is running:

bash
pm2 list
curl http://localhost:8000

🎉 Congratulations!

Your MERN stack app is now live with:

  • ✅ Custom domain
  • ✅ HTTPS/SSL certificate
  • ✅ Auto-restart on crash (PM2)
  • ✅ Production-ready Nginx setup
Success

Your site is now accessible at https://yourdomain.com and API at https://api.yourdomain.com!


📚 Quick Reference

VPS Setup:

bash
ssh root@<vps-ip>
sudo apt update && sudo apt upgrade

Node.js:

bash
nvm install --lts

MongoDB:

bash
sudo systemctl start mongod

PM2:

bash
pm2 start npm --name "app" -- start

Nginx:

bash
sudo nginx -t
systemctl restart nginx

SSL:

bash
sudo certbot --nginx -d domain.com

Need help? Drop a comment below! 💬

Found this useful? Share it with other developers! 🚀