MERN Stack

Comprehensive Guide to Deploying and Managing MERN Stack Applications on RHEL-Based VPS

By the Domain India teamPublished 8 min read
Knowledge base article
Contents (11 sections)

The MERN stack (MongoDB, Express, React and Node.js) runs well on a RHEL-family server such as AlmaLinux or Rocky Linux. This guide takes a fresh VPS to a production setup: a secured server, Node.js LTS, MongoDB with authentication, your API under PM2, the React build served by nginx, and free HTTPS. It applies to a server you administer yourself; on shared hosting you can't install MongoDB or system packages.

Key takeaways

Create a sudo user with SSH key login and turn on firewalld. Install Node.js 22 or 24 and MongoDB 8.0 from their official repositories, enable MongoDB authentication and keep it bound to 127.0.0.1. Run the Express API with PM2 on a local port, build the React app with Vite, and let nginx serve the build and proxy /api to Node. Allow nginx to proxy under SELinux, then add a Let's Encrypt certificate with Certbot. Back up with mongodump and keep copies off the server.

1. What you need

  • A VPS running AlmaLinux 9 or Rocky Linux 9 (or 10) with root access. Commands below are for 9; release 10 differs only where noted.
  • A domain whose A records for @ and www point to the VPS IP address. See Managing DNS records.
  • Your MERN project in a Git repository, with the API and the React client in separate folders.

2. Secure the server first

Log in as root, then:

bash
dnf upgrade -y
adduser deploy && passwd deploy
usermod -aG wheel deploy

On your own computer, create a key and copy it to the new user:

bash
ssh-keygen -t ed25519 -C "[email protected]"
ssh-copy-id [email protected]

Once ssh [email protected] works with the key, turn off root and password logins. Create /etc/ssh/sshd_config.d/50-hardening.conf containing:

text
PermitRootLogin no
PasswordAuthentication no

Then run sudo systemctl reload sshd. Keep your current session open until a new login succeeds.

Turn on the firewall and allow only SSH and web traffic:

bash
sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-service={ssh,http,https}
sudo firewall-cmd --reload

Leave SELinux in enforcing mode; section 7 shows the one setting nginx needs.

3. Install Node.js LTS

Node.js 20 reached end of life in 2026, so use 22 or 24. On release 9, the AppStream module is the simplest route:

bash
dnf module list nodejs
sudo dnf module enable -y nodejs:22
sudo dnf install -y nodejs git
node -v

On release 10, run dnf list 'nodejs*' to see the versions packaged. If you need a newer major version than your release offers, use the NodeSource repository and read its setup script before running it.

Install PM2 globally:

bash
sudo npm install -g pm2

4. Install and secure MongoDB

Create /etc/yum.repos.d/mongodb-org-8.0.repo:

