Home JavaScript PM2 — Node.js Process Management and Deployment
Intermediate 8 min · 2026-07-12

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..

N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Drawn from code that ran under real load.

Follow
Production
production tested
July 19, 2026
last updated
2,466
articles · all by Naren
Before you start⏱ 15-20 minutes
  • Basic Node.js and JavaScript knowledge. Familiarity with npm and Express.js is recommended.
 ● Production Incident
Quick Answer

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

✦ Definition~90s read
What is PM2?

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), and supports zero-downtime deployments via graceful reload. Cluster mode uses Node.js cluster module internally, spawning one worker per CPU core and distributing incoming connections across workers.

PM2 is like a pit crew for your Node.js app.

PM2 ecosystem.config.js defines application configuration (script path, environment variables, instances, max memory). Production features include startup hooks for server reboots (pm2 startup), log rotation (pm2-logrotate), and the PM2 Plus monitoring dashboard.

Plain-English First

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.

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

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.

terminalBASH
1
2
3
npm install -g pm2
pm2 start app.js --name my-app
pm2 status
Output
┌─────┬───────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ mode │ status │ ↺ │ cpu │ memory │ user │ watching │ uptime │ restarts │ version │
├─────┼───────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0 │ my-app │ fork │ online │ 0 │ 0% │ 28.3mb │ root │ disabled │ 2m │ 0 │ 1.0.0 │
└─────┴───────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
🔥PM2 is not a cluster module replacement
PM2's cluster mode uses Node's built-in cluster module under the hood. It's a wrapper, not a replacement. For advanced clustering logic, you may still need to use the cluster module directly.
📊 Production Insight
We once had a memory leak that crashed the process every 48 hours. Without PM2, the app would have been down until manual restart. With PM2, it restarted automatically, buying us time to fix the leak.
🎯 Key Takeaway
PM2 ensures your Node.js app stays running and restarts automatically on failure.
pm2-nodejs-process-management THECODEFORGE.IO PM2 Process Management Stack Layered architecture from OS to application Application Layer Node.js App | Express Server PM2 Process Layer Process Manager | Cluster Master | Watchdog Clustering Layer Load Balancer | Worker Processes Monitoring Layer Log Aggregator | Metrics Collector | Health Checker Deployment Layer Ecosystem Config | SSH Deploy | Startup Hooks OS Layer Linux Systemd | Init Scripts | File System THECODEFORGE.IO
thecodeforge.io
Pm2 Nodejs Process Management

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.

terminalBASH
1
2
3
4
5
6
7
8
9
10
11
# Install PM2 globally
npm install -g pm2

# Start an app
pm2 start app.js --name my-app

# Generate startup script (systemd)
pm2 startup

# Save process list
pm2 save
Output
[PM2] Init System found: systemd
[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u root --hp /root
[PM2] Saving current process list...
[PM2] Successfully saved in /root/.pm2/dump.pm2
⚠ Run pm2 startup as the same user as your app
If you run 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.
📊 Production Insight
We forgot to run pm2 save after adding a new process. After a server reboot, the new process didn't start. Always save after changes.
🎯 Key Takeaway
Install PM2 globally and set up startup scripts for automatic recovery after server reboot.

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.

terminalBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Stop a process
pm2 stop my-app

# Restart a process
pm2 restart my-app

# Reload with zero downtime (cluster mode only)
pm2 reload my-app

# Delete a process from PM2
pm2 delete my-app

# Stop all processes
pm2 stop all
Output
[PM2] Stopping my-app...
[PM2] my-app stopped
[PM2] Restarting my-app...
[PM2] my-app restarted
[PM2] Reloading my-app...
[PM2] my-app reloaded
[PM2] Deleting my-app...
[PM2] my-app deleted
💡Use reload for zero-downtime deployments
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.
📊 Production Insight
We once used pm2 restart during a deployment and caused a 5-second outage. Switching to pm2 reload eliminated that.
🎯 Key Takeaway
Use pm2 reload for zero-downtime deployments in cluster mode.
pm2-nodejs-process-management THECODEFORGE.IO PM2 Process Management Stack Layered architecture for Node.js apps Application Layer Node.js App | Express Server | API Endpoints PM2 Process Layer Process Manager | Cluster Mode | Graceful Shutdown Monitoring Layer Logging | Health Checks | Metrics Dashboard Deployment Layer Ecosystem File | SSH Config | Deploy Scripts Infrastructure Layer Load Balancer | Reverse Proxy | Multi-Core CPU THECODEFORGE.IO
thecodeforge.io
Pm2 Nodejs Process Management

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.

terminalBASH
1
2
3
4
5
6
7
8
# Start with max instances (one per CPU core)
pm2 start app.js -i max --name my-app

# Start with 2 instances
pm2 start app.js -i 2 --name my-app

# Scale to 4 instances
pm2 scale my-app 4
Output
[PM2] Starting /app/app.js in cluster_mode (max instances)
[PM2] Done.
┌─────┬───────────┬──────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ mode │ status │ ↺ │ cpu │ memory │ user │ watching │ uptime │ restarts │ version │
├─────┼───────────┼──────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0 │ my-app │ cluster │ online │ 0 │ 0% │ 28.3mb │ root │ disabled │ 2m │ 0 │ 1.0.0 │
│ 1 │ my-app │ cluster │ online │ 0 │ 0% │ 28.3mb │ root │ disabled │ 2m │ 0 │ 1.0.0 │
│ 2 │ my-app │ cluster │ online │ 0 │ 0% │ 28.3mb │ root │ disabled │ 2m │ 0 │ 1.0.0 │
│ 3 │ my-app │ cluster │ online │ 0 │ 0% │ 28.3mb │ root │ disabled │ 2m │ 0 │ 1.0.0 │
└─────┴───────────┴──────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
🔥Cluster mode requires stateless design
Since each worker is a separate process, in-memory sessions or caches won't be shared. Use external stores like Redis for session data.
📊 Production Insight
We saw a 4x throughput increase after switching to cluster mode on a 4-core server. But we had to move session storage from memory to Redis.
🎯 Key Takeaway
Cluster mode leverages all CPU cores for better performance, but requires stateless application design.

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).

