PM2 — Node.js Process Management and Deployment
PM2 Node.js process manager: process management, cluster mode, zero-downtime deployments, log management, startup scripts, and production monitoring..
20+ years shipping production JavaScript and front-end systems at scale. Drawn from code that ran under real load.
- ✓Basic Node.js and JavaScript knowledge. Familiarity with npm and Express.js is recommended.
PM2 is a production process manager for Node.js that keeps your application running indefinitely: it restarts crashed processes, manages log files, provides cluster mode (scaling across CPU cores), an
PM2 is like a pit crew for your Node.js app. You drive the car (your app), but PM2 keeps the engine running, changes tires (restarts crashes), monitors fuel (memory/CPU), and signals when something's wrong. Without it, your app is a solo driver with no support—one crash and you're stuck on the side of the road.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
Your Node.js server crashed at 3 AM and nobody noticed until the morning standup. A crashed process means lost revenue, angry users, and a bad reputation. PM2 is the most widely used process manager for Node.js in production because it does one thing well: it keeps your application running. When the process crashes, PM2 restarts it. When the server reboots, PM2 starts your application automatically. This article covers PM2 setup, cluster mode for multi-core servers, zero-downtime deployments, log management, and production monitoring.
Why PM2? The Case for Process Management in Node.js
Node.js runs on a single thread. If your app crashes, it's gone. No retries, no restarts, no graceful recovery. PM2 solves this by acting as a process manager: it keeps your app alive, restarts it on failure, and provides zero-downtime reloads. In production, you need more than just node app.js. You need logging, clustering, environment management, and monitoring. PM2 does all of that with minimal configuration. It's battle-tested at scale — used by companies like Uber and Walmart. If you're deploying Node.js to a server, PM2 should be your default choice.
Installation and Basic Setup
Install PM2 globally via npm. It works on Linux, macOS, and Windows (though Windows support is limited for some features like daemonization). After installation, you can start any Node.js script with pm2 start. The --name flag gives your process a human-readable name. PM2 will daemonize the process, meaning it runs in the background even if you close the terminal. Use pm2 status to see all running processes. For production, you'll want to set up a startup script so PM2 launches on server boot: pm2 startup. This generates a systemd (or equivalent) unit file. Then run pm2 save to persist the current process list.
pm2 startup as root but your app runs as a different user, the startup script may fail. Always run the command as the user that will run the app.pm2 save after adding a new process. After a server reboot, the new process didn't start. Always save after changes.Process Management: Start, Stop, Restart, and Delete
PM2 provides intuitive commands to manage processes. pm2 stop <name> stops the process but keeps it in the list. pm2 restart <name> stops and starts it. pm2 delete <name> removes it from PM2's list entirely. You can also use pm2 stop all, pm2 restart all, etc. For zero-downtime deployments, use pm2 reload instead of restart. Reload works by restarting workers one by one, ensuring at least one worker is always serving requests. This is critical for production deployments where you can't afford downtime.
pm2 reload only works in cluster mode. If your app is in fork mode, it will restart all at once, causing downtime. Use pm2 start app.js -i max to enable cluster mode.pm2 restart during a deployment and caused a 5-second outage. Switching to pm2 reload eliminated that.pm2 reload for zero-downtime deployments in cluster mode.Clustering for Multi-Core Performance
Node.js is single-threaded, but your server likely has multiple CPU cores. PM2's cluster mode spawns multiple processes (workers) that share the same port. Each worker runs on a separate core, handling requests concurrently. To enable cluster mode, use the -i flag: pm2 start app.js -i max spawns one worker per CPU core. You can also specify a number: -i 2. PM2 acts as a load balancer, distributing incoming connections across workers. This can dramatically improve throughput. However, be aware that each worker has its own memory space — shared state (like in-memory caches) won't be shared across workers. Use Redis or similar for shared state.
Environment Management with Ecosystem File
Hardcoding environment variables in commands is error-prone. PM2's ecosystem file (ecosystem.config.js) lets you define environments, scripts, and options declaratively. You can have separate configurations for development, staging, and production. The file exports an object with an apps array. Each app can have its own env and env_ objects. Use pm2 start ecosystem.config.js --env production to load the production environment. This file should be committed to your repository (without secrets — use a vault or CI/CD secrets for sensitive values).
--env production flag. The ecosystem file made it easy to enforce the correct environment in CI/CD.Logging and Monitoring
PM2 provides built-in logging. By default, it logs stdout and stderr to ~/.pm2/logs. You can view logs with pm2 logs or pm2 logs my-app. For production, you'll want log rotation to prevent disk exhaustion. PM2 has a pm2-logrotate module that compresses and rotates logs. Install it with pm2 install pm2-logrotate. You can configure retention, size, and compression. For real-time monitoring, use pm2 monit which shows CPU/memory usage per process. For historical metrics, consider integrating with a monitoring service like Datadog or New Relic via PM2's metrics bus.
Graceful Shutdown and Health Checks
When PM2 restarts or stops a process, it sends a SIGINT signal by default. Your app should listen for this signal and perform cleanup: close database connections, finish processing requests, etc. Use process.on('SIGINT', ...) to handle graceful shutdown. PM2 also supports health checks via the --kill-timeout option (default 1600ms). If your app doesn't exit within the timeout, PM2 sends SIGKILL. For zero-downtime reloads, implement a health check endpoint (e.g., /health) that returns 200 when the app is ready. PM2 can wait for this endpoint before routing traffic to a new worker.
--kill-timeout in the ecosystem file. Otherwise, PM2 will forcefully kill it, potentially causing data loss.Deployment with PM2: From Local to Production
PM2 has a built-in deployment system that uses SSH and Git. Define a deploy section in your ecosystem file with production and staging environments. Each environment specifies user, host, ref (branch), repo, path, and post-deploy commands. Run pm2 deploy ecosystem.config.js production setup to clone the repo on the server. Then pm2 deploy ecosystem.config.js production to deploy. This is a simple alternative to full CI/CD pipelines for small teams. However, for larger projects, consider using a dedicated CI/CD tool and only use PM2 for process management.
Advanced: Keymetrics and Monitoring Stack
PM2 offers a paid monitoring service called Keymetrics (now part of PM2 Plus). It provides real-time metrics, error tracking, and custom alerts. You can link your PM2 instance to Keymetrics with pm2 link <secret> <public>. This gives you a dashboard with CPU, memory, HTTP metrics, and more. For self-hosted monitoring, you can use PM2's metrics bus to export metrics to Prometheus or other systems. Alternatively, use pm2 monit for a quick overview. For production, always have some form of monitoring — don't rely solely on PM2's built-in tools.
Common Pitfalls and Production Gotchas
PM2 is powerful but has sharp edges. First, never run pm2 start as root unless necessary — it's a security risk. Second, watch out for the --watch flag in production: it restarts the app on file changes, which can cause unexpected downtime during deployments. Third, be careful with pm2 save and pm2 startup: if you save a process list that includes a broken app, it will keep failing on restart. Fourth, PM2's cluster mode uses round-robin load balancing, which can cause issues with sticky sessions (use Redis for session store). Finally, always test your ecosystem file locally before deploying.
Integrating PM2 with CI/CD Pipelines
PM2 works well with CI/CD. In your pipeline, after building the app, you can SSH into the server and run pm2 reload or pm2 restart. For zero-downtime, use pm2 reload. If you use PM2's deploy system, you can call it from CI. However, for more control, use a dedicated deployment tool like Ansible or a CI/CD service. Example GitHub Actions step: run: pm2 reload ecosystem.config.js --env production. Ensure the CI runner has PM2 installed and the ecosystem file present. Also, consider using a deployment user with limited permissions.
Alternatives and When Not to Use PM2
PM2 is not the only process manager. Alternatives include: systemd (built-in on Linux), Docker (container orchestration), and Kubernetes (full orchestration). If you're already using Docker, you don't need PM2 — Docker's restart policies handle crashes. If you're on Kubernetes, use liveness probes instead. PM2 shines in traditional VPS setups where you need a simple, battle-tested process manager. It's also great for development. But for microservices or containerized environments, skip PM2 and use the platform's native tools.
pm2-logrotate: Configuring Log Rotation for Production
In production, logs grow fast. Without rotation, a single log file can consume gigabytes of disk space and crash your server. PM2's built-in log rotation is handled by the pm2-logrotate module. Install it globally: pm2 install pm2-logrotate. The default configuration rotates logs daily, keeps 30 files, and compresses old logs. But you'll want to tune these values. Use pm2 set pm2-logrotate:max_size 10M to rotate when a log reaches 10MB instead of daily. Set pm2 set pm2-logrotate:retain 7 to keep only 7 rotated files. For high-traffic apps, set pm2 set pm2-logrotate:compress true to gzip old logs. You can also set pm2 set pm2-logrotate:workerInterval 30 to check every 30 seconds. To apply changes, restart PM2: pm2 restart pm2-logrotate. Always test rotation in staging: generate logs, wait for rotation, and verify files are created and compressed. A common pitfall: forgetting to set max_size leads to daily rotation only, which may still fill disk if logs are huge. Also, ensure your app writes to stdout/stderr, not to files directly, so PM2 captures them.
PM2 Startup with systemd: Auto-Restart on Reboot
PM2 can generate a systemd service to restart your apps on server reboot. This is critical for production. Run pm2 startup systemd as root or with sudo. It prints a command you must execute to enable the service. For example: sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u deploy --hp /home/deploy. This creates a systemd unit file at /etc/systemd/system/pm2-deploy.service. After running the printed command, verify with systemctl status pm2-deploy. The service runs as the specified user, so ensure that user has PM2 installed and apps saved (pm2 save). To disable, run pm2 unstartup systemd. A common mistake: running pm2 startup without pm2 save first — PM2 will start with no processes. Always save your process list after any changes. Also, if you update Node.js or PM2, regenerate the startup script. For multi-user environments, each user needs their own startup script. Test by rebooting a staging server and checking that all apps are running.
pm2 save before pm2 startup to ensure your current process list is persisted.pm2 startup systemd to auto-start apps on reboot. Always run pm2 save first and test on staging.pm2-dev Mode: Hot Reload for Development
PM2's pm2-dev mode watches your files and restarts the process on changes, similar to nodemon but with PM2's process management. Start with pm2-dev start app.js. By default, it watches all files in the current directory. You can customize with --watch paths: pm2-dev start app.js --watch ./src. It ignores node_modules automatically. Unlike pm2 start, pm2-dev runs in the foreground and logs to stdout. It's ideal for development because you get PM2's restart behavior (graceful shutdown, max restarts) without the daemon overhead. However, pm2-dev does not persist processes — it's meant for interactive use. For a more production-like dev environment, use pm2 start ecosystem.config.js --env development with watch: true in the ecosystem file. That runs as a daemon and watches files. But pm2-dev is simpler for quick iterations. One gotcha: pm2-dev uses --watch under the hood, which can cause infinite restart loops if your app writes to watched directories. Exclude logs and temp files with --ignore-watch. Also, pm2-dev does not support cluster mode — it runs a single instance. For multi-core dev, use pm2 start with -i max and --watch.
pm2-dev for quick dev cycles. For persistent dev servers, use pm2 start with --watch in ecosystem file.Deploy via SSH with ecosystem.json
PM2's deployment feature lets you push code to remote servers via SSH using an ecosystem.config.js file. Define deploy object with production and development environments. Example: deploy: { production: { user: 'deploy', host: 'your-server.com', ref: 'origin/main', repo: 'git@github.com:user/repo.git', path: '/var/www/app', 'post-deploy': 'npm install && pm2 reload ecosystem.config.js --env production' } }. Then run pm2 deploy production setup to clone the repo on the remote server. Subsequent deploys: pm2 deploy production update. PM2 handles SSH keys — ensure your public key is on the server. For multiple hosts, use an array: host: ['host1', 'host2']. You can also specify pre-deploy and post-deploy hooks. A common issue: SSH connection timeout — set ssh_options: 'StrictHostKeyChecking=no' (insecure, use only in trusted networks). Also, ensure the remote user has write permissions to the path. For zero-downtime, use pm2 reload instead of pm2 restart in post-deploy. Test deployment in a staging environment first.
pm2 deploy production setup only once; subsequent runs use update.pm2 reload for zero-downtime updates.Source Map Support for Error Stack Traces
When deploying minified or transpiled code (TypeScript, Babel), error stack traces point to compiled files, not original source. PM2 supports source maps if you enable them. First, generate source maps during build (e.g., tsc --sourceMap). Then, in your ecosystem file, set source_map_support: true (default is true in PM2 5+). PM2 uses the source-map-support module internally. Ensure the .map files are deployed alongside the compiled JS. PM2 reads the sourceMappingURL comment in the JS file. If your build process strips comments, you may need to configure the bundler to keep them. For webpack, set devtool: 'source-map'. For TypeScript, set sourceMap: true in tsconfig.json. Test by throwing an error in your app and checking the stack trace in PM2 logs — it should show original file paths and line numbers. If not, verify the map files are accessible and the paths are correct. A common pitfall: source maps are not loaded if the app runs in a different working directory — use absolute paths in the source map or set cwd in ecosystem file.
CPU/Memory Profiling with pm2 monit
PM2's built-in pm2 monit provides a real-time terminal dashboard showing CPU and memory usage per process. Launch it with pm2 monit. It displays a list of processes with CPU%, memory, and uptime. Select a process to see detailed metrics: heap usage, event loop latency, and garbage collection stats. This is useful for quick debugging without external tools. For deeper profiling, PM2 integrates with clinic.js and 0x. Install clinic: npm install -g clinic. Then run clinic doctor -- pm2 start app.js. This profiles the app and generates a flamegraph. For memory leaks, use clinic heap. For event loop issues, use clinic loop. PM2 also supports --node-args to pass V8 flags like --prof for CPU profiling. Generate a profile with node --prof app.js and process with node --prof-process. But pm2 monit is the first line of defense — if you see high CPU or memory, investigate further. A common mistake: relying on pm2 monit in production without setting up alerts. Use it as a diagnostic tool, not a monitoring solution.
pm2 monit for a real-time health check. For deep dives, use clinic.js or 0x.The Silent Memory Leak That Took Down Production Twice
- Always clean up streams and transform instances in 'finally' blocks, not just in success paths.
- Monitor memory trends, not just CPU or error rates—sawtooth patterns indicate leaks.
- Don't assume third-party libraries are the culprit; profile your own code first.
- Set PM2's 'max_memory_restart' as a safety net, but understand it masks the real issue.
| File | Command / Code | Purpose |
|---|---|---|
| terminal | npm install -g pm2 | Why PM2? The Case for Process Management in Node.js |
| terminal | pm2 stop my-app | Process Management |
| terminal | pm2 start app.js -i max --name my-app | Clustering for Multi-Core Performance |
| ecosystem.config.js | module.exports = { | Environment Management with Ecosystem File |
| terminal | pm2 logs my-app | Logging and Monitoring |
| server.js | const express = require('express'); | Graceful Shutdown and Health Checks |
| terminal | pm2 link | Advanced |
| terminal | sudo pm2 start app.js | Common Pitfalls and Production Gotchas |
| .github | name: Deploy | Integrating PM2 with CI/CD Pipelines |
| terminal | [Unit] | Alternatives and When Not to Use PM2 |
| terminal | pm2 install pm2-logrotate | pm2-logrotate |
| terminal | pm2 save | PM2 Startup with systemd |
| terminal | pm2-dev start app.js --watch ./src --ignore-watch 'node_modules logs' | pm2-dev Mode |
| terminal | pm2 monit | CPU/Memory Profiling with pm2 monit |
Key takeaways
pm2 startup systemd to auto-restart apps on reboot. Always run pm2 save first.pm2 reload for zero-downtime updates.pm2 save after any process change.Interview Questions on This Topic
How does PM2 handle zero-downtime deployments?
Frequently Asked Questions
20+ years shipping production JavaScript and front-end systems at scale. Drawn from code that ran under real load.
That's Node.js. Mark it forged?
8 min read · try the examples if you haven't