ini
[mongodb-org-8.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/9/mongodb-org/8.0/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://pgp.mongodb.com/server-8.0.asc

Check MongoDB's installation page for the correct path on release 10 or a newer MongoDB version. Then:

bash
sudo dnf install -y mongodb-org
sudo systemctl enable --now mongod
mongosh --eval 'db.runCommand({ ping: 1 })'

The old mongo shell no longer exists; use mongosh. Create an administrator and an application user:

javascript
// in mongosh
use admin
db.createUser({ user: "admin", pwd: passwordPrompt(), roles: ["root"] })
use appdb
db.createUser({ user: "appuser", pwd: passwordPrompt(), roles: [{ role: "readWrite", db: "appdb" }] })

Then edit /etc/mongod.conf so it contains:

yaml
net:
  bindIp: 127.0.0.1
security:
  authorization: enabled

Restart with sudo systemctl restart mongod. Do not open port 27017 in the firewall: your API talks to MongoDB on the same machine.

5. Deploy the Express API

As the deploy user:

bash
git clone https://github.com/you/your-app.git ~/app
cd ~/app/server
npm ci --omit=dev

Create ~/app/server/.env and make it readable only by you (chmod 600 .env):

ini
NODE_ENV=production
PORT=5000
MONGO_URI=mongodb://appuser:[email protected]:27017/appdb?authSource=appdb

A minimal server.js for Express 5 and Mongoose 8:

javascript
require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');

const app = express();
app.use(express.json());
app.get('/api/health', (req, res) => res.json({ ok: true }));

mongoose.connect(process.env.MONGO_URI)
  .then(() => app.listen(process.env.PORT, '127.0.0.1',
    () => console.log(`API on ${process.env.PORT}`)))
  .catch((err) => { console.error(err); process.exit(1); });

Binding to 127.0.0.1 keeps the API reachable only through nginx. Start it with PM2 and make it survive reboots:

bash
pm2 start server.js --name api
pm2 startup systemd      # run the command it prints, with sudo
pm2 save

More detail: PM2 for Node.js.

6. Build the React front end

Create React App is deprecated; new projects use Vite (npm create vite@latest client -- --template react). Build the client and publish the output:

bash
cd ~/app/client
npm ci && npm run build
sudo mkdir -p /var/www/app
sudo cp -r dist/* /var/www/app/

In the React code, call the API with relative paths such as /api/health, so the same build works in development and production.

7. Configure nginx

bash
sudo dnf install -y nginx

Create /etc/nginx/conf.d/app.conf:

nginx
server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/app;

    location /api/ {
        proxy_pass http://127.0.0.1:5000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

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

The try_files line sends unknown paths to React's router. Allow nginx to proxy and fix file labels under SELinux, then start it:

bash
sudo setsebool -P httpd_can_network_connect 1
sudo restorecon -Rv /var/www/app
sudo nginx -t && sudo systemctl enable --now nginx

8. Add HTTPS

Certbot comes from EPEL:

bash
sudo dnf install -y epel-release
sudo dnf install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

Certbot edits the nginx file and installs a timer that renews the certificate automatically. Test renewal with sudo certbot renew --dry-run.

9. Backups and updates

  1. Dump the database daily.
    Add a cron job for the deploy user: mongodump --uri="$MONGO_URI" --gzip --archive=$HOME/backups/appdb-$(date +%F).gz. Load the URI from a protected file rather than typing the password into the crontab.
  2. Copy backups off the server,
    for example with rsync to another machine or object storage, and delete old copies.
  3. Test a restore
    with mongorestore --gzip --archive=… on a spare database.
  4. Patch regularly.
    Run sudo dnf upgrade on a schedule and npm audit in your project, then pm2 reload api.

10. Troubleshooting

SymptomLikely causeCheck
502 Bad GatewayAPI stopped, or SELinux blocked the proxypm2 logs api, then sudo ausearch -m avc -ts recent
Authentication failedWrong password or authSource in MONGO_URILog in with mongosh using the same URI
React routes give 404 on refreshtry_files missingCheck the location / block
Certbot failsDNS not pointing to the VPS, or port 80 closeddig A example.com +short and firewall-cmd --list-services

11. Running this on Domain India

A Domain India VPS is self-managed with full root access on KVM virtualisation, so this guide applies as written. The VPS page lists AlmaLinux and Rocky Linux among the operating systems offered, and plans include automated snapshots, a useful second layer beside your own database dumps. VPS plans don't include cPanel. Prices on the cards are live and exclude 18% GST.

VPS Starter
₹552.65/mo + GST
  • 1 vCPU
  • 2 GB DDR4 RAM
  • 64 GB NVMe SSD Storage
  • 2 TB Monthly Bandwidth
See plan details
VPS Basic
₹1,105.30/mo + GST
  • 2 vCPU
  • 4 GB DDR4 RAM
  • 128 GB NVMe SSD Storage
  • 3 TB Monthly Bandwidth
See plan details

If you'd rather not manage a server, the App Platform detects and builds Node.js apps automatically and includes PostgreSQL and SSL, but no MongoDB, so you would use an external MongoDB service. See Getting started with the App Platform.

Which Node.js version should I use for a MERN app in 2026?

Use Node.js 22 or 24, which are the supported LTS lines. Node.js 20 and older have reached end of life.

Should MongoDB be open to the internet?

No. Keep bindIp set to 127.0.0.1, enable authorization, and don't open port 27017 in the firewall. Your API connects locally.

Why does nginx return 502 Bad Gateway on a RHEL-family server?

Either the Node.js API is not running, or SELinux is blocking nginx from connecting to it. Check pm2 logs, and run sudo setsebool -P httpd_can_network_connect 1.

Is Create React App still recommended?

No. Create React App is deprecated. Start new React projects with Vite or a framework, and serve the production build with nginx.

Can I run a MERN app on Domain India shared hosting?

Shared hosting can run small Node.js apps from the control panel, but you can't install MongoDB there. For a full MERN stack, use a VPS, or the App Platform with an external MongoDB service.

How do I keep my app running after a reboot?

Start it with PM2, run pm2 startup systemd and the command it prints, then pm2 save. PM2 then restores your app when the server boots.

Ready to deploy? Compare VPS plans, look at the App Platform if you'd rather not manage a server, or open a support ticket with questions about which fits your app.

Deploy your MERN app on a VPS

Full root access, your choice of Linux distribution and automated snapshots on every plan.

See VPS plans

Was this article helpful?

Your answer helps us decide what to improve next.

Still need help? Open a support ticket and our team will reply.

Prefer an app? Add this site to your home screen.Get the app