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.
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
@andwwwpoint 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:
dnf upgrade -y
adduser deploy && passwd deploy
usermod -aG wheel deployOn your own computer, create a key and copy it to the new user:
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:
PermitRootLogin no
PasswordAuthentication noThen 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:
sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-service={ssh,http,https}
sudo firewall-cmd --reloadLeave 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:
dnf module list nodejs
sudo dnf module enable -y nodejs:22
sudo dnf install -y nodejs git
node -vOn 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:
sudo npm install -g pm24. Install and secure MongoDB
Create /etc/yum.repos.d/mongodb-org-8.0.repo:
[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.ascCheck MongoDB's installation page for the correct path on release 10 or a newer MongoDB version. Then:
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:
// 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:
net:
bindIp: 127.0.0.1
security:
authorization: enabledRestart 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:
git clone https://github.com/you/your-app.git ~/app
cd ~/app/server
npm ci --omit=devCreate ~/app/server/.env and make it readable only by you (chmod 600 .env):
NODE_ENV=production
PORT=5000
MONGO_URI=mongodb://appuser:[email protected]:27017/appdb?authSource=appdbA minimal server.js for Express 5 and Mongoose 8:
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:
pm2 start server.js --name api
pm2 startup systemd # run the command it prints, with sudo
pm2 saveMore 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:
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
sudo dnf install -y nginxCreate /etc/nginx/conf.d/app.conf:
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:
sudo setsebool -P httpd_can_network_connect 1
sudo restorecon -Rv /var/www/app
sudo nginx -t && sudo systemctl enable --now nginx8. Add HTTPS
Certbot comes from EPEL:
sudo dnf install -y epel-release
sudo dnf install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.comCertbot 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
- Dump the database daily.Add a cron job for the
deployuser: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. - Copy backups off the server,for example with
rsyncto another machine or object storage, and delete old copies. - Test a restorewith
mongorestore --gzip --archive=…on a spare database. - Patch regularly.Run
sudo dnf upgradeon a schedule andnpm auditin your project, thenpm2 reload api.
10. Troubleshooting
| Symptom | Likely cause | Check |
|---|---|---|
| 502 Bad Gateway | API stopped, or SELinux blocked the proxy | pm2 logs api, then sudo ausearch -m avc -ts recent |
| Authentication failed | Wrong password or authSource in MONGO_URI | Log in with mongosh using the same URI |
| React routes give 404 on refresh | try_files missing | Check the location / block |
| Certbot fails | DNS not pointing to the VPS, or port 80 closed | dig 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.
- 1 vCPU
- 2 GB DDR4 RAM
- 64 GB NVMe SSD Storage
- 2 TB Monthly Bandwidth
- 2 vCPU
- 4 GB DDR4 RAM
- 128 GB NVMe SSD Storage
- 3 TB Monthly Bandwidth
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.
Full root access, your choice of Linux distribution and automated snapshots on every plan.
See VPS plans