Configure PM2, Startup Scripts, and Log Rotation on VPS

by Fahim

Running Node.js directly with node server.js is fine locally, but in production, the process dies the moment your SSH terminal closes or an unhandled rejection slips through. I’ve had servers sit dead for hours overnight because nobody was monitoring the process. Here is how I set up PM2 on fresh VPS instances, configure zero-downtime clustering, wire up systemd so services survive reboots, and keep runaway log files from filling up the disk.

Linux terminal showing PM2 process status and cluster management on a VPS
Linux terminal showing PM2 process status and cluster management on a VPS

Installing PM2 Globally and Basic Process Execution

PM2 runs as a background daemon on your machine. First, install it globally using npm or your package manager of choice.

Run this on your VPS as a user with sudo privileges:

sudo npm install -g pm2@5.3.1

Verify the binary is in your PATH and check the installed version:

pm2 --version

If you followed my guide to deploy Node.js on a Hostinger VPS with Nginx, you already have an entrypoint like app.js or dist/index.js ready. You can fire up a standalone process right away with a custom name:

pm2 start server.js --name "api-service"

Ad-hoc CLI flags work for quick smoke tests, but for real deployments with environment variables and multiple workers, you want a declarative configuration file in source control.

Structuring an Ecosystem File (ecosystem.config.js)

An ecosystem file keeps your process definitions inside your Git repository. It controls ports, environment variables, cluster instances, and log paths in one clean place.

Generate a starter template in your project’s root directory:

pm2 init simple

Open ecosystem.config.js and replace it with this production-ready setup:

module.exports = { apps: [ { name: 'web-api', script: './dist/index.js', cwd: '/var/www/my-app', instances: 'max', exec_mode: 'cluster', autorestart: true, watch: false, max_memory_restart: '500M', env_production: { NODE_ENV: 'production', PORT: 3000 }, env_staging: { NODE_ENV: 'staging', PORT: 3001 }, error_file: '/var/log/pm2/web-api-error.log', out_file: '/var/log/pm2/web-api-out.log', merge_logs: true, time: true } ]
};

Make sure the target log directory actually exists and your deploy user has write permissions to it:

sudo mkdir -p /var/log/pm2
sudo chown -R $USER:$USER /var/log/pm2

Start your application using the production profile:

pm2 start ecosystem.config.js --env production

Configuring Cluster Mode and Zero-Downtime Reloads

Node.js runs single-threaded by default. By specifying exec_mode: 'cluster' and instances: 'max' in your ecosystem file, PM2 uses the native Node.js cluster module to spin up one worker process per CPU core.

The real benefit is seamless deployments. If you run a single instance, restarting it drops in-flight connections. With cluster mode, PM2 reloads workers one by one.

To reload all worker threads sequentially without dropping a single active request:

pm2 reload ecosystem.config.js --env production

If you run modern full-stack setups, the same cluster logic applies, as detailed in my guide on how to deploy Next.js on a VPS.

Setting Up Systemd Startup Scripts for Reboot Persistence

PM2 runs in user space. If your hosting provider reboots the VPS for maintenance or a kernel patch, your processes won’t come back up on their own. You need to bind PM2 to your system’s init manager using systemd service definitions.

Generate the startup configuration command:

pm2 startup systemd

PM2 will detect your OS and print a specific sudo env PATH=... command. Copy and execute that exact output line in your terminal:

sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2-startup install systemd -u deployer --hp /home/deployer

Once the systemd service is active, save your currently running PM2 process list to disk:

pm2 save

This writes your active process list to ~/.pm2/dump.pm2. Whenever the server boots, systemd reads that dump and revives every worker automatically.

Automating Log Rotation with pm2-logrotate

Node.js applications running under PM2 write stdout and stderr directly to disk. Left unchecked on a busy API, these files quickly balloon into gigabytes and eventually eat all available disk space and inodes.

PM2 has a built-in module called pm2-logrotate that handles automatic file rotation, gzip compression, and retention cleanups.