ecosystem.config.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
  apps: [{
    name: 'my-app',
    script: './dist/server.js',
    instances: 'max',
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'development',
      PORT: 3000
    },
    env_production: {
      NODE_ENV: 'production',
      PORT: 8080
    }
  }]
};
Try it live
⚠ Never commit secrets in ecosystem file
Use environment variables from your CI/CD pipeline or a secrets manager. The ecosystem file should only contain non-sensitive defaults.
📊 Production Insight
We once had a developer accidentally deploy with development environment because they forgot the --env production flag. The ecosystem file made it easy to enforce the correct environment in CI/CD.
🎯 Key Takeaway
Use ecosystem.config.js to manage environment-specific configurations cleanly.

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.

terminalBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
# View logs
pm2 logs my-app

# Install logrotate module
pm2 install pm2-logrotate

# Configure logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true

# Monitor processes
pm2 monit
Output
[PM2] pm2-logrotate installed
[PM2] Setting: pm2-logrotate:max_size = 10M
[PM2] Setting: pm2-logrotate:retain = 7
[PM2] Setting: pm2-logrotate:compress = true
# pm2 monit opens a terminal UI showing CPU/memory per process
💡Log rotation is essential in production
Without log rotation, logs can fill up your disk and crash the server. Always configure pm2-logrotate or use an external log shipper.
📊 Production Insight
We had a production incident where logs grew to 50GB and caused disk full errors. After setting up logrotate with 7-day retention, the problem disappeared.
🎯 Key Takeaway
Use pm2-logrotate for log management and pm2 monit for real-time monitoring.

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.

server.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const express = require('express');
const app = express();

app.get('/health', (req, res) => {
  res.status(200).send('OK');
});

const server = app.listen(process.env.PORT || 3000, () => {
  console.log('Server started');
});

// Graceful shutdown
process.on('SIGINT', () => {
  console.log('Shutting down gracefully...');
  server.close(() => {
    console.log('Server closed');
    process.exit(0);
  });
});
Try it live
🔥Set kill-timeout appropriately
If your app takes longer to shutdown, increase --kill-timeout in the ecosystem file. Otherwise, PM2 will forcefully kill it, potentially causing data loss.
📊 Production Insight
We had a database connection pool that wasn't closed on shutdown, causing connection leaks. Adding a SIGINT handler fixed it.
🎯 Key Takeaway
Implement graceful shutdown to avoid dropped connections and data loss during restarts.

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.

