PM2 on the Server: A Comprehensive Guide to Process Management for Node.js Applications
Learn how to use PM2 professionally to run and manage Node.js applications in production. This comprehensive guide covers process management, clustering, environment variables, logging, graceful shutdowns, startup configuration, deployments, monitoring, and common mistakes.
Running a Node.js application locally is easy:
node server.jsBut production is a different environment.
What happens when the application crashes? What if the server reboots? How do you run multiple Node.js processes? How do you manage environment variables? How do you inspect logs? How do you gracefully restart an application after deploying new code?
This is where PM2 becomes useful.
PM2 is a process manager for Node.js applications. It keeps applications running, provides process monitoring, handles logs, supports clustering, and can automatically restore processes after a server restart.
This guide covers how to use PM2 in a production server, from installation and basic commands to ecosystem configuration, deployments, logging, clustering, and common mistakes.
What Is PM2?
PM2 is a production process manager designed primarily for Node.js applications.
Without a process manager, you might start an application with:
node server.jsIf the application crashes, the process stops.
If you close your SSH session, depending on how the process was started, it may also terminate.
After a server reboot, the application won't automatically come back unless something starts it again.
PM2 adds a management layer:
Linux Server
│
▼
PM2
┌──────────┼──────────┐
▼ ▼ ▼
API Server Worker Cron
│
▼
Node.js AppPM2 can:
- Start applications
- Restart crashed processes
- Restart applications after server reboots
- Manage multiple applications
- Manage multiple instances of an application
- Collect logs
- Monitor processes
- Manage environment variables
- Perform graceful reloads
- Run applications in cluster mode
Why Use PM2 in Production?
You technically don't need PM2 to run Node.js in production.
Linux can run Node.js processes perfectly well.
The problem is process lifecycle management.
Imagine running:
node server.jsand then the process crashes at 3 AM.
Nobody is available to run:
node server.jsagain.
With PM2:
Node.js application
│
▼
PM2
│
├── Process crashes
│
▼
Detect failure
│
▼
Restart appThis is one of PM2's primary benefits.
Installing PM2
PM2 is normally installed globally with npm:
npm install -g pm2Verify the installation:
pm2 --versionYou should see the installed PM2 version.
It's generally better to install PM2 globally on a server because it acts as server-level process-management infrastructure rather than being an application dependency.
Starting a Node.js Application
Suppose your application has this structure:
my-app/
├── package.json
├── src/
└── server.jsYou could normally start it with:
node server.jsWith PM2:
pm2 start server.jsPM2 assigns the application a process entry.
Check it with:
pm2 listYou might see something similar to:
┌────┬─────────────┬────────┬─────────┬─────────┐
│ id │ name │ mode │ status │ memory │
├────┼─────────────┼────────┼─────────┼─────────┤
│ 0 │ server │ fork │ online │ 72 MB │
└────┴─────────────┴────────┴─────────┴─────────┘The important part is:
status: onlineGive Your Application a Meaningful Name
Instead of allowing PM2 to derive the process name, explicitly name it:
pm2 start server.js --name apiNow:
pm2 listwill show:
apiThis becomes particularly useful when a server runs multiple applications:
api
worker
admin
notificationsYou can then manage them individually:
pm2 restart api
pm2 restart worker
pm2 restart notificationsMeaningful process names become increasingly valuable as infrastructure grows.
Running npm Scripts With PM2
You don't always start Node.js applications directly.
Many projects use:
{
"scripts": {
"start": "node dist/server.js"
}
}In that case, PM2 can execute the npm script:
pm2 start npm --name api -- startThe syntax is important:
pm2 start npm --name api -- start
│ │
│ └── npm script
└────────── PM2 application nameFor example:
pm2 start npm --name api -- startis effectively asking PM2 to manage:
npm startRunning a TypeScript Application
In production, you generally shouldn't rely on ts-node or a development runtime unless there is a specific reason to do so.
A typical TypeScript project might build into:
dist/
├── server.js
├── routes/
└── services/Your deployment process can be:
npm ci
npm run build
pm2 restart apiPM2 should normally run the compiled JavaScript:
pm2 start dist/server.js --name apiThis keeps the production runtime separate from the TypeScript development tooling.
Understanding PM2 Process Modes
PM2 primarily operates applications in two useful modes:
fork
clusterFork Mode
Fork mode runs a single application process.
pm2 start server.js --name apiConceptually:
PM2
│
└── Node.js processThis is often perfectly adequate for smaller applications.
Cluster Mode
Node.js applications normally execute JavaScript on a single main thread.
If your server has multiple CPU cores, you may want multiple Node.js instances.
PM2 can run an application in cluster mode:
pm2 start server.js -i 4Or:
pm2 start server.js -i maxmax tells PM2 to use the available CPU cores.
Conceptually:
PM2
│
┌─────────┼─────────┐
▼ ▼ ▼
Node.js Node.js Node.js
Worker 1 Worker 2 Worker 3PM2 handles distributing incoming connections between cluster instances.
Should You Always Use Cluster Mode?
No.
More processes don't automatically mean better performance.
Cluster mode can be useful for CPU utilization and throughput, but your application needs to be designed appropriately.
For example, relying on process-local memory can cause problems.
Imagine:
const sessions = new Map();With one process:
Request
↓
Node process
↓
sessionsWith four processes:
Load balancing
│
┌────────────┼────────────┐
▼ ▼ ▼
Process 1 Process 2 Process 3
│ │ │
sessions sessions sessionsEach process has its own memory.
Therefore, in a clustered application, shared state should generally live in something like:
- PostgreSQL
- MySQL
- Redis
- another external data store
rather than relying on process memory.
PM2 Ecosystem Files
Once your application becomes more complex, using long command-line arguments becomes inconvenient.
For example:
pm2 start dist/server.js \
--name api \
--instances 4 \
--max-memory-restart 500MInstead, use an ecosystem configuration.
Create:
ecosystem.config.jsExample:
module.exports = {
apps: [
{
name: "api",
script: "./dist/server.js",
instances: 4,
exec_mode: "cluster",
max_memory_restart: "500M"
}
]
};Start it with:
pm2 start ecosystem.config.jsNow your application's process configuration is version-controlled.
That's a significant improvement.
A More Complete Ecosystem Configuration
A production configuration might look like:
module.exports = {
apps: [
{
name: "api",
script: "./dist/server.js",
instances: "max",
exec_mode: "cluster",
max_memory_restart: "500M",
time: true,
env: {
NODE_ENV: "development"
},
env_production: {
NODE_ENV: "production"
}
}
]
};You can then start the production environment with:
pm2 start ecosystem.config.js --env productionThis allows environment-specific configuration without creating separate process definitions.
Environment Variables
Environment variables are an important part of production deployments.
For example:
module.exports = {
apps: [
{
name: "api",
script: "./dist/server.js",
env_production: {
NODE_ENV: "production",
PORT: 3000
}
}
]
};Then:
pm2 start ecosystem.config.js --env productionYour application can access:
process.env.NODE_ENV
process.env.PORTHowever, be careful about putting secrets directly into the ecosystem file.
Avoid committing things such as:
DATABASE_PASSWORD: "super-secret-password"into Git.
Use your server's environment management or a proper secrets-management solution instead.
PM2 and .env Files
If your application uses something like dotenv, you can keep environment configuration separate from PM2.
For example:
.env
.env.exampleYour application can load:
import "dotenv/config";and access:
process.env.DATABASE_URLThis is often preferable to putting sensitive credentials directly inside ecosystem.config.js.
Also remember:
.envshould generally be excluded from Git:
.env
.env.*
!.env.exampleThe exact pattern should match your project's environment-file strategy.
Managing Applications
Once an application is running, you'll frequently use a small set of PM2 commands.
List Processes
pm2 listor:
pm2 lsStart
pm2 start ecosystem.config.jsStop
pm2 stop apiRestart
pm2 restart apiDelete
pm2 delete apiThis removes the process from PM2's process list.
Reload vs Restart
This distinction becomes important in production.
A restart completely restarts the process:
pm2 restart apiA reload is designed to replace workers more gracefully, particularly in cluster mode:
pm2 reload apiFor applications where zero-downtime behavior matters, reloads can be preferable.
However, whether you actually achieve zero downtime depends on your application, load balancer, connection handling, and deployment architecture.
PM2 doesn't magically make every application zero-downtime.
Graceful Shutdown
This is one of the most overlooked aspects of production Node.js applications.
Suppose your application is handling a request when PM2 restarts it.
If the application immediately terminates, that request could fail.
A better application handles termination signals:
const server = app.listen(PORT);
process.on("SIGTERM", () => {
console.log("SIGTERM received");
server.close(() => {
console.log("HTTP server closed");
process.exit(0);
});
});The basic idea is:
PM2
│
└── SIGTERM
│
▼
Application stops accepting work
│
▼
Existing requests finish
│
▼
Connections close
│
▼
Process exitsThis becomes particularly important when using:
- cluster mode
- load balancers
- containers
- rolling deployments
- Kubernetes
- zero-downtime deployment strategies
Process management and application lifecycle management need to work together.
Automatic Restart
One of PM2's most useful features is automatic restart.
If the application crashes:
Node.js
│
X
crash
│
▼
PM2 detects it
│
▼
restartYou can inspect the process afterward:
pm2 listFor a deeper look:
pm2 logs apiMemory-Based Restarts
Applications can occasionally consume more memory than expected.
PM2 allows you to configure a memory threshold:
{
name: "api",
script: "./dist/server.js",
max_memory_restart: "500M"
}If the process exceeds the configured threshold, PM2 can restart it.
This is useful as a safety mechanism.
But don't confuse it with fixing a memory leak.
If your application continuously grows from:
100 MB
200 MB
300 MB
400 MB
500 MBand then gets restarted, you've only hidden the symptom.
You should still investigate the underlying memory issue.
PM2 Logs
Logs are one of the first things you'll need when troubleshooting production applications.
View logs:
pm2 logsFor a specific application:
pm2 logs apiYou can also limit the number of lines:
pm2 logs api --lines 100This is useful when the application has been running for a long time.
Log Files
PM2 maintains application logs on the server.
You can inspect them with:
pm2 info apiThis can show useful information such as:
- Process ID
- Script path
- Node.js version
- Restart count
- Memory usage
- CPU usage
- Log paths
For production systems, don't assume PM2's local log files are a complete observability strategy.
For larger systems, consider centralized logging through tools such as:
- Elasticsearch
- Loki
- CloudWatch
- Datadog
- Grafana
- another centralized logging platform
PM2 is useful for local process logs, but production observability is a larger concern.
Clearing PM2 Logs
Logs can grow significantly.
You can clear PM2 logs with:
pm2 flushFor production systems, however, log rotation is preferable to periodically deleting everything.
PM2 provides a log rotation module:
pm2 install pm2-logrotateThen inspect the configuration:
pm2 conf pm2-logrotateThe exact settings should be adjusted according to your application's traffic and retention requirements.
Monitoring Applications
PM2 provides a simple monitoring interface:
pm2 monitYou can inspect:
- CPU usage
- Memory usage
- Processes
- Logs
For example:
┌──────────────────────────────────┐
│ CPU │
│ ████████ │
│ │
│ Memory │
│ ████████████ │
│ │
│ Processes │
│ api │
│ worker │
└──────────────────────────────────┘This is useful for quick server-side diagnostics.
For serious production monitoring, though, you'd generally want metrics and alerting outside PM2 as well.
Running Multiple Applications
A single VPS might host several Node.js applications:
Server
│
├── API
├── Admin
├── Worker
└── Notification ServicePM2 can manage all of them.
Your ecosystem file could contain:
module.exports = {
apps: [
{
name: "api",
script: "./api/dist/server.js"
},
{
name: "worker",
script: "./worker/dist/index.js"
},
{
name: "notifications",
script: "./notifications/dist/index.js"
}
]
};Then:
pm2 start ecosystem.config.jsYou can manage each application independently:
pm2 restart api
pm2 restart worker
pm2 restart notificationsThis is much cleaner than manually maintaining background processes.
PM2 and Server Reboots
One of the most important production features is restoring your processes after a server restart.
First:
pm2 startupPM2 will print a command specific to your system.
Run the command it provides.
Then save your currently managed processes:
pm2 saveThe conceptual flow is:
Server shutdown
│
▼
Server reboot
│
▼
Linux starts
│
▼
PM2 starts
│
▼
Saved PM2 processes restoredThis is critical for applications that need to survive machine reboots.
Don't Forget pm2 save
A common mistake is:
pm2 startupand then assuming everything is done.
pm2 startup configures the startup mechanism.
You should also save the currently managed process list:
pm2 saveAfter that, PM2 knows which processes it should restore.
Whenever you intentionally change the set of production processes, remember to update the saved process list.
A Production Deployment Workflow
Let's say your application lives at:
/var/www/apiA simple deployment might look like:
cd /var/www/api
git pull
npm ci
npm run build
pm2 reload apiThe basic flow is:
Git
│
▼
Pull latest code
│
▼
Install dependencies
│
▼
Build
│
▼
PM2 reload
│
▼
New application versionThis works for small deployments.
But there are important questions to consider:
- What happens if
npm run buildfails? - What happens if migrations fail?
- What happens if the new version crashes?
- How do you roll back?
- How do you know the deployment succeeded?
A production deployment system should answer these questions.
Don't Put Your Entire Deployment Into One Shell Command
You may eventually see scripts like:
git pull && npm install && npm run build && pm2 restart apiThis is convenient, but it isn't necessarily a robust deployment system.
For example, if the build succeeds but the application doesn't start correctly, you need a way to detect and recover from that state.
For more mature systems, use a deployment pipeline:
CI/CD
│
├── Build
├── Test
├── Package
├── Deploy
├── Health check
└── Rollback if necessaryPM2 can be one component of that system.
Health Checks Matter
PM2 can tell you:
status: onlineBut "online" doesn't necessarily mean your application is healthy.
Your Node.js process might be running while:
- the database is unavailable
- Redis is down
- an external API is failing
- migrations weren't applied
- the HTTP server isn't responding correctly
A better production application exposes a health endpoint:
GET /healthResponse:
{
"status": "ok"
}A monitoring system or load balancer can then verify actual application availability.
For example:
PM2
│
└── Node process → running
Health monitor
│
└── GET /health → HTTP 200These are different signals.
PM2 With Nginx
A common Node.js deployment architecture is:
Internet
│
▼
Nginx
│
reverse proxy
│
▼
PM2
│
┌───────┴───────┐
▼ ▼
Node.js Node.js
worker 1 worker 2Nginx can handle:
- TLS termination
- HTTP/2
- static assets
- domain routing
- reverse proxying
- request buffering
- access logs
PM2 handles:
- Node.js process lifecycle
- restarts
- clustering
- application logs
This separation of responsibilities is useful.
Example Nginx Configuration
A simple reverse proxy might look like:
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Now the architecture becomes:
https://api.example.com
│
▼
Nginx
│
▼
127.0.0.1:3000
│
▼
Node.jsThe Node.js process doesn't need to directly expose itself to the public internet.
PM2 vs systemd
PM2 isn't the only process manager available on Linux.
systemd is the native service manager on many Linux distributions.
For example, systemd can manage:
Node.js
Python
Go
Docker
custom servicesSo why use PM2?
PM2 provides Node.js-specific functionality such as:
- cluster mode
- convenient Node process management
- application-focused logs
- simple process monitoring
- ecosystem configuration
- developer-friendly commands
For a single Node.js application, either approach can be valid.
For infrastructure-heavy environments, systemd may be preferable.
For teams already using PM2 for multiple Node.js applications, PM2 can provide a convenient abstraction.
The important thing is to understand that PM2 isn't mandatory for production Node.js.
PM2 vs Docker
PM2 and Docker solve different problems.
Docker provides:
Application
+
Dependencies
+
Runtime environment
+
IsolationPM2 primarily provides:
Process management
+
Restarting
+
Monitoring
+
Clustering
+
LogsYou can even encounter both together:
Docker container
│
▼
PM2
│
▼
Node.js processesHowever, using PM2 inside Docker isn't automatically beneficial.
Containers are often managed by:
- Docker Compose
- Kubernetes
- ECS
- another container orchestrator
which already provide process restart and lifecycle management.
In such environments, adding PM2 may introduce unnecessary complexity unless you specifically need its features.
Common PM2 Mistakes
1. Running Everything as Root
Avoid:
sudo pm2 start server.jsunless you have a specific reason.
Ideally, the application should run under a dedicated non-root user.
This reduces the potential impact of a compromised application.
2. Hardcoding Secrets
Don't put:
DATABASE_PASSWORD: "..."into a committed ecosystem file.
Use appropriate secret management.
3. Assuming online Means Healthy
PM2 showing:
onlineonly tells you that the process is running.
It doesn't guarantee that your application is functioning correctly.
4. Running Development Commands in Production
Avoid:
npm run devfor production unless you have a specific architecture that requires it.
Production should generally run a built application:
npm run build
pm2 start dist/server.js5. Forgetting pm2 save
If you want processes restored after reboot:
pm2 savematters.
6. Using Cluster Mode Without Understanding State
If your application depends on:
const users = new Map();don't assume all cluster workers share that state.
They don't.
7. Using PM2 as a Complete Monitoring System
PM2 is useful for process monitoring.
It isn't a complete observability platform.
Production systems may also need:
- metrics
- tracing
- centralized logs
- alerting
- uptime monitoring
- application performance monitoring
A Practical Production Checklist
Before considering a PM2 deployment complete, check the following.
Application
- Application builds successfully
- Production dependencies are installed
- Environment variables are configured
- Secrets aren't committed to Git
- Health endpoint exists
- Graceful shutdown is implemented
PM2
- Application has a meaningful name
- Ecosystem configuration is defined where appropriate
- Restart behavior has been tested
- Memory limits are considered
- Logs are accessible
- Log rotation is configured if necessary
Server
- Application runs under an appropriate user
- Firewall is configured
- Nginx or another reverse proxy is configured where appropriate
- HTTPS is configured
- PM2 startup is configured
-
pm2 savehas been executed
Deployment
- Build process is repeatable
- Database migrations are handled safely
- Deployment failures are detectable
- Rollback strategy exists
- CI runs tests before deployment
A Simple PM2 Production Setup
For a relatively small Node.js application, you can keep the architecture straightforward:
Internet
│
▼
Nginx
│
▼
PM2
│
┌──────┴──────┐
▼ ▼
Node.js Node.js
worker 1 worker 2
│ │
└──────┬──────┘
│
▼
PostgreSQLPM2 handles the Node.js processes.
Nginx handles incoming HTTP traffic.
PostgreSQL handles persistent data.
A CI/CD system handles deployment.
A monitoring system handles observability.
Each component has a clear responsibility.
Final Thoughts
PM2 is easy to start using, but professional server management requires thinking beyond:
pm2 start server.jsThe real value comes from designing a predictable application lifecycle.
A reasonable progression looks like this:
Level 1
pm2 start
↓
Level 2
named processes
↓
Level 3
ecosystem configuration
↓
Level 4
startup + saved processes
↓
Level 5
logging + monitoring
↓
Level 6
graceful shutdown
↓
Level 7
CI/CD deployment
↓
Level 8
health checks + observabilityThe important lesson is that PM2 is a process manager, not a complete production architecture.
Use it to solve process lifecycle problems. Let Nginx handle reverse proxying, let CI/CD handle deployment, let your database handle persistence, and let proper monitoring handle observability.
When each layer has a clear responsibility, your Node.js server becomes significantly easier to operate, troubleshoot, and scale.