How to Run a Node.js App in Production with PM2
Deploy a Node.js application with PM2: process management, ecosystem config files, systemd startup integration, and structured log rotation.
Before you start
- ▸Node.js 18 LTS or newer installed and available in PATH
- ▸A non-root user account with sudo privileges
- ▸A working Node.js application with a defined entry point file
- ▸npm available (bundled with Node.js)
PM2 is a production-grade process manager for Node.js that handles automatic restarts, clustering, structured logging, and startup integration. It fills the gap between running node app.js in a terminal and a fully supervised service — without forcing you to write a systemd unit file by hand for every project.
Prerequisites
- Node.js and npm installed (v18 LTS or newer recommended)
- A working Node.js application with a defined entry point
- A non-root user with
sudoprivileges - Basic familiarity with the terminal
Install PM2
Install PM2 globally via npm. This works the same across all major distros as long as Node.js is already present.
sudo npm install -g pm2
Confirm the installation:
pm2 --version
You should see a version string such as 5.3.x. If the command is not found, ensure /usr/local/bin or your npm global bin directory is in your PATH.
Start Your Application
The simplest way to launch an app is with pm2 start, passing the entry point and an optional name. The name is used in logs and management commands, so choose something meaningful.
pm2 start /var/www/myapp/index.js --name myapp
Check the running process list immediately after:
pm2 list
Output will look roughly like:
┌────┬────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │
├────┼────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┤
│ 0 │ myapp │ default │ 1.0.0 │ fork │ 12345 │ 5s │ 0 │ online │
└────┴────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┘
A status of online means the process is running. A status of errored means Node.js threw an uncaught exception at startup — check logs immediately (covered below).
Cluster Mode
For CPU-bound or high-concurrency apps, PM2 can fork one worker per logical CPU core using Node's cluster module, with zero code changes required:
pm2 start /var/www/myapp/index.js --name myapp -i max
Replace max with a specific integer (e.g., 4) to pin the instance count. Cluster mode only works correctly if your app is stateless or uses an external session store like Redis.
Use an Ecosystem File
Hard-coding flags on the command line does not scale. An ecosystem file is a versioned configuration that lives alongside your code, specifying entry points, environment variables, cluster settings, and more.
Generate a starter file:
cd /var/www/myapp
pm2 ecosystem
This creates ecosystem.config.js. Edit it to match your application:
module.exports = {
apps: [
{
name: 'myapp',
script: './index.js',
instances: 'max',
exec_mode: 'cluster',
watch: false,
max_memory_restart: '512M',
env: {
NODE_ENV: 'development',
PORT: 3000,
},
env_production: {
NODE_ENV: 'production',
PORT: 8080,
},
},
],
};
Key fields to understand:
- instances: number of processes;
maxuses all CPU cores - exec_mode:
clusterorfork(single process) - watch: set
falsein production — file watching restarts on any change, including log rotation artifacts - max_memory_restart: PM2 will restart a worker that exceeds this threshold, guarding against memory leaks
- env_production: injected when you pass
--env productionat start
Start (or reload) using the ecosystem file with the production environment:
pm2 start ecosystem.config.js --env production
To apply config changes to already-running workers with zero downtime:
pm2 reload ecosystem.config.js --env production
reload cycles workers one at a time. Use restart only when a hard restart is acceptable, as it kills all workers simultaneously.
Configure Startup on Boot
PM2 integrates with systemd on modern distros. The startup subcommand generates and installs a systemd unit that re-launches PM2 (and all saved processes) after a reboot.
Generate the Startup Hook
pm2 startup
PM2 detects your init system and prints a sudo command you must run. It will look like:
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u youruser --hp /home/youruser
Copy and run that exact command — do not paraphrase it, as the paths are specific to your environment.
Save the Process List
After your apps are running as you want them, save the current process list so PM2 restores it on reboot:
pm2 save
This writes a dump file to ~/.pm2/dump.pm2. Any time you add or remove apps intended to survive reboots, run pm2 save again.
Verify the systemd Unit
systemctl status pm2-youruser.service
The unit should be active (running). If it is inactive or failed, check the journal:
journalctl -u pm2-youruser.service -n 50 --no-pager
Managing Logs
PM2 writes stdout and stderr to rotating log files under ~/.pm2/logs/ by default. Each app gets two files: appname-out.log and appname-error.log.
Tail Logs in Real Time
# All apps combined
pm2 logs
# One specific app
pm2 logs myapp
# Last 100 lines then follow
pm2 logs myapp --lines 100
Rotate Logs with pm2-logrotate
Without rotation, log files grow unbounded. Install the official PM2 log rotation module:
pm2 install pm2-logrotate
Then tune the defaults. These settings keep 30 rotated files, rotate when a file hits 10 MB, and check daily:
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 30
pm2 set pm2-logrotate:rotateInterval '0 0 * * *'
If you prefer to let the system's logrotate handle PM2 files instead, write a drop-in under /etc/logrotate.d/pm2 pointing at /home/youruser/.pm2/logs/*.log.
Essential Day-to-Day Commands
| Command | What it does |
|---|---|
pm2 list | Show all managed processes and their status |
pm2 monit | Real-time CPU/memory dashboard in the terminal |
pm2 stop myapp | Stop without removing from process list |
pm2 delete myapp | Stop and remove from process list |
pm2 describe myapp | Full metadata for a single process |
pm2 env 0 | Show environment variables for process ID 0 |
Troubleshooting
App keeps restarting (errored or high restart count)
Run pm2 logs myapp --err --lines 50 to isolate the Node.js stack trace. Common causes: missing .env file, wrong NODE_ENV, or a port already in use. Check with ss -tlnp | grep 8080.
Changes to ecosystem.config.js are not picked up
A plain pm2 restart does not re-read the config file. You must run pm2 reload ecosystem.config.js or pm2 delete myapp && pm2 start ecosystem.config.js.
Process does not survive reboot despite running pm2 save
Confirm the systemd unit is enabled: systemctl is-enabled pm2-youruser.service. If it prints disabled, run pm2 startup again and re-run the generated command. Also check that pm2 save was run after the app was started, not before.
Cluster mode workers crash immediately
The most common cause is application code that calls process.on('message') or uses IPC in a way that conflicts with PM2's own cluster IPC. Test in fork mode first to isolate whether the issue is cluster-specific.
Frequently asked questions
- What is the difference between pm2 restart and pm2 reload?
- restart kills all workers simultaneously and brings them back up, causing a brief outage. reload cycles workers one at a time so there is always at least one process accepting connections — use reload in production cluster mode.
- Should I run PM2 as root?
- No. Run PM2 as the application user. The only time root is needed is to execute the one-time sudo command generated by pm2 startup, which installs the systemd unit file.
- Can PM2 manage non-Node.js processes?
- Yes. PM2 can manage any executable using the --interpreter flag or by setting the interpreter field in the ecosystem file, making it usable for Python, Ruby, or shell scripts.
- Does PM2 replace a reverse proxy like Nginx?
- No. PM2 manages Node.js processes; Nginx or Caddy should still sit in front to handle TLS termination, static files, and routing. PM2 cluster mode and Nginx are complementary.
- How do I update PM2 itself without losing running processes?
- Run npm install -g pm2 to install the new version, then run pm2 update. The pm2 update command instructs the old daemon to hand off to the new binary without restarting your applications.
Related guides
Configure Prometheus Alertmanager
Configure Prometheus Alertmanager with routing trees, receivers, inhibition rules, grouping, Go templates, and PagerDuty/Slack on-call integrations.
Build an Intranet Server on Linux
Set up a complete small-office intranet on one Linux box: Nginx web server, dnsmasq local DNS, Samba file sharing, and a Wiki.js team wiki.
Build an nftables Firewall Script
Build a complete nftables firewall from scratch: tables, chains, sets, default-deny input policy, service allowlisting, and persistent systemd configuration.
Caddy as a Reverse Proxy
Set up Caddy as a reverse proxy with automatic HTTPS, load balancing, WebSocket passthrough, reusable snippets, and header control — no certbot required.