ecosystem.config.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module.exports = {
  apps: [{
    name: 'my-app',
    script: './dist/server.js',
    instances: 'max',
    exec_mode: 'cluster',
    env_production: {
      NODE_ENV: 'production'
    }
  }],
  deploy: {
    production: {
      user: 'deploy',
      host: '123.45.67.89',
      ref: 'origin/main',
      repo: 'git@github.com:user/my-app.git',
      path: '/var/www/my-app',
      'post-deploy': 'npm install && npm run build && pm2 reload ecosystem.config.js --env production'
    }
  }
};
Try it live
⚠ SSH keys must be set up correctly
PM2 deployment uses SSH. Ensure the deploy user has SSH access to the server and the Git repository. Test SSH connection before deploying.
📊 Production Insight
We used PM2 deploy for a side project and it worked well. But for our main product, we moved to GitHub Actions because we needed more complex pipelines (testing, staging, etc.).
🎯 Key Takeaway
PM2 deployment is a simple Git-based deploy tool suitable for small teams.

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.

terminalBASH
1
2
3
4
5
6
7
8
# Link to Keymetrics (PM2 Plus)
pm2 link <secret_key> <public_key>

# View metrics bus (for custom exporters)
pm2 metrics

# Use pm2 monit for local monitoring
pm2 monit
Output
[PM2] Linking to Keymetrics...
[PM2] Successfully linked to Keymetrics
# pm2 monit opens a terminal UI
🔥Keymetrics is not free for production
PM2 Plus (Keymetrics) has a free tier with limited features. For production, you'll likely need a paid plan. Consider open-source alternatives like Prometheus + Grafana.
📊 Production Insight
We used PM2 Plus to detect a memory leak early. The dashboard showed increasing memory usage over time, which we wouldn't have noticed otherwise.
🎯 Key Takeaway
Use PM2 Plus or external monitoring for production visibility.

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.

terminalBASH
1
2
3
4
5
6
7
8
9
10
11
# Bad: running as root
sudo pm2 start app.js

# Good: run as deploy user
pm2 start app.js

# Avoid --watch in production
pm2 start app.js --watch  # not recommended for production

# Check PM2 logs for errors
pm2 logs --lines 100
⚠ Don't use --watch in production
The watch feature is for development. In production, it can cause unnecessary restarts and complicate deployments.
📊 Production Insight
We once had a deployment script that triggered a file change, causing PM2 to restart the app mid-deployment. We disabled watch in production after that.
🎯 Key Takeaway
Avoid running as root, disable watch in production, and test your configuration.

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.

.github/workflows/deploy.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
name: Deploy
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install PM2
        run: npm install -g pm2
      - name: Deploy
        run: |
          pm2 deploy ecosystem.config.js production --force
        env:
          NODE_ENV: production
💡Use --force with pm2 deploy in CI
Without --force, pm2 deploy will abort if the local repo is not up to date. In CI, the repo is fresh, so --force is safe.
📊 Production Insight
We automated our deployment with GitHub Actions and PM2 deploy. It cut deployment time from 10 minutes (manual) to 2 minutes.
🎯 Key Takeaway
Integrate PM2 with CI/CD for automated, zero-downtime deployments.

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.

terminalBASH
1
2
3
4
5
6
7
8
9
10
11
12
# systemd service example (alternative to PM2)
[Unit]
Description=My Node App
After=network.target

[Service]
ExecStart=/usr/bin/node /path/to/app.js
Restart=always
User=deploy

[Install]
WantedBy=multi-user.target
🔥PM2 vs Docker: choose one
Running PM2 inside a Docker container is redundant. Docker already handles process management. Use PM2 on bare metal or VMs, not inside containers.
📊 Production Insight
We migrated from PM2 on VMs to Kubernetes. The transition was smooth because we had already designed stateless apps. PM2 was great for the VMs, but K8s gave us better scalability.
🎯 Key Takeaway
Use PM2 for VPS/bare metal; use Docker/Kubernetes for containerized environments.

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.

terminalBASH
1
2
3
4
5
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true
pm2 restart pm2-logrotate
Output
[PM2] Module pm2-logrotate installed
[PM2] Setting: max_size = 10M
[PM2] Setting: retain = 7
[PM2] Setting: compress = true
[PM2] Module pm2-logrotate restarted
⚠ Disk Space Alert
Without log rotation, a single Node.js error loop can fill your disk in minutes. Always configure pm2-logrotate before going live.
📊 Production Insight
Set max_size to 10-100MB depending on log verbosity. For high-traffic apps, use compress to save space.
🎯 Key Takeaway
Configure pm2-logrotate with max_size and retain to prevent disk overflow. Always test rotation in staging.

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.

