What You'll Build
This guide deploys a Node.js application (Express, Fastify, Next.js, or any framework using a listening HTTP server) to an Ubuntu 22.04 or 24.04 LTS VPS. The production architecture is simple and robust: Node runs under PM2 for process management, systemd supervises PM2 itself, and Nginx handles TLS, compression, and static assets in front of it.
Install Node.js from NodeSource
The Ubuntu archive ships an older Node release. For current LTS versions (20.x or 22.x), use the NodeSource repository.
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
node --version
npm --version
Install build-essential so native addons (sharp, bcrypt, better-sqlite3) compile when needed:
sudo apt install build-essential -y
Create an App User
Never run Node as root. Create a dedicated system user with a home directory under /opt:
sudo adduser --system --group --home /opt/nodeapp nodeapp
sudo mkdir -p /opt/nodeapp && sudo chown nodeapp:nodeapp /opt/nodeapp
Deploy the Application
Switch to the app user and clone your repository. Install dependencies without dev packages and build any assets.
sudo -u nodeapp -H bash
cd /opt/nodeapp
git clone https://github.com/yourorg/yourapp.git app
cd app
npm ci --omit=dev
npm run build # if applicable
Copy your environment template to a .env file and fill in production values. Keep it readable only by the nodeapp user:
cp .env.example .env
chmod 600 .env
Test the app manually:
NODE_ENV=production node server.js
Once it listens on the configured port (commonly 3000), stop it with Ctrl+C.
Use PM2 for Process Management
PM2 keeps your Node process alive, handles log files, and can run multiple instances in cluster mode to use all CPU cores.
sudo npm install -g pm2
pm2 start server.js --name myapp -i max
pm2 save
The -i max flag spawns one worker per CPU core, giving you parallel request handling without changing application code. pm2 save persists the process list so PM2 can resurrect it on boot.
Generate the systemd integration so PM2 starts with the server:
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u nodeapp --hp /opt/nodeapp
PM2 prints a command — run it as root to install the service. From now on, systemctl status pm2-nodeapp shows the supervisor's status and PM2 resurrects on reboot.
Essential PM2 Commands
| Command | Purpose |
|---|---|
pm2 list | Show running apps |
pm2 logs myapp | Tail stdout and stderr |
pm2 restart myapp | Restart after code changes |
pm2 reload myapp | Zero-downtime reload (cluster mode) |
pm2 monit | Live CPU and memory dashboard |
pm2 save | Persist process list |
Configure Nginx as a Reverse Proxy
Nginx handles TLS, HTTP/2, gzip, and static file caching in front of Node. Install it and create a site config:
sudo apt install nginx -y
sudo nano /etc/nginx/sites-available/myapp
server {
listen 80;
server_name example.com www.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
The Upgrade and Connection headers matter for WebSocket apps (Socket.IO, Next.js hot reload, real-time features). Enable and reload:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
For tuning details — rate limiting, gzip, buffer sizes — see our Nginx reverse proxy guide.
Add HTTPS with Certbot
After DNS propagates, issue a Let's Encrypt certificate:
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com -d www.example.com
Certbot rewrites the Nginx config to listen on 443 and installs a renewal timer. For premium or wildcard certs, see our SSL certificate offerings.
Handle Deployments Cleanly
A simple deploy script can pull new code, install production dependencies, rebuild assets, and reload PM2 with zero downtime:
#!/bin/bash
set -e
cd /opt/nodeapp/app
git pull origin main
npm ci --omit=dev
npm run build
pm2 reload myapp --update-env
Run it as the nodeapp user via SSH key from your CI runner. For larger teams, consider blue-green deploys behind Nginx upstream groups.
Operational Tips
- Log to stdout — PM2 captures it; ship from the journal to your log aggregator
- Set memory limits with
pm2 start ... --max-memory-restart 500M - Use
NODE_ENV=productionin every environment file - Schedule backups of the database and uploads directory
- Add application monitoring (New Relic, Datadog, or open-source alternatives)
Running Node.js in production? MassiveGRID's Cloud VPS gives you NVMe storage, root access, and four global data centers for low-latency Node hosting. Explore our developer plans or contact our team to discuss your deployment.
Published by MassiveGRID, your trusted Linux VPS hosting partner. Explore our Cloud VPS plans for root-access Ubuntu hosting.