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.