terminalBASH
1
2
3
4
5
pm2 save
sudo pm2 startup systemd -u deploy --hp /home/deploy
# Run the printed command (example):
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u deploy --hp /home/deploy
systemctl status pm2-deploy
Output
[PM2] Init system found: systemd
[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u deploy --hp /home/deploy
● pm2-deploy.service - PM2 process manager
Loaded: loaded (/etc/systemd/system/pm2-deploy.service; enabled; vendor preset: enabled)
Active: active (running)
💡Always Save First
Run pm2 save before pm2 startup to ensure your current process list is persisted.
📊 Production Insight
After OS updates or PM2 upgrades, regenerate the startup script. For security, run PM2 as a non-root user.
🎯 Key Takeaway
Use 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.

terminalBASH
1
pm2-dev start app.js --watch ./src --ignore-watch 'node_modules logs'
Output
PM2-dev started with watching ./src
[PM2] App [app] (1) started in fork mode
[PM2] Watching path ./src for changes...
[STREAM] App [app] (1) restarted because file changed: src/index.js
🔥Dev vs Production
Use pm2-dev for quick dev cycles. For persistent dev servers, use pm2 start with --watch in ecosystem file.
📊 Production Insight
Never use pm2-dev in production. It's designed for interactive development only.
🎯 Key Takeaway
pm2-dev provides hot reload for development with PM2's process management. Use --ignore-watch to avoid restart loops.

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.

ecosystem.config.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module.exports = {
  apps: [{
    name: 'app',
    script: 'app.js',
    env: {
      NODE_ENV: 'development'
    },
    env_production: {
      NODE_ENV: 'production'
    }
  }],
  deploy: {
    production: {
      user: 'deploy',
      host: ['server1.com', 'server2.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'
    }
  }
};
Try it live
⚠ SSH Key Management
Use ssh-agent or a dedicated deploy key. Never store private keys in the repo.
📊 Production Insight
Always test deployment on a staging server. Use pm2 deploy production setup only once; subsequent runs use update.
🎯 Key Takeaway
PM2 deploy automates SSH-based deployments. Use 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.

ecosystem.config.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
module.exports = {
  apps: [{
    name: 'app',
    script: './dist/app.js',
    source_map_support: true,
    env: {
      NODE_ENV: 'production'
    }
  }]
};
Try it live
💡Source Maps in Production
Deploy source maps to production but restrict access via NGINX or use a separate monitoring service to avoid exposing code.
📊 Production Insight
Use a service like Sentry that can consume source maps directly, reducing the need to deploy maps to production.
🎯 Key Takeaway
Enable source_map_support in PM2 and deploy .map files to get readable stack traces in production.
PM2 vs Manual Process Management Comparing automation and reliability PM2 Manual (nohup, &) Process Restart on Crash Automatic restart Manual restart required Multi-Core Utilization Built-in cluster mode Manual cluster setup Log Management Centralized logs with rotation Separate log files per process Deployment Automation One-command deploy via ecosystem Manual SSH and file transfer Graceful Shutdown Configurable timeout and signals No built-in support THECODEFORGE.IO
thecodeforge.io
Pm2 Nodejs Process Management

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.

terminalBASH
1
2
3
pm2 monit
# In another terminal, generate load:
ab -n 1000 -c 10 http://localhost:3000/
Output
┌──────────────────┬──────┬────────┬──────┬──────────┬─────────┬──────────┐
│ App name │ id │ mode │ pid │ status │ restart │ uptime │
├──────────────────┼──────┼────────┼──────┼──────────┼─────────┼──────────┤
│ app │ 0 │ fork │ 1234 │ online │ 0 │ 2h │
└──────────────────┴──────┴────────┴──────┴──────────┴─────────┴──────────┘
Select process for details:
CPU: 45.2% Memory: 128.3MB Heap: 85.1MB Event Loop: 3.2ms
🔥Quick Diagnostics
Use pm2 monit for a real-time health check. For deep dives, use clinic.js or 0x.
📊 Production Insight
Set up alerts based on metrics from pm2 monit or use Keymetrics for historical data.
🎯 Key Takeaway
pm2 monit gives instant CPU/memory metrics. Pair with clinic.js for advanced profiling.
● Production incidentPOST-MORTEMseverity: high

The Silent Memory Leak That Took Down Production Twice

Symptom
PM2 logs showed 'Process exited with code 0' (SIGTERM) followed by 'Online' repeatedly every few hours. Memory usage graph showed a sawtooth pattern: gradual increase to ~90% then sudden drop. No error logs.
Assumption
The team assumed the image processing library (sharp) had a memory leak, or that the Node.js garbage collector was failing. They tried upgrading the library and increasing --max-old-space-size.
Root cause
A Readable stream from an HTTP request was piped to a sharp transform, but the sharp instance was not properly destroyed after processing. The stream remained open, holding references to large Buffers. Node.js could not garbage-collect them because the stream was still referenced in a closure.
Fix
Added a 'finally' block to destroy the sharp instance and unpipe the stream after processing, regardless of success or failure. Also added a memory usage metric alert at 80%.
Key lesson
  • 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.
⚙ Quick Reference
14 commands from this guide
FileCommand / CodePurpose
terminalnpm install -g pm2Why PM2? The Case for Process Management in Node.js
terminalpm2 stop my-appProcess Management
terminalpm2 start app.js -i max --name my-appClustering for Multi-Core Performance
ecosystem.config.jsmodule.exports = {Environment Management with Ecosystem File
terminalpm2 logs my-appLogging and Monitoring
server.jsconst express = require('express');Graceful Shutdown and Health Checks
terminalpm2 link Advanced
terminalsudo pm2 start app.jsCommon Pitfalls and Production Gotchas
.githubworkflowsdeploy.ymlname: DeployIntegrating PM2 with CI/CD Pipelines
terminal[Unit]Alternatives and When Not to Use PM2
terminalpm2 install pm2-logrotatepm2-logrotate
terminalpm2 savePM2 Startup with systemd
terminalpm2-dev start app.js --watch ./src --ignore-watch 'node_modules logs'pm2-dev Mode
terminalpm2 monitCPU/Memory Profiling with pm2 monit

Key takeaways

1
PM2 ensures uptime
It automatically restarts crashed processes and provides zero-downtime reloads via cluster mode.
2
Use ecosystem files
Declarative configuration for environments, scripts, and deployment settings keeps your setup reproducible.
3
Implement graceful shutdown
Handle SIGINT to close connections cleanly, preventing data loss and resource leaks.
4
Monitor and rotate logs
Use pm2-logrotate to prevent disk exhaustion and integrate with external monitoring for production visibility.
5
pm2-logrotate
Configure max_size and retain to prevent disk overflow. Always test rotation in staging.
6
PM2 Startup with systemd
Use pm2 startup systemd to auto-restart apps on reboot. Always run pm2 save first.
7
Deploy via SSH
PM2 deploy automates SSH-based deployments. Use pm2 reload for zero-downtime updates.
8
pm2 startup systemd
Ensures app restarts on reboot. Run pm2 save after any process change.
9
pm2 monit
Quick CPU/memory check. Use it before diving into advanced profiling tools.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does PM2 handle zero-downtime deployments?
Q02JUNIOR
What is the difference between 'pm2 start' and 'pm2 start --watch'?
Q03SENIOR
Explain how PM2 clustering works and when you would use it.
Q04JUNIOR
How do you configure PM2 to automatically restart a Node.js app if it cr...
Q05SENIOR
What is the purpose of the 'pm2 save' and 'pm2 resurrect' commands?
Q06SENIOR
How would you handle graceful shutdown in a PM2-managed app?
Q01 of 06SENIOR

How does PM2 handle zero-downtime deployments?

ANSWER
PM2 uses the 'reload' command, which restarts workers one by one, waiting for each new worker to be ready before killing the old one. This requires the app to be stateless and handle SIGTERM properly.
FAQ · 12 QUESTIONS

Frequently Asked Questions

01
What is the difference between pm2 restart and pm2 reload?
02
How do I set environment variables with PM2?
03
Can PM2 run multiple apps on the same server?
04
How do I handle graceful shutdown with PM2?
05
Is PM2 suitable for Docker containers?
06
How do I monitor PM2 processes remotely?
07
How do I integrate PM2 with NGINX as a reverse proxy?
08
Can PM2 run multiple apps with different Node.js versions?
09
How do I handle PM2 logs in a Docker container?
10
How do I set up PM2 to start on boot with systemd?
11
Can I use PM2 deploy with multiple servers?
12
Why are my source maps not working in PM2?
N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Drawn from code that ran under real load.

Follow
Verified
production tested
July 19, 2026
last updated
2,466
articles · all by Naren
🔥

That's Node.js. Mark it forged?

8 min read · try the examples if you haven't

Previous
Docker for Node.js Development — Complete Guide
40 / 47 · Node.js
Next
CI/CD for Node.js with GitHub Actions