Every command to bring the backend up on a fresh Ubuntu server
Generate an SSH key, add the public key to GitHub, then clone the repo.
# Create an SSH key and print the public half to add at GitHub → Settings → SSH keys ssh-keygen -t ed25519 -C "you@example.com" -f ~/.ssh/id_ed25519 -N "" cat ~/.ssh/id_ed25519.pub # Clone and enter the backend git clone git@github.com:mohit49/social-mobile-app.git /home/mobile-app cd /home/mobile-app/backend
Via the official NodeSource repository.
apt-get update -y
apt-get install -y ca-certificates curl gnupg
mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
| gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" \
> /etc/apt/sources.list.d/nodesource.list
apt-get update -y
apt-get install -y nodejs
# Verify
node -v # v20.x
npm -v
curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc \
| gpg --dearmor -o /usr/share/keyrings/mongodb-server-8.0.gpg
echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/8.0 multiverse" \
> /etc/apt/sources.list.d/mongodb-org-8.0.list
apt-get update -y
apt-get install -y mongodb-org
# Start now and enable on boot
systemctl enable --now mongod
systemctl status mongod --no-pager
Config is segregated per NODE_ENV:
.env.development for local, .env.production for prod.
Only the *.example files are committed.
# Create the production env file from the template, then edit it cp .env.production.example .env.production # Generate strong JWT secrets and paste them in openssl rand -hex 32 # -> JWT_ACCESS_SECRET openssl rand -hex 32 # -> JWT_REFRESH_SECRET
APP_URL=https://mobilevps.tech,
MONGODB_URI=mongodb://127.0.0.1:27017/whereabout, and tighten
CORS_ORIGIN to your real client origins.
npm install
npm run build # compiles TypeScript -> dist/
PM2 keeps the app alive, restarts on crash, and brings it back after reboot.
# Install PM2 into the system prefix so it uses the persistent Node npm install -g pm2 --prefix /usr # Start from the ecosystem config (single fork instance — Socket.IO safe) pm2 start ecosystem.config.cjs # Persist the process list and enable boot startup pm2 save pm2 startup systemd -u root --hp /root systemctl enable --now pm2-root
ecosystem.config.cjs runs a single fork instance on purpose.
Serves the app on standard port 80 so the public URL has no :4000.
The app keeps serving /api/v1, /socket.io,
/uploads and the docs at root — Nginx just forwards to it.
apt-get install -y nginx # Site config lives at /etc/nginx/sites-available/whereabout # (proxies / and /socket.io to 127.0.0.1:4000 with websocket upgrade) ln -sf /etc/nginx/sites-available/whereabout /etc/nginx/sites-enabled/whereabout rm -f /etc/nginx/sites-enabled/default nginx -t # validate config systemctl enable --now nginx systemctl reload nginx
app.set('trust proxy', 1) and
APP_URL=https://mobilevps.tech (no port) so generated links
(emails, profile photos) are absolute and port-less.
# Public URLs — no port curl https://mobilevps.tech/health # {"success":true,"data":{"status":"ok","service":"whereabout-backend"}} # Socket.IO handshake curl "https://mobilevps.tech/socket.io/?EIO=4&transport=polling" # API base & docs in a browser # https://mobilevps.tech/api/v1/... # https://mobilevps.tech/
TCP 80. On UFW: ufw allow 'Nginx HTTP'
(or ufw allow 80/tcp). Port 4000 stays internal.
pm2 status # list processes pm2 logs whereabout-backend # tail logs pm2 restart whereabout-backend --update-env pm2 stop whereabout-backend pm2 delete whereabout-backend # Deploy a new version cd /home/mobile-app/backend git pull npm install npm run build pm2 restart whereabout-backend --update-env
npm shortcuts: npm run pm2:start, npm run pm2:restart, npm run pm2:logs, npm run pm2:stop.