⚙️
Hosting

Managing Node.js Apps with PM2: Start, Logs, Cluster Mode, Boot

05.11.2026
← All articles

Starting a Node.js app with node server.js is fine for testing, but in production it breaks quickly. One unhandled exception kills the process, a dropped SSH session takes the app down, and after a reboot someone has to start it by hand. PM2 is a process manager that handles all of this: it keeps the app in the background, restarts it on crash and writes output to log files.

Installing PM2 and the first start

PM2 is installed globally through npm, so Node.js must already be on the server. Give the app a clear name so you can manage it by name later.

npm install -g pm2
cd /var/www/misol-app
pm2 start server.js --name misol-app
pm2 list

pm2 list shows status, restart count, CPU and memory for each process. A restart counter that keeps climbing means the app crashes on startup and it is time to read the logs.

The ecosystem file

Typing a dozen flags every time is error-prone. An ecosystem file lives in the project folder, is committed to Git and gives every team member identical settings.

// ecosystem.config.js
module.exports = {
  apps: [{
    name: "misol-app",
    script: "server.js",
    cwd: "/var/www/misol-app",
    instances: 1,
    max_memory_restart: "300M",
    env: {
      NODE_ENV: "production",
      PORT: 3000
    }
  }]
};
pm2 start ecosystem.config.js
pm2 restart ecosystem.config.js --update-env

max_memory_restart restarts the app when it exceeds the limit. That protects the server from a memory leak but does not fix the leak itself. --update-env makes PM2 reload changed environment variables.

Logs

PM2 keeps separate output and error logs per app in ~/.pm2/logs. Follow them live or print the last few hundred lines; install the logrotate module so they never fill the disk.

pm2 logs misol-app --lines 200
pm2 flush misol-app
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7

Cluster mode

A single Node.js process mostly uses one CPU core. Cluster mode starts several copies and balances requests between them. It only suits stateless apps: keep sessions in Redis or a database, or users will land on another copy and lose their login.

pm2 start server.js --name misol-app -i max
pm2 reload misol-app

reload replaces copies one by one, so a deploy causes no downtime, while restart stops all copies at once.

Starting at boot

PM2 itself must start with the system. Generate the startup script, run the sudo line it prints, then save the process list. Run pm2 save again whenever you add or remove an app.

pm2 startup
# copy and run the printed sudo env PATH=... command
pm2 save

Example: a private school portal

A private school in Tashkent runs two Node.js services on example.uz: a grades API for parents and a small worker that sends Telegram notifications. Both are defined in one ecosystem file, the API in cluster mode with two copies and the worker with one. On report-card day the API copies share the load, and the memory limit protects the server even if a leak appears. Deploying is just git pull followed by pm2 reload ecosystem.config.js.

Putting Nginx in front

Rather than exposing the app directly, let Nginx accept ports 80 and 443, terminate SSL and forward traffic to port 3000.

location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

This needs root access, so a VPS from the sayt.uz cabinet is the natural place for Node.js, PM2 and Nginx together.

Related articles

⚡ MySQL Indexes: Making a Slow Site Several Times Faster 🧩 Deploying Laravel on Shared Hosting: Solving the public Folder Problem 🛠️ 500 Internal Server Error: where to look for the cause 🗜️ Gzip and Brotli compression: shrink your site and speed up loading
🌐 Language
🇺🇿 O'zbek 🇺🇿 Ўзбек 🇷🇺 Русский 🇬🇧 English ✓