Node.js lets you run JavaScript on the server. PM2 manages processes: it automatically restarts the application on crashes and at system startup.
Installing Node.js via NodeSource
# Install Node.js 20 LTS
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Check the versions
node --version # v20.x.x
npm --version # 10.x.x
Installing PM2
sudo npm install -g pm2
Starting the Application
# Clone the project
git clone https://github.com/your/app.git /var/www/myapp
cd /var/www/myapp
npm install
npm run build # for Next.js
# Start with PM2
pm2 start npm --name "myapp" -- start
# For Express
pm2 start server.js --name "myapp"
PM2 Autostart on Reboot
# Save the current processes
pm2 save
# Configure autostart
pm2 startup systemd
# Copy and run the command that PM2 outputs
Useful PM2 Commands
pm2 list # list of all processes
pm2 logs myapp # application logs
pm2 restart myapp # restart
pm2 stop myapp # stop
pm2 monit # real-time CPU/RAM monitoring
Nginx as a Reverse Proxy
sudo nano /etc/nginx/sites-available/myapp
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
💡 Use
pm2 monit to track RAM usage in real time. For Node.js applications, we recommend a VPS with 1–2 GB RAM.Installing Node.js and PM2
# Node.js 20 LTS via NodeSource
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install nodejs -y
# PM2 globally
npm install -g pm2
# Check
node --version && pm2 --version
ecosystem.config.js
module.exports = {
apps: [{
name: 'myapp',
script: './server.js',
instances: 'max',
exec_mode: 'cluster',
watch: false,
max_memory_restart: '500M',
env: {
NODE_ENV: 'production',
PORT: 3000
}
}]
}
Main PM2 commands
| Command | Description |
|---|---|
| pm2 list | List of processes |
| pm2 logs myapp | Application logs |
| pm2 monit | Real-time monitoring |
| pm2 reload myapp | Zero-downtime reload |
| pm2 save | Save the process list |
| pm2 startup | Auto-start on boot |
Auto-start on reboot
pm2 save
pm2 startup
# Run the command that PM2 outputs
💡 In cluster mode, PM2 automatically balances the load between workers and restarts crashed instances.
Common Node.js and PM2 Errors
| Message | What it means |
|---|---|
| Error: listen EADDRINUSE: address already in use :::3000 | The port is already taken by a process PM2 isn't tracking — the app was started manually via node instead of pm2 start |
| PM2 process died with exit code 1 (restart loop) | The app crashes right after starting, most often due to missing environment variables or a Node.js version mismatch |
| npm ERR! code EACCES during a global install | npm install -g was run without permission or without a properly configured prefix for the current user |
| The app didn't come back after a server reboot | pm2 save wasn't run after the last change to the process list — autostart restores the old snapshot |
PM2 vs systemd: What to Use
| Criterion | PM2 | systemd |
|---|---|---|
| Zero-downtime reload | Yes, pm2 reload | No, only restart with dropped connections |
| Cluster mode for Node.js | Built-in exec_mode cluster | Requires manually running several unit files |
| Log rotation | Via pm2-logrotate | Via journald out of the box |
| Simplicity for non-Node processes | Not designed for that | Universal for any service |
When PM2 Isn't a Fit
- The project is already managed by Docker and an orchestrator (Kubernetes, Swarm) — restarting and scaling are the orchestrator's job
- The server runs a systemd-first distro, and the team wants one way to manage every service through systemctl
- The application isn't Node.js — PM2 can run arbitrary binaries, but that's not its main use case
- You need integration with cgroups and OS-level resource limits — systemd or containers fit that better
Verifying the Result
# Detailed process status: memory, uptime, restart count
pm2 describe myapp
# The app actually responds over HTTP
curl -sI http://localhost:3000/ | head -1
If pm2 describe shows a high restart count in a short time, that's the restart loop — look for the cause in pm2 logs myapp --lines 50. Related: basic Nginx setup and Docker Compose on a VDS as an alternative to PM2.