Run Node.js in production on a VPS with PM2

You wrote a Node.js app, tested it locally, and it works. Then you deploy it on a VPS, and three hours later you wake up to find the process died and nobody noticed. That is the exact problem PM2 solves. PM2 is a production-grade process manager for Node.js that keeps your app alive, handles log rotation, runs in cluster mode across all CPU cores, and gives you a monitoring dashboard. This guide walks through deploying a Node.js application on a Linux VPS with PM2, from installation to zero-downtime deployment, using real commands that work on any modern Linux distribution.
Prerequisites
- A Linux VPS running Ubuntu 24.04, Debian 12, or AlmaLinux 9 with root or sudo access.
- Node.js and npm already installed (the current LTS is Node.js 22.x at the time of writing).
- A Node.js application (Express, Fastify, NestJS, or any framework) ready to deploy.
- Optional but recommended: a domain pointing to the VPS IP for reverse proxy setup.
生产环境运行 Node.js 必须使用进程管理器,PM2 是首选方案。
Running Node.js in production without a process manager is a risk you should not take. PM2 is the industry-standard tool for this.
Why you need PM2 for a production workload
Running node app.js in a terminal works for development. In production, that approach fails the moment you close the SSH session, when the app crashes, or when a memory leak eats through your RAM. PM2 solves all three. It runs your application as a daemon, automatically restarts it after a crash, and can fork your app into multiple processes that share incoming traffic across all available CPU cores. This is called cluster mode. A single-threaded Node.js process cannot use more than one core, but PM2 spawns a worker per core, and the built-in load balancer distributes requests round-robin. Without this, a 4-core VPS would leave 75% of its CPU idle under load.
Beyond process management, PM2 gives you a centralized log system. Every console.log and console.error from every worker goes into separate files, rotated daily so disk usage stays predictable. You get a built-in monitoring dashboard, restart limits to prevent infinite crash loops, and a startup script that resurrects all your apps when the VPS reboots. It is the first thing I install on any Node.js VPS, before the app itself.
Step 1, Installing PM2 globally
Install PM2 via npm so it is available system-wide. The command is the same on any Linux distribution.
sudo npm install -g pm2
Verify the installation and check the version.
pm2 --version
The output should print a version like 5.4.3 or newer. If the command is not found after installation, ensure npm's global bin directory is in your PATH. The default location is /usr/local/bin or /usr/bin, depending on how you installed Node.js. If needed, re-login or run export PATH=$PATH:/usr/local/bin to fix it.
Step 2, Deploying your Node.js application to the VPS
Get your application code onto the server. Assume your app lives in a Git repository and you want to deploy it under /opt/myapp. Create the directory, clone the repository, and install dependencies.
sudo mkdir -p /opt/myapp
sudo chown -R $USER:$USER /opt/myapp
git clone https://github.com/your-username/your-repo.git /opt/myapp
cd /opt/myapp
npm install --production
Using --production skips devDependencies, which reduces the node_modules size and install time. Ensure your application loads via a start script in package.json, PM2 reads this by default. If your entry point is server.js or index.js and it is not in the scripts section, you can pass the file directly in the next step.
Test that the app starts manually first.
node server.js
Hit Ctrl+C to stop it once you confirm it runs without errors. This catches missing dependencies and syntax problems before PM2 gets involved.
Step 3, Starting the application with PM2
Start the app with PM2, giving it a meaningful name.
pm2 start server.js --name myapp
This launches one process. Check that it is running.
pm2 status
The output shows a table with the app name, process ID, status (online/errored), CPU and memory usage, uptime, and restart count. If it says errored, run pm2 logs myapp to see the error. The most common cause is a missing environment variable or a port conflict.
Set your environment variables before the next start if you need them. Create a file /opt/myapp/.env and load it.
pm2 start server.js --name myapp --env-file /opt/myapp/.env
Step 4, Cluster mode for multi-core utilization
A single PM2 instance uses one CPU core. To use all cores, start in cluster mode with the -i flag. Using -i max spawns one worker per logical CPU.
pm2 delete myapp
pm2 start server.js --name myapp -i max
Verify the cluster.
pm2 status
You should see multiple processes, each with a different PM2 ID, all running under the same application name. Incoming HTTP requests are distributed across these workers by PM2's built-in load balancer. If your app stores session state in memory, this breaks because a request might hit a different worker on the next trip. The fix is either use a shared Redis store or set -i 1 to run a single instance. For stateless API servers and microservices, -i max is the right choice.
To control the number of workers manually when four cores are more than you need for the traffic.
pm2 start server.js --name myapp -i 2
Step 5, Setting up PM2 to start on boot
When the VPS restarts, whether for a kernel update or a power cycle, PM2 processes die unless you configure a startup script. PM2 can generate a systemd service that runs on boot and resurrects your saved process list.
pm2 startup
This prints a command that you must copy and run as root to enable the systemd unit. On Ubuntu, the output looks similar to:
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u youruser --hp /home/youruser
Run the printed command. Then save your current process list so PM2 knows what to restart.
pm2 save
The pm2 save command writes the process list to ~/.pm2/dump.pm2. On reboot, the systemd service runs pm2 resurrect which reads this dump and restarts all the apps. Verify the systemd unit is active.
systemctl status pm2-youruser
If it shows enabled and active (exited), the startup hook is working. The (exited) status is normal, the service runs the pm2 resurrect command and then exits.
Step 6, Log management and rotation
PM2 writes logs by default to ~/.pm2/logs/. Without rotation, a busy application can fill the disk in days. PM2 includes a separate log rotation module that handles this automatically.
pm2 install pm2-logrotate
Configure the rotation parameters.
pm2 set pm2-logrotate:max_size 50M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true
This keeps logs under 50 MB per file, retains 7 compressed files, and discards anything older. Disk usage stays predictable regardless of traffic volume. Verify the configuration.
pm2 conf pm2-logrotate
Step 7, Zero-downtime reload
When you deploy new code, stopping the app and restarting drops in-flight requests. PM2's reload command restarts workers one by one, waiting for each new worker to start accepting traffic before killing the old one.
pm2 reload myapp
This only works in cluster mode (-i max or -i N). In single-process mode, use pm2 restart myapp, which briefly drops connections. For a true zero-downtime deployment script, write a short shell script that pulls new code, installs dependencies, and reloads.
#!/bin/bash
cd /opt/myapp
git pull origin main
npm install --production
pm2 reload myapp
Run this script from your CI/CD pipeline or manually during a low-traffic window. The reload waits for the listening event from each new worker, so your app must call server.listen() before PM2 considers it ready.
Step 8, Monitoring with PM2
PM2 provides a real-time monitoring dashboard that shows CPU, memory, request volume, and log tail for all processes. Open it in the terminal.
pm2 monit
The screen splits into two panes. The left pane lists all process groups. Selecting one shows live CPU and memory usage on the right. Below the process list, the bottom pane tails the logs of the selected process. This is useful for catching memory leaks, if memory grows monotonically without dropping after garbage collection, something is leaking.
For a remote web-based dashboard, PM2 also offers a paid pm2 plus service. The free monit tool covers most needs on a single server. Run pm2 logs myapp to tail logs without the monitoring overhead.
Step 9, Setting up a reverse proxy with Nginx
PM2 runs your app on a port like 3000. Exposing that port directly to the internet is possible but not recommended. Nginx sits in front, handles SSL termination, serves static files directly, and proxies API requests to the PM2 cluster.
sudo apt install nginx -y
Create an Nginx site configuration.
sudo nano /etc/nginx/sites-available/myapp
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Enable the site and test the configuration.
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Obtain an SSL certificate with Certbot.
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.com
Verify HTTPS is working.
curl -I https://yourdomain.com
You should get a 200 OK response. The Nginx reverse proxy also shields your Node.js app from slow HTTP attacks and connection floods by buffering requests.
Step 10, Verify the full stack
Run a final check that everything is wired correctly.
pm2 status
systemctl status nginx
ss -tlnp | grep -E ':80|:443|:3000'
The ss output should show Nginx listening on 80 and 443, and your Node.js app listening on 3000 (or whatever port you configured). Test the application endpoints through the domain. If something fails, check both Nginx and PM2 logs.
sudo tail -f /var/log/nginx/error.log
pm2 logs myapp
Troubleshooting
PM2 status shows errored
Run pm2 logs myapp --lines 50 to see the last error. Common causes: a port is already in use, a required environment variable is missing, or a dependency failed to install. Check sudo lsof -i :3000 to see what is using the port.
PM2 startup command fails
The pm2 startup command outputs a line you must run as root. If the systemd unit does not appear after reboot, run sudo systemctl enable pm2-youruser and then pm2 save again.
Nginx returns 502 Bad Gateway
Your Node.js app is either not running or listening on a different port. Verify with pm2 status. If it is running, check that the proxy_pass directive in the Nginx config points to the correct port and that the app is bound to 127.0.0.1, not 0.0.0.0.
FAQ
How do I update a Node.js app without downtime?
Use pm2 reload instead of pm2 restart. Reload works only in cluster mode and restarts workers one at a time, keeping the service available throughout the process. Combine it with a script that pulls new code and reinstalls dependencies.
What happens if my app uses too much memory?
PM2 can auto-restart an app when memory exceeds a threshold. Set it when starting the app: pm2 start server.js --max-memory-restart 300M. This prevents a single leaky process from exhausting the VPS memory.
Can I run multiple Node.js apps on one VPS with PM2?
Yes. Each app gets its own PM2 process entry. Start them with different names and port numbers. Use Nginx as a reverse proxy to route traffic based on domain name to the correct port.
Does PM2 work with frameworks like Next.js or Nuxt?
Yes. For Next.js, run pm2 start npm --name nextapp -- start. For Nuxt, use pm2 start npm --name nuxtapp -- run start. Both work with cluster mode if your application supports it.
How do I see logs from all workers at once?
Run pm2 logs without specifying an app name. This tails logs from every process. Use pm2 logs myapp --lines 100 to see the last 100 lines of a specific app.
Should I use PM2 in a Docker container?
It depends. If the container runs only one Node.js process, Docker's built-in restart policy is sufficient. If you need cluster mode or the ecosystem file inside a container, PM2 is still useful. For most Docker setups, skip PM2 and rely on Docker Compose health checks.
Related articles
- Set up LEMP stack Nginx MariaDB PHP 8.3 on Ubuntu 24.04
- Nginx tuning for high traffic worker gzip buffer cache
- User and sudo permissions management on Linux VPS
- Set up Redis cache for web applications on a Linux VPS
PM2 生产环境部署 Node.js 到 VPS 的完整指南
PM2 是一个成熟的 Node.js 进程管理器,在生产环境中必不可少。它能确保应用自动启动、崩溃后重启,并支持集群模式充分利用 VPS 的多个 CPU 核心。配合 Nginx 反向代理和 SSL 证书,可以在越南 VPS 上稳定运行 Node.js 后端服务。零宕机更新功能让代码部署不影响在线用户。建议所有 Node.js 生产环境都使用 PM2,并开启内存限制和日志轮转。


