Configure PM2 for Production
20 Apr 2026pm2 start server.js works for a demo. It is not a production config — no cluster mode, no log rotation, and it won’t survive a reboot. Here’s what to actually set before you trust it with real traffic.
Use an ecosystem file, not CLI flags
pm2 start server.js --instances 4 --name myapp
…is fine to run once, but you’ll never remember the exact flags six months later, and it’s not something you can commit to the repo. Use ecosystem.config.js instead:
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',
merge_logs: true,
time: true
}]
}
pm2 start ecosystem.config.js
Commit this file. It’s the actual deploy config, not tribal knowledge someone has to remember.
Cluster mode: use all your cores
instances: 'max' runs one Node process per CPU core, load-balanced by PM2’s built-in round-robin. Node itself is single-threaded — without cluster mode, a 4-core server runs your app on exactly one of those cores while the rest sit idle.
Caveat: cluster mode means multiple processes, so anything in-memory (a cache, a rate limiter, WebSocket connection state) is not shared across instances unless you externalize it to Redis or similar. This is the most common bug people hit switching from fork to cluster mode — in-memory state that worked fine with one process now behaves inconsistently because requests land on different processes.
Restart on crash, but stop the crash loop
max_restarts: 10,
min_uptime: '10s',
restart_delay: 4000
Without min_uptime, a process that crashes immediately on every start (bad config, missing env var) restarts in an infinite tight loop, burning CPU and flooding your logs. min_uptime: '10s' means a crash before 10 seconds counts against max_restarts; after 10 restarts within that pattern, PM2 stops trying and marks it errored instead of looping forever.
Memory limit as a safety net, not a fix
max_memory_restart: '500M'
PM2 restarts a process if it exceeds this. This is not a substitute for finding an actual memory leak — it’s a guard so a slow leak causes a clean restart instead of the process getting OOM-killed by the kernel at an arbitrary moment, taking other processes on the box down with it.
Survive a server reboot
pm2 startup
This prints a command specific to your OS/init system (systemd on most modern Linux) — copy and run the line it gives you, it registers PM2 itself as a service. Then:
pm2 save
pm2 save snapshots the current process list so pm2 startup’s systemd unit knows what to bring back up after a reboot. Skip pm2 save and a reboot brings back an empty PM2 with nothing running — the most common “it worked yesterday” surprise with PM2 in production.
Log rotation — PM2 doesn’t do this by default
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 50M
pm2 set pm2-logrotate:retain 7
Without this, out.log and error.log grow forever. A busy app can fill a disk with log files in weeks, and a full disk takes down more than just logging.
Zero-downtime reload
pm2 reload myapp
Not pm2 restart — reload only works in cluster mode and restarts instances one at a time, keeping at least one alive to serve traffic throughout. restart kills everything and starts fresh, with a real gap where nothing is listening.
Monitoring, without a separate dashboard
pm2 monit
pm2 logs myapp --lines 100
pm2 monit gives you live CPU/memory per instance in the terminal. Enough for a small deployment; for anything you need alerting on, ship out_file/error_file to a real log aggregator rather than relying on someone watching pm2 monit.
The config that actually matters
ecosystem.config.js committed to the repo, pm2 startup + pm2 save run once per server, and pm2-logrotate installed. Those three things are the difference between PM2 as a toy and PM2 as something you can deploy and walk away from.