How to Choose Between Fork and Cluster Mode in PM2
PM2 fork vs cluster mode: key differences, when to use each, plus graceful reload, zero-downtime restart, and production best practices for Node.js applications.

PM2 fork vs cluster mode is one of the first decisions you face when running Node.js applications in production. PM2 is the most widely used Node.js process manager (43,000+ GitHub stars, 600M+ npm downloads), and it gives you two fundamentally different ways to run your apps. Pick the wrong mode and you either waste resources or break features like graceful reload.
PM2 v7.0.3 (June 2026) is the current release. It requires Node.js >= 18, adds Bun runtime support, ships OpenTelemetry tracing, and includes native source maps. If you’re running an older PM2, the upgrade path matters. More on that below.
This guide covers what each mode does, the real differences (including some corrections to what you’ll find elsewhere), when to pick each one, and the production best practices that keep your apps alive.
For a broader PM2 tutorial, see managing applications with PM2.
Quick Decision: Fork or Cluster?
| Use Fork Mode When | Use Cluster Mode When |
|---|---|
| Background jobs, cron, workers, scripts | HTTP, TCP, or WebSocket servers |
| Non-Node.js apps (Python, Ruby, binaries) | Node.js-only apps |
| Different Node versions per app | All apps on same Node version |
| Memory-constrained VPS (1-2 GB) | Multi-core VPS (2+ cores, 2+ GB) |
| Development or local testing | Production with uptime requirements |
What is fork mode in PM2?
Fork mode is PM2’s default. It spawns a single process per application using Node’s child_process.fork(). One process, one app.
Fork mode is the right choice for:
- Scripts, workers, and cron jobs: anything that doesn’t listen on a port.
- Non-Node.js runtimes: fork mode can run Python, Ruby, or arbitrary binaries.
- Apps needing different Node versions: use
node_argsto point at a specific binary. - Development and testing: simpler, lower memory footprint.
Basic fork mode usage
# Run app.js in fork mode (default)
pm2 start app.js
Or with an ecosystem file:
// ecosystem.config.js
module.exports = {
apps: [{
script: "app.js",
exec_mode: "fork"
}]
};
pm2 start ecosystem.config.js
A common misconception: features like cron restarts, source map support, and custom log formats are not fork-mode exclusives. These are general PM2 attributes that work in both fork and cluster mode. PM2 v7 replaced the source-map-support npm dependency with native process.setSourceMapsEnabled(), so source maps work everywhere without extra packages.
For managing PM2 environment variables across modes, see the linked guide.
What is cluster mode in PM2?
Cluster mode spawns multiple worker processes using the Node.js cluster module. Each worker is a separate OS process running your app, and they share the same server port via IPC handle sharing. That’s a Node.js feature, not something PM2 invented. The cluster module handles round-robin load balancing across workers.
Starting cluster mode
# Run with as many workers as CPU cores
pm2 start app.js -i max
# Run with exactly 4 workers
pm2 start app.js -i 4
With an ecosystem file:
// ecosystem.config.js
module.exports = {
apps: [{
script: "app.js",
instances: "max",
exec_mode: "cluster"
}]
};
Since PM2 v7, setting instances on a Node.js app automatically enables cluster mode. Explicit exec_mode: "cluster" is optional but recommended for clarity. Your future self will thank you.
The instances values:
"max"or0: one worker per CPU core-1: all cores minus one (leaves headroom for the OS)- Any number: that many workers
Cluster mode advantages
- Multi-core utilization: your app handles parallel requests across all cores.
- Fault tolerance: if one worker crashes, PM2 restarts it while the others keep serving.
- Graceful reload / zero-downtime restart:
pm2 reloadreplaces workers one-by-one (covered in detail below).
Cluster mode limitations
- Higher memory usage: each worker is a separate process. 4 workers at 200 MB each = 800 MB minimum.
- Requires the same Node.js version for all workers (cluster module limitation). Bun is supported in PM2 v7+ but has edge cases. Test thoroughly.
- Requires stateless applications. This is the gotcha most tutorials skip.
Stateless Apps Required
If your app stores session data in memory, uses in-process caches, or relies on shared mutable state, cluster mode will break it. Worker A gets a request, stores the session. Worker B gets the next request and has no idea about that session.
Use Redis, Memcached, or your database for shared state. For WebSocket apps, see the sticky session section below.
Memory Math
4 instances x 200 MB = 800 MB RAM. On a 2 GB Hetzner Cloud VPS, you can safely run about 8 instances total across all clustered apps (leaving room for the OS and PM2 itself). Plan your instance count before you hit OOM.
PM2 fork vs cluster mode: key differences
| Feature | Fork Mode | Cluster Mode |
|---|---|---|
| Process model | Single process per app | Multiple workers per app |
| Load balancing | None (or external) | Automatic (Node.js cluster module) |
Graceful reload (pm2 reload) |
Falls back to restart (brief downtime) | True rolling restart (zero downtime) |
| Port sharing | Separate ports required | Same port via IPC handle sharing |
| Non-Node.js runtimes | Supported (Python, Ruby, binaries) | Node.js only (Bun supported in v7+) |
| Multiple Node versions | Supported via node_args |
Single version (cluster module limitation) |
| Cron restarts | Yes | Yes |
| Source map support | Yes (native in v7+) | Yes (native in v7+) |
| Custom log formats | Yes | Yes |
| Memory per instance | Lower (single process) | Higher (N x single instance) |
| Best for | Workers, scripts, cron, mixed runtimes | HTTP/TCP servers, production web apps |
If you’re sizing a VPS for cluster mode, benchmark your VPS first to understand the actual headroom.
Cluster mode is usually the better default for production web apps that serve HTTP traffic. Fork mode wins for non-networked workloads, background jobs, and environments where you need multiple runtimes or Node versions.
Graceful reload and zero-downtime restart in PM2
Graceful reload and zero-downtime restart are what separate a dev setup from a production deployment. Most PM2 guides skip this part.
pm2 reload vs pm2 restart
These are different commands with different behavior:
pm2 reload my-app # rolling restart (cluster) / restart (fork)
pm2 restart my-app # immediate kill + start
pm2 restartkills all processes, then starts them. Brief downtime in fork mode. Full downtime in cluster mode (all workers killed at once).pm2 reloadin cluster mode is a true rolling restart. Workers are replaced one-by-one; at least one worker is always serving traffic. In fork mode,pm2 reloadfalls back topm2 restart(downtime), because there’s only one process. There’s nothing to roll.
For zero-downtime deploys in cluster mode, always use pm2 reload.
Application-side graceful shutdown (SIGINT handling)
Setting wait_ready: true in your ecosystem config tells PM2 to wait for a “ready” signal from your app before considering it started. But that signal has to come from your code. When PM2 sends a SIGINT to reload a worker, your app needs to handle it: close the server, flush connections, exit cleanly.
Without this code, PM2 will wait for listen_timeout, then force-kill the process. That’s not graceful. It’s a timeout with data loss risk.
const express = require('express');
const app = express();
// ... your routes and middleware ...
const PORT = process.env.PORT || 3000;
const server = app.listen(PORT, () => {
console.log(`Worker ${process.pid} listening on ${PORT}`);
// Tell PM2 the worker is ready (required with wait_ready: true)
if (process.send) {
process.send('ready');
}
});
// Handle PM2's graceful reload signal
process.on('SIGINT', () => {
console.log('SIGINT received, closing server...');
server.close(() => {
// Close DB connections, flush logs, release resources
console.log('Server closed, exiting');
process.exit(0);
});
// Safety: force exit if not closed within kill_timeout
setTimeout(() => {
console.error('Forced shutdown after timeout');
process.exit(1);
}, 5000);
});
The ecosystem config to pair with this:
module.exports = {
apps: [{
name: "my-api",
script: "./app.js",
instances: "max",
exec_mode: "cluster",
wait_ready: true, // wait for process.send('ready')
listen_timeout: 5000, // ms to wait for 'ready' signal
kill_timeout: 5000, // ms to wait for clean exit after SIGINT
}]
};
Without This Code, Graceful Reload Won't Work
If your app doesn’t handle SIGINT and call process.send('ready'), PM2 will wait for listen_timeout then force-kill. Your users get dropped connections. Add the shutdown handler above before enabling wait_ready.
PM2 cluster mode best practices for production
Production ecosystem configuration
Generate a starter file with pm2 init simple (or pm2 ecosystem, both still work). Then build it out:
// ecosystem.config.js
module.exports = {
apps: [{
name: "my-api",
script: "./app.js",
instances: "max",
exec_mode: "cluster", // explicit, recommended for clarity
max_memory_restart: "300M", // auto-restart if worker exceeds 300 MB
wait_ready: true, // wait for process.send('ready')
listen_timeout: 5000, // ms to wait for ready signal
kill_timeout: 5000, // ms to wait for clean shutdown
max_restarts: 10, // restart limit within min_uptime window
min_uptime: "10s", // if process runs less than 10s, it's a crash
log_date_format: "YYYY-MM-DD HH:mm:ss Z",
merge_logs: true, // merge worker logs into one file
env: {
NODE_ENV: "development",
},
env_production: {
NODE_ENV: "production",
},
}]
};
Start in production:
pm2 start ecosystem.config.js --env production
Instance scaling strategy
Not every app needs instances: "max". Scale based on your workload:
- CPU-bound apps:
instances: "max"(one per core). - I/O-bound apps: can try 1.5x-2x core count; watch memory.
- Memory-constrained VPS: calculate
available_RAM / per_instance_RAM= safe max.
Dynamic scaling without restart:
pm2 scale my-app +2 # add 2 more workers
pm2 scale my-app 4 # scale to exactly 4 workers
Environment variable management
A common gotcha: when you restart via CLI with new env vars, they won’t update unless you pass --update-env:
NODE_ENV=production pm2 restart my-app --update-env
Ecosystem files always update env vars on restart or reload. Use them when possible. See PM2 environment variables for the full details.
Security: filter_env
If your VPS has other services or users, prevent sensitive env vars from leaking into PM2 child processes with filter_env:
// In ecosystem.config.js
module.exports = {
apps: [{
name: "my-api",
script: "./app.js",
// Only pass these env var prefixes to the worker
filter_env: ["NODE_", "APP_", "DB_"],
// Or strip ALL global env vars:
// filter_env: true,
// ...
}]
};
For broader VPS hardening, see securing your VPS server.
Log rotation
By default, PM2 logs grow until your disk is full. Install the log rotation module:
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 30
pm2 set pm2-logrotate:compress true
This keeps logs to 10 MB per file, retains 30 rotated files, and compresses old ones. Essential for any production VPS.
For broader monitoring, see monitoring CPU usage on Linux.
Production checklist
- Set
max_memory_restartto prevent memory leaks from crashing the box - Set
wait_ready: trueand implement SIGINT handling in your app - Set
max_restartsandmin_uptimeto catch crash loops - Use
merge_logs: trueto keep log files manageable - Install
pm2-logrotateto prevent disk fill - Use ecosystem files (not CLI flags) for reproducible deploys
- Run
pm2 saveandpm2 startupto survive reboots - Keep Node.js and dependencies updated. See keeping your Node.js dependencies updated
PM2 in containers: pm2-runtime for Docker
If you’re running PM2 inside a Docker container, use pm2-runtime instead of pm2 start.
Regular pm2 start spawns a daemon process, wrong for containers. The container starts, PM2 forks, the main process exits, and Docker thinks the container is done. pm2-runtime stays in the foreground and streams logs to stdout/stderr, which is what Docker expects.
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
CMD ["pm2-runtime", "start", "ecosystem.config.js"]
Or without an ecosystem file:
CMD ["pm2-runtime", "npm", "--", "start"]
One decision: in Docker, cluster mode scales via instances in the ecosystem file or via Docker/Kubernetes replicas. Pick one, don’t do both, or you’ll get N x M workers and wonder why your container is OOM-killed.
For more on container commands, see Docker commands for production. Fork mode in PM2 can also run Python apps in Docker or other non-Node.js runtimes.
WebSocket and sticky session considerations
Cluster mode uses round-robin load balancing by default. For HTTP requests, that’s fine. Each request is independent. For WebSocket connections, it’s a problem. The initial handshake lands on Worker A, but subsequent frames might get routed to Worker B, which has no idea about that connection.
Three solutions:
- Sticky sessions at the reverse proxy: Nginx:
ip_hash. HAProxy:cookie-based stickiness. This pins a client to one worker. - Redis adapter for socket.io:
@socket.io/redis-adapterlets workers share connection state. Works well but adds Redis as a dependency. - Separate WebSocket server: run your WebSocket handler in fork mode on a different port, alongside your clustered HTTP server. Simple, no shared state needed.
Pick the option that matches your architecture. If you’re running a single VPS with Nginx in front, sticky sessions are the easiest path.
PM2 v7: what’s new (2025-2026)
PM2 v7.0.0 (May 2026) was a major release. Key changes relevant to the fork vs cluster decision:
- Node.js >= 18 required. PM2 dropped Node 16 support. If you’re still on 16, you need to upgrade first.
- Bun runtime support. Fork mode since v6.0.5, cluster mode since v7.0.0. Caveat: Bun + cluster has edge cases (see GitHub issues). Test thoroughly before deploying.
- Native source maps. Replaced the
source-map-supportnpm dependency withprocess.setSourceMapsEnabled(). Works in both modes, no extra packages needed. - OpenTelemetry built-in.
@opentelemetry/api,sdk-node, andauto-instrumentations-nodeare direct dependencies. Caveat: in cluster mode, workers may report duplicate spans (known OpenTelemetry issue). pm2 lsshows host metrics by default (v7.0.2). CPU, memory, disk at a glance.max_memory_restartshown inpm2 describe(v7.0.1). Easier to audit memory limits.- Security fixes. CVE-2025-5891 (ReDoS), CVE-2026-27699 (proxy-agent), three command injection fixes, prototype pollution fix. If you’re on v5 or v6, these are good reasons to upgrade.
Upgrading from PM2 v5/v6?
PM2 v7 requires Node.js >= 18. Check your Node version first (node -v). The security fixes alone justify the upgrade. Run npm install -g pm2@latest then pm2 update to reload your processes with the new PM2 binary. If you’re using Bun, see updating packages with Bun for the upgrade workflow.
Troubleshooting PM2 fork and cluster issues
EADDRINUSE: port already in use
In fork mode, two apps can’t share a port. If you see EADDRINUSE, another process (or another PM2 app) is already listening on that port.
# Check what's using the port
lsof -i :3000
# Check PM2 apps for conflicts
pm2 listIn cluster mode, workers share the port automatically. You won’t hit this within one app. But two different cluster-mode apps still can’t share a port.
Workers crashing in a loop
If you see pm2 restart count exceeded in the logs, your workers are crashing faster than min_uptime allows. PM2 stops restarting after max_restarts attempts.
# Check logs for the root cause
pm2 logs my-app --lines 100
# Describe the app for restart counts
pm2 describe my-appFix the underlying bug first. If it’s a transient issue (e.g., database not ready), increase max_restarts or lower min_uptime temporarily.
Graceful reload not working
If pm2 reload kills connections instead of draining them, your app probably isn’t handling SIGINT or isn’t sending process.send('ready'). See the graceful shutdown code in the best practices section above.
Verify:
# Check if wait_ready is enabled
pm2 describe my-app | grep wait_ready
# Check logs for "SIGINT received" message during reload
pm2 logs my-appIf you don’t see “SIGINT received” in the logs during reload, your handler isn’t registered.
Memory leaks / OOM
Use max_memory_restart to auto-restart workers that exceed a memory threshold. This catches leaks before they crash the whole VPS.
# Watch memory in real time
pm2 monitIf a specific worker keeps hitting the limit, it’s leaking. Fix the code. max_memory_restart is a band-aid, not a cure.
WebSocket connections dropping in cluster mode
PM2’s cluster mode uses round-robin by default. WebSocket connections that start on one worker may get routed to another on reconnect. See the sticky session section above for solutions: Nginx ip_hash, Redis adapter, or a separate fork-mode WebSocket server.
Environment variables not updating
When restarting via CLI with new env vars, you must pass --update-env:
NODE_ENV=production pm2 restart my-app --update-envEcosystem files always update env vars on restart/reload. This is the most common PM2 gotcha in CI/CD pipelines. Your deploy script sets a new env var, restarts, and nothing changes.
Quick verification commands
After deploying, confirm everything is running as expected:
pm2 list # check mode column (fork/cluster), instance count, status
pm2 describe 0 # detailed info: exec_mode, restart count, memory limits
pm2 monit # live CPU/memory per instance
pm2 logs # check for startup errors
Conclusion
The old “fork for features, cluster for scaling” split is outdated. Cron restarts, source maps, and custom log formats work in both modes. Choose based on your app’s architecture:
- Cluster mode for production HTTP/TCP servers that need uptime and multi-core utilization. The zero-downtime reload alone makes it worth the extra memory.
- Fork mode for background workers, scripts, cron jobs, non-Node.js runtimes, and environments where you need different Node versions per app.
If you’re running on a budget VPS like Hetzner Cloud or Hostinger VPS, cluster mode with 2 to 4 instances works fine on a 2 to 4 GB plan. Just do the memory math first.
For the full PM2 tutorial covering process management, monitoring, and deployment, see managing applications with PM2. If you’re looking at broader server management options, check out self-hosted server panels for alternatives to manual PM2 management.
PM2 Management Tutorial

