Running a Node.js app in production means more than node app.js in a screen session. PM2 is the process manager that turns Node into a real production runtime — it restarts crashed processes, runs them at boot, manages logs, balances multiple workers across cores, and gives you zero-downtime deploys. Pair it with Nginx as a reverse proxy and you have a stack that handles thousands of requests per second on a small VPS. This guide walks through the whole thing.
Prerequisites: Ubuntu 22.04 or 24.04 VPS, sudo user, an Nginx setup if you want a reverse proxy in front (covered in our Nginx + Let's Encrypt guide). 1GB RAM works for small apps; 2GB+ recommended for anything serious.
Steps in this guide
Step 1: Install Node.js via NodeSource
Don't use Ubuntu's default Node packages — they're typically 2-3 major versions behind. Use NodeSource for current LTS:
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
This installs Node 20 LTS (Iron). Replace 20.x with 22.x for the current LTS as of late 2025+. Verify:
node --version
npm --version
If you need multiple Node versions on the same box (rare for production, common for dev), use nvm instead.
Step 2: Install PM2 globally
sudo npm install -g pm2
Verify:
pm2 --version
Step 3: Run your first app under PM2
Assuming you have a Node app at /home/yourname/myapp/server.js:
cd /home/yourname/myapp
npm install --production
pm2 start server.js --name myapp
The --name flag gives the process a friendly name. Check status:
pm2 list
You'll see your app with status online, memory and CPU usage, and uptime. Useful PM2 commands:
pm2 logs myapp # tail logs
pm2 logs myapp --lines 100 # last 100 log lines
pm2 restart myapp # restart
pm2 stop myapp # stop (but keep config)
pm2 delete myapp # remove
pm2 monit # live dashboard
pm2 info myapp # detailed info
Step 4: Make PM2 start on boot
If your VPS reboots, you want PM2 (and your apps) to start back up automatically.
pm2 startup
This prints a command — copy and run it (it'll be something like sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u yourname --hp /home/yourname). It installs a systemd service that brings PM2 up on boot.
Save your current process list as the one to restore on boot:
pm2 save
Test it: sudo reboot, wait, SSH back in, run pm2 list. Your apps should be running.
Step 5: Cluster mode for multi-core CPUs
Node.js is single-threaded. On a multi-core VPS, a single Node process uses one core. PM2's cluster mode runs multiple instances behind a load balancer, using all your cores.
pm2 delete myapp
pm2 start server.js --name myapp -i max
The -i max flag spawns one instance per CPU core. PM2 distributes incoming connections across them automatically. Use a number instead of max if you want a specific count.
For this to work, your app needs to be stateless (or use a shared store like Redis for session state). Two cluster instances can't share in-process memory.
Step 6: Log rotation
PM2 logs every stdout/stderr line your app produces. On a busy app, log files balloon fast. Install the log rotation module:
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true
That keeps 7 compressed daily logs of up to 10MB each — plenty for debugging without filling your disk.
Step 7: Nginx as reverse proxy
Don't expose Node directly to the internet. Put Nginx in front for SSL termination, gzip, static-file serving, and a layer of protection. Assuming you've already installed Nginx with Let's Encrypt, the relevant server block:
server {
listen 443 ssl http2;
server_name app.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/app.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.yourdomain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 60s;
}
}
server {
listen 80;
server_name app.yourdomain.com;
return 301 https://$host$request_uri;
}
Your Node app listens on 127.0.0.1:3000 (or whatever port). Nginx terminates SSL and forwards traffic. The X-Forwarded-* headers tell your app the real client info. Your Node app needs to app.set('trust proxy', 1) in Express (or equivalent) to honor those headers.
Production Node.js needs real hardware
Dedicated CPU cores so cluster mode actually scales, NVMe storage so DB queries don't drag, KVM virtualization that doesn't break npm. Starting at $3.99/mo.
See VPS Plans →Zero-downtime deploys
The naive deploy: push new code, pm2 restart myapp, brief downtime while the process restarts. PM2's reload command is smarter — it brings up new instances before killing old ones, so requests in flight don't drop.
pm2 reload myapp
This works perfectly in cluster mode. PM2 starts a new instance, waits for it to be ready, terminates one old instance, starts the next new one, and so on. Production-grade rolling restart.
Ecosystem files for cleaner config
For real apps, use a PM2 ecosystem file instead of CLI flags:
pm2 ecosystem
Edit the generated ecosystem.config.js:
module.exports = {
apps: [{
name: 'myapp',
script: 'server.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000,
},
max_memory_restart: '500M',
error_file: './logs/error.log',
out_file: './logs/out.log',
time: true,
}],
};
Then deploy:
pm2 reload ecosystem.config.js --env production
pm2 save
Note max_memory_restart: '500M' — PM2 restarts the process if it exceeds 500MB of RAM. Catches memory leaks before they crash the server.
FAQ
Should I use PM2 or Docker for Node.js apps?
Both work. PM2 is simpler for single-server setups — install Node, install PM2, run your app. Docker is better when you want immutable deploys, multi-service stacks, or eventual orchestration (Kubernetes, Docker Swarm). For a small startup or solo project on a single VPS, PM2 is less overhead. For anything multi-service or scaling out, Docker pays off.
What about systemd instead of PM2?
systemd works fine for single-process Node apps. It's simpler than PM2 and uses fewer resources. PM2 wins when you need cluster mode, log management, ecosystem files, and zero-downtime reload. For "just keep this thing running and restart on boot," systemd is enough.
How much RAM does PM2 itself use?
PM2's daemon (the "God" process) uses about 30-60MB. Each app instance uses whatever Node + your app uses (typically 50-200MB depending on the app). On a 1GB VPS you can run 3-5 small Node apps comfortably with PM2.
Do I need PM2 Plus / PM2 Enterprise?
Almost certainly not. The free PM2 covers everything most production apps need. PM2 Plus adds remote monitoring dashboards and metrics aggregation across multiple servers — useful for fleets of 5+ servers, overkill for a single VPS.
Why is my Node app crashing under load?
Most common: file descriptor limits. Node defaults to 1024 open files; busy apps blow through that. See our Linux performance tuning guide for raising limits. Second most common: synchronous code blocking the event loop. Profile with --inspect and Chrome DevTools.