Install and Set Up PM2 for Node.js
Install PM2 with npm install -g pm2@latest, then start your app with pm2 start app.cjs --name my-app. PM2 supervises the process and can restart it after a crash. To restore your process list after a server reboot, configure pm2 startup and run pm2 save.
This guide uses a Linux shell and a supported Node.js LTS release. Run your application and PM2 commands as the same non-root deployment user. The demo needs only Node.js; it doesn't require Express or a database.
Reviewed September 25, 2026. Commands follow the official PM2 quick start.
What is PM2?
PM2 is a process manager with commands for starting, stopping, restarting, and inspecting applications. For compatible Node.js servers, its cluster mode can run multiple workers and share incoming connections across them.
PM2 is not a reverse proxy, a database backup system, or proof that an application is healthy. A process can be “online” while its checkout endpoint fails. Use an external request check for an important user journey.
How do you install PM2?
node --version
npm --version
npm install -g pm2@latest
pm2 --version If Node or npm is missing, install a supported LTS release using your server's supported installation method. If the global npm directory is not writable, fix the deployment user's Node/npm installation rather than running the application as root.
Create a runnable Node.js application
Create a new demo directory and save the following as app.cjs. The .cjs extension makes the example explicitly CommonJS.
// app.cjs: dependency-free example for a supported Node.js LTS release
const http = require('node:http');
const port = Number(process.env.PORT || 3000);
const server = http.createServer((request, response) => {
response.writeHead(200, { 'Content-Type': 'application/json' });
response.end(JSON.stringify({ status: 'ok', pid: process.pid }));
});
server.listen(port, '127.0.0.1', () => {
console.log('Listening on port ' + server.address().port);
// Send this only after any required dependencies are ready too.
if (process.send) process.send('ready');
});
let stopping = false;
function shutdown() {
if (stopping) return;
stopping = true;
// Stop accepting new connections; let active requests finish.
server.close(() => process.exit(0));
// Exit before PM2's 10-second kill_timeout if a connection hangs.
setTimeout(() => process.exit(1), 8000).unref();
}
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown); The example listens on the server's loopback address. Run the following request on that server. For public traffic, put an HTTPS reverse proxy in front of it; PM2 doesn't provide that proxy.
pm2 start app.cjs --name my-app
pm2 status
curl --fail http://127.0.0.1:3000/
pm2 logs my-app --lines 20 --nostream Expect a JSON response containing "status":"ok" and the process ID. Use your real application entry point instead of app.cjs when deploying an existing project.
Which PM2 commands should you know?
| Command | What it does |
|---|---|
pm2 status | Lists processes managed by this user's PM2 daemon. |
pm2 describe my-app | Shows process details and paths to its logs. |
pm2 logs my-app | Streams logs. Ctrl+C exits the viewer, not the application. |
pm2 restart my-app | Restarts the application. Expect interruption. |
pm2 reload my-app | Reloads compatible cluster workers in sequence; may fall back to a restart. |
pm2 stop my-app | Stops the process while keeping its entry in the process list. |
pm2 delete my-app | Stops and removes the entry from the process list. |
pm2 save | Saves the current list for later restoration. |
Target the application name instead of all when other applications share the PM2 daemon.
How do you keep a PM2 app running after reboot?
Start the intended applications, run pm2 startup, and execute the privileged setup command it prints. That command records the deployment user and environment. Then run pm2 save as the deployment user.
pm2 startup
# Run the setup command PM2 prints, then:
pm2 save Verify the generated service during a planned restart window. Running pm2 kill stops applications managed by that daemon; it is not a harmless reboot test. After changing your Node.js installation path, regenerate the startup integration following PM2's startup documentation.
Use an ecosystem file for repeatable settings
Save this as ecosystem.config.js next to the demo application, in a directory without a package-level "type": "module" declaration. For an ES-module project, keep the PM2 configuration in a CommonJS-scoped directory and set an appropriate application path or cwd.
// ecosystem.config.js (CommonJS configuration)
module.exports = {
apps: [{
name: 'my-app',
script: './app.cjs',
instances: 2,
exec_mode: 'cluster',
watch: false,
wait_ready: true,
listen_timeout: 10000,
kill_timeout: 10000,
max_memory_restart: '500M',
time: true,
env_production: {
NODE_ENV: 'production',
PORT: 3000
}
}]
}; The memory threshold applies per worker. Leave enough memory for all workers, the operating system, and any local dependencies.
# For the disposable demo, replace its earlier fork-mode entry:
pm2 delete my-app
pm2 start ecosystem.config.js --env production
pm2 save Deleting the demo briefly stops it. Plan a separate migration procedure before changing a live application's process mode. The --env production flag selects the ecosystem file's env_production values; it doesn't invent production settings for an arbitrary script.
Does PM2 reload guarantee zero downtime?
No. In cluster mode, PM2 can replace workers sequentially while others serve traffic. The application must become ready, handle shutdown, and keep shared state outside individual workers. If a reload times out, PM2 may fall back to a restart.
The example sends process.send('ready') after listening because its configuration uses wait_ready: true. In a real application, connect required dependencies before reporting readiness. The shutdown handler drains HTTP requests before PM2's configured kill timeout.
Cluster workers share the listening port. Don't increment the port per worker for this pattern. Store sessions and other shared state externally; in-memory state belongs to one worker.
pm2 reload ecosystem.config.js --env production
pm2 status
curl --fail http://127.0.0.1:3000/ Read the cluster-mode guide and graceful start/shutdown documentation for the requirements. Budget for detection and recovery with the uptime calculator; the 99.9% vs 99.99% guide shows how a failed rollback can consume that budget.
PM2 restart vs reload: what happened to requests in our test?
Single-process restarts failed 101 of 735 requests across five trials. With two cluster workers, both restart and reload recorded zero failed requests in this experiment. A two-worker control with no lifecycle command also recorded zero failures.
This is a local, synthetic experiment run on September 25, 2026, using Node.js v24.14.0, PM2 7.0.4, and an Apple M2 Max running Darwin 25.6.0 (arm64). It measures one small HTTP application's behavior on loopback, not CloudPloy production availability or Linux server performance.
| Scenario | Workers | Requests attempted | Failed requests |
|---|---|---|---|
| cluster-control | 2 | 843 | 0 |
| fork-restart | 1 | 735 | 101 |
| cluster-restart | 2 | 863 | 0 |
| cluster-reload | 2 | 848 | 0 |
How the experiment works
The application waits 250 milliseconds before listening, simulating initialization, and delays each response by 50 milliseconds. It sends PM2's readiness message after listening and drains active HTTP requests on SIGINT or SIGTERM. Both cluster cases use two workers; the fork case uses one.
The runner attempts a request every 20 milliseconds using a fresh TCP connection, with a one-second socket timeout. After one second of traffic it issues the lifecycle command, then continues traffic for 1.5 seconds after that command finishes. For the control it waits one second instead of issuing a command. Timers are best-effort, so actual request timestamps are included in the data.
Scenario order reverses on alternating repetitions. Each trial starts a new application, checks the worker count and online status, and verifies that a restart or reload replaced every worker PID. Requests must return HTTP 200 with the expected JSON to count as successful. All 101 failures were connection refusals, from requests started during the single-process restart command.
What these results do and don't tell you
The result supports testing process mode alongside the command. In this setup, a second worker was enough to avoid observed failures for both cluster operations. The data doesn't establish that reload beats restart for every application, or that either command guarantees uninterrupted service.
The experiment has no reverse proxy, TLS, database, external network, WebSockets, or shared session state. It doesn't stress CPU capacity or test a worker that never becomes ready. Different command durations produce different request totals. Don't treat the fraction of failed requests as time-based uptime, or multiply it into a monthly SLA estimate.
For your service, repeat the experiment in staging with realistic startup work, longer requests, and the actual proxy path. Test dependency failures separately. A successful health-check response isn't proof that a checkout or background job works.
Download the data and reproduce the experiment
The raw JSON results include every request outcome, relative timestamp, worker PID, and the application fixture. The Node.js experiment runner creates a temporary PM2 home, binds the demo to loopback, and removes its own daemon after finishing.
# Use Node.js 24.14.0 to match the recorded experiment.
mkdir pm2-reload-study
cd pm2-reload-study
npm install --save-exact pm2@7.0.4
curl --fail --output pm2-reload-study.mjs \
https://cloudploy.com/experiments/pm2-reload-study.mjs
node pm2-reload-study.mjs ./node_modules/pm2/bin/pm2 results.json The runner refuses to overwrite an existing result file. Expect the counts and timings to vary with the machine and scheduler. Compare the raw request outcomes rather than expecting identical totals.
How do you inspect logs and memory?
pm2 monit
pm2 logs my-app --lines 100 --nostream
pm2 describe my-app PM2's terminal monitoring is local. Hosted PM2 monitoring products have separate plans; they aren't required for these commands. Watch the application's own error rate and external availability alongside CPU and memory.
For log rotation, PM2 provides an optional module:
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true Choose retention to match your disk budget and incident-review needs. Don't clear logs you still need to investigate a failure.
How do you deploy an update?
- Test the release, including startup and shutdown behavior.
- Prepare a release directory with the locked dependencies and any build output. Use
npm ciwhen the project has an npm lockfile. - Keep database changes compatible with the previous application version while old and new workers overlap.
- Point the application configuration at the prepared release, then reload the named application.
- Check real requests and logs. Keep the previous release available if you need to roll back.
PM2 reload is one step in deployment; it doesn't make file updates, database migrations, or dependency installation atomic. For PM2's optional SSH deployment system, use the deployment documentation.
If you use containers, choose a deliberate process-supervision model rather than assuming the host startup procedure applies inside a container. See PM2's Docker integration and our Docker deployment guide.
Troubleshooting PM2 setup
Why is pm2 not found after installation?
Check npm prefix -g and the current user's PATH. A Node version manager may install PM2 for one Node version but not another. Use the same user and Node installation for setup, deployment, and startup.
Why does the application keep restarting?
Read its logs with pm2 logs my-app --lines 100 --nostream. Check missing environment variables, a port already in use, unavailable dependencies, and memory restarts. Test the entry point directly in a separate development environment to isolate application errors.
Why did my environment variable change not apply?
When changing shell-provided variables, restart with --update-env. For example, NODE_ENV=production pm2 restart my-app --update-env. For ecosystem settings, select the intended env_* block with --env.
Why is the process list empty after reboot?
Confirm you configured the startup service and saved the process list as the same deployment user. Inspect the service's logs and Node path. Running PM2 under another user creates a different daemon and process list.
Can PM2 install replace npm install?
No. npm install -g pm2 installs PM2 itself; npm ci or npm install installs application dependencies. pm2 install installs PM2 modules such as the log-rotation module.
Compare this with CloudPloy's deployment workflow
See how CloudPloy works before deciding which parts of deployment you want to manage yourself. This tutorial describes a self-managed PM2 setup, not a claim that PM2 is preinstalled on every CloudPloy deployment.