Install the logrotate module directly through the PM2 CLI:

pm2 install pm2-logrotate

Once installed, tune the rotation thresholds for production:

# Rotate logs when they hit 10 Megabytes
pm2 set pm2-logrotate:max_size 10M # Retain the last 14 rotated log files
pm2 set pm2-logrotate:retain 14 # Compress rotated logs with gzip
pm2 set pm2-logrotate:compress true # Rotate daily at midnight using standard cron syntax
pm2 set pm2-logrotate:rotateInterval '0 0 * * *' # Set the rotated date format
pm2 set pm2-logrotate:dateFormat YYYY-MM-DD_HH-mm-ss

Check that your rotation rules applied cleanly:

pm2 conf pm2-logrotate

Managing Memory Limits and Auto-Restarts

Memory leaks happen in Node.js apps. If an unchecked leak runs rampant, the Linux kernel Out-Of-Memory (OOM) killer will step in and forcefully kill your process, leaving your reverse proxy throwing 502 Bad Gateway errors.

Set a ceiling on memory usage so PM2 can recycle bloated workers gracefully before the kernel takes drastic action:

pm2 start ecosystem.config.js --max-memory-restart 500M

If an individual worker creeps past 500 MB of RSS memory, PM2 reloads that worker in the background while the rest of the cluster continues serving traffic.

If your upstream proxy drops connections during high-churn restarts, see my troubleshooting guide on fixing 502 bad gateway errors to dial in reverse proxy timeouts and upstream buffers.

Monitoring Processes and Inspecting Live Logs

PM2 includes an interactive terminal UI for monitoring memory, CPU load, event loop latency, and request metrics across all workers.

Open the real-time terminal dashboard with:

pm2 monit

To tail real-time log output across all apps or scope it to a single process:

# Stream logs for all services
pm2 logs # Stream only lines from web-api and display the last 50 entries
pm2 logs web-api --lines 50

To flush and wipe all current log files without restarting your running workers:

pm2 flush

Common Gotchas and How I Fixed Them

A few tricky issues always seem to trip people up when configuring PM2 across multi-user VPS environments:

  • Missing Node binary on system reboot: If you installed Node via NVM under a non-root user, the pm2 startup command generated by root will look for /usr/bin/node and fail silently on boot. Either symlink your active NVM node binary to /usr/local/bin/node or install Node system-wide via the official NodeSource repository.
  • Environment variable caching: Running pm2 restart app-name does NOT re-read updated variables from your .env file or ecosystem.config.js. Always pass pm2 restart app-name --update-env to force PM2 to reload the new environment.
  • Port collisions from orphaned processes: If your app crashes on boot claiming port 3000 is occupied, you probably have a rogue process running outside PM2. Track it down and kill it with sudo lsof -i :3000.

Frequently Asked Questions

Why should I use PM2 instead of raw systemd service files?

Systemd is rock-solid for system services, but PM2 gives you built-in cluster load balancing, zero-downtime rolling reloads, live log streaming, and instant process metric monitoring without having to write custom bash reload scripts or complex systemd service templates.

Does PM2 work with TypeScript without pre-compiling?

Yes, PM2 can run TypeScript directly via ts-node. But doing that in production hurts boot times and hogs extra RAM. You should compile your TypeScript into plain JavaScript using tsc or esbuild and have PM2 run the build output.

How do I update PM2 without stopping my live applications?

To upgrade the PM2 daemon in place without interrupting live worker processes, run:

npm install -g pm2@latest
pm2 update

According to the official PM2 documentation, pm2 update dumps your current process state, stops the old master daemon, boots the new one, and restores your running workers .

Next Steps for Your VPS Stack

With PM2 clustered, monitored, and set to auto-start on system boot, your Node processes can handle traffic spikes and hardware reboots without manual intervention. If you’re building out a complete production server with databases and frontends, check out my guide on how to deploy a full-stack web app on a VPS to tie the whole architecture together.

all_in_one_marketing_tool