Deploy Node.js on VPS with PM2 and Nginx
Node.js doesn't come with a built-in web server like Apache or Nginx. The correct approach for production is to use PM2 to manage your Node process and configure Nginx as a reverse proxy to accept requests from the internet and forward them to your app. This article covers all the steps you need.
Install Node.js (LTS)
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
apt install nodejs -y
node -v && npm -v
Install PM2
npm install -g pm2
Deploy Your Application
# Clone or copy your project to the VPS
mkdir -p /var/www/myapp
cd /var/www/myapp
git clone https://github.com/you/myapp.git .
npm install --production
Start Your App with PM2
# Start with a direct script
pm2 start app.js --name myapp
# Or if you use npm start script
pm2 start npm --name myapp -- start
# Check status
pm2 status
pm2 logs myapp
Configure PM2 to Auto-Start on Reboot
pm2 startup systemd
# Copy the command PM2 displays and run it
pm2 save
Set Up Nginx Reverse Proxy
nano /etc/nginx/sites-available/myapp
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
}
}
ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Add SSL with Certbot
apt install certbot python3-certbot-nginx -y
certbot --nginx -d yourdomain.com
Environment Variables
Never store secrets in your source code. Instead, use PM2's ecosystem file:
# ecosystem.config.js
module.exports = {
apps: [{
name: 'myapp',
script: 'app.js',
env: {
NODE_ENV: 'production',
PORT: 3000,
DB_URL: 'mongodb://localhost/mydb'
}
}]
}
# Start with
pm2 start ecosystem.config.js
Commonly Used PM2 Commands
pm2 list # View all processes
pm2 restart myapp # Restart
pm2 reload myapp # Reload with zero-downtime
pm2 stop myapp # Stop
pm2 delete myapp # Remove from list
pm2 monit # Real-time dashboard
pm2 logs myapp --lines 100 # View recent logs
pm2 reload instead of restart when updating code — reload spawns new instances before killing old ones, so there's no downtimeSummary
PM2 + Nginx is the standard combination for Node.js production deployments on VPS. PM2 handles process lifecycle management while Nginx acts as a load balancer and SSL terminator, making your system robust and deployment straightforward.
Verify Node.js Deployment is Successful
# Check PM2 status
pm2 status
pm2 list
# View recent logs
pm2 logs myapp --lines 30
# Test the response
curl -I https://yourdomain.com
# Verify the process survives a reboot
pm2 startup systemd
pm2 save
pm2 start app.js -i max to automatically run multiple instances equal to the number of cores, boosting throughput without code changesCommon Issues & Troubleshooting
Error: "EACCES: permission denied, open '/root/...'"
You're running PM2 as root and trying to access files from another user. It's recommended to always run PM2 as a non-root user:
# Create a dedicated user for Node app
adduser nodeapp
su - nodeapp
pm2 start /var/www/myapp/app.js --name myapp
Error: "Error: listen EADDRINUSE: address already in use :3000"
Port 3000 is already in use by another process, or PM2 has an old process still running:
# Check what's using port 3000
ss -tlnp | grep :3000
lsof -i :3000
# If PM2 has an old process stuck
pm2 delete myapp
pm2 start app.js --name myapp
App crashes and PM2 restarts it repeatedly (restart loop)
First, check the error before it crashes:
# View error logs
pm2 logs myapp --err --lines 100
# Stop the restart loop temporarily
pm2 stop myapp
# Test running the app directly first
node app.js
Node.js app is slow or has a memory leak on production
# View memory usage for each process
pm2 monit
# Set max memory before auto-restart
pm2 start app.js --name myapp --max-memory-restart 500M
# Inspect heap with Node debugger
node --inspect app.js
npm install is slow or fails with ENOMEM on a low-RAM VPS
# Add temporary swap for npm install
fallocate -l 1G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
npm install --production # Install production dependencies only
Frequently Asked Questions
What's the difference between PM2 and systemd? Which should I use?
PM2 is specifically designed for Node.js and includes cluster mode, log management, monitoring, and restart policies built-in. systemd is a general-purpose process manager used by the OS. If you have multiple Node apps, PM2 is easier to manage overall.
How do I deploy new code without downtime?
# Pull the new code
git pull origin main
npm install --production
# Reload with zero-downtime
pm2 reload myapp # Not restart
Can Node.js handle HTTPS by itself without Nginx?
Yes, with https.createServer(), but it's not recommended for production. Nginx is more efficient at serving SSL, static files, compression, and rate limiting, letting your Node.js focus on application logic.