MERN (MongoDB, Express, React, Node.js) and MEAN (with Angular) are popular full-stack JavaScript stacks. In production they become three pieces: a static frontend (the built React or Angular app), a Node.js API (Express), and a MongoDB database. Here’s how to deploy all three on one VPS, with Nginx in front.
Architecture on the VPS
Browser ──HTTPS──► Nginx
├── / → static frontend files (React/Angular build)
└── /api/ → Node.js (Express) on 127.0.0.1:3000
└── MongoDB on 127.0.0.1:27017
Requirements
- Ubuntu VPS with 2 vCPU and 2–4 GB RAM (MongoDB likes RAM for its cache). See how much RAM a VPS needs.
- Node.js LTS. See install Node.js.
- A domain pointing to the server.
1. MongoDB (local, secured)
Install MongoDB Community from MongoDB’s official repository. See install MongoDB on Ubuntu. Then:
- keep
bindIp: 127.0.0.1in/etc/mongod.conf(never expose 27017 publicly); - enable authentication, and create an app user:
// mongosh
use admin
db.createUser({ user: "admin", pwd: passwordPrompt(), roles: ["root"] })
use myapp
db.createUser({ user: "myapp", pwd: passwordPrompt(), roles: [{ role: "readWrite", db: "myapp" }] })
# /etc/mongod.conf
security:
authorization: enabled
sudo systemctl restart mongod
Or use a managed MongoDB service, and connect over TLS.
2. The API (Express)
sudo mkdir -p /opt/myapp && sudo chown $USER /opt/myapp
cd /opt/myapp && git clone https://git.example.com/team/myapp.git .
cd server && npm ci --omit=dev
Configuration via environment variables (server/.env, chmod 600):
NODE_ENV=production
PORT=3000
MONGODB_URI=mongodb://myapp:<password>@127.0.0.1:27017/myapp?authSource=myapp
JWT_SECRET=<long random string>
Run it with PM2, so it restarts on crashes and at boot:
sudo npm install -g pm2
pm2 start server.js --name api -i 2 # 2 instances (cluster mode)
pm2 save && pm2 startup # follow the printed command
Make sure Express listens on 127.0.0.1, or that the firewall blocks port 3000 externally. See PM2.
3. Build the frontend
cd /opt/myapp/client
npm ci
npm run build # React (Vite): dist/ Angular: dist/<project>/browser/
sudo mkdir -p /var/www/myapp && sudo cp -r dist/* /var/www/myapp/
Set the API base URL to a relative path (/api) at build time, so the frontend and API share the domain. That avoids CORS headaches.
4. Nginx: static files + API proxy
server {
server_name app.example.com;
root /var/www/myapp;
index index.html;
location /api/ {
proxy_pass http://127.0.0.1:3000;
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; # websockets / socket.io
proxy_set_header Connection "upgrade";
}
location / {
try_files $uri $uri/ /index.html; # SPA client-side routing
}
location ~* \.(js|css|png|jpg|svg|woff2)$ {
expires 30d;
access_log off;
}
}
try_files … /index.html makes deep links (/dashboard/settings) work with React Router and the Angular router. See deploy a React app with Nginx and deploy Angular with Nginx.
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d app.example.com
In Express, set app.set('trust proxy', 1), so secure cookies and client IPs work behind Nginx.
Docker alternative
Run api, mongo and an nginx (serving the built frontend) as one Compose stack, with MongoDB’s data in a named volume, and no published Mongo port. See Docker Compose in production.
Deploying updates
cd /opt/myapp && git pull
cd server && npm ci --omit=dev && pm2 reload api # zero-downtime reload in cluster mode
cd ../client && npm ci && npm run build && sudo rsync -a --delete dist/ /var/www/myapp/
Automate this with CI. See GitHub Actions deploy to VPS and zero-downtime Node.js deploys.
Backups
mongodump --uri="mongodb://admin:<pw>@127.0.0.1:27017/?authSource=admin" --archive=/backups/mongo-$(date +%F).gz --gzip
See MongoDB backup and restore.
Quick reference
| Piece | Setup |
|---|---|
| Frontend | Static build served by Nginx, try_files … /index.html |
| API | Express on 127.0.0.1:3000 via PM2 (cluster) |
| Database | MongoDB on localhost, auth enabled |
| Proxy | /api/ → Node, WebSocket headers |
| HTTPS | certbot --nginx |
Related: deploy Next.js on a VPS and twelve-factor app.