Files
docs/content/vps/nodejs-pm2.md
2026-07-28 07:20:32 -05:00

4.7 KiB

title, description, section, order
title description section order
Deploy a Node.js App with PM2 and Nginx Run a Node.js application behind Nginx reverse proxy with PM2 process management. vps 4

Deploy a Node.js App with PM2 and Nginx

This guide covers running a Node.js application on your Arcline VPS with PM2 for process management and Nginx as a reverse proxy.


Prerequisites

  • A VPS with Nginx installed
  • A Node.js application (Express, Koa, Fastify, or similar)
  • SSH access to your VPS

Step 1 — Install Node.js

Install Node.js from the official NodeSource repository (recommended over the system package manager):

# Node.js 22.x (LTS)
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install nodejs -y

Verify:

node --version
npm --version

Step 2 — Install PM2

PM2 is a process manager that keeps your Node.js app running, handles logging, and provides monitoring.

sudo npm install -g pm2

Step 3 — Upload your application

Create a directory for your app:

sudo mkdir -p /var/www/example.com
sudo chown -R $USER:$USER /var/www/example.com

Upload your application files via SFTP, rsync, or git:

cd /var/www/example.com
git clone https://git.arcline.it/yourname/your-app.git .
npm install --production

Step 4 — Start the app with PM2

pm2 start app.js --name example-app

Or if your app uses npm start:

pm2 start npm --name example-app -- start

Save the PM2 process list so it restarts on reboot:

pm2 save
pm2 startup

The pm2 startup command will output a command for you to run with sudo. Follow its instructions.


Step 5 — Configure Nginx as a reverse proxy

Your Node.js app runs on a port like 3000 or 8080. Nginx will sit in front of it, handling SSL and serving static assets directly.

Create an Nginx config:

sudo nano /etc/nginx/sites-available/example.com
server {
    listen 80;
    server_name example.com www.example.com;

    # If your app serves static files from a public directory
    location /static/ {
        alias /var/www/example.com/public/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    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_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}

Enable the site:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Set up SSL:

sudo certbot --nginx -d example.com -d www.example.com

Step 6 — Environment variables

Create an environment file:

nano /var/www/example.com/.env
PORT=3000
NODE_ENV=production
DATABASE_URL=postgres://...

Update your PM2 process to load it:

pm2 delete example-app
pm2 start app.js --name example-app --env-file /var/www/example.com/.env
pm2 save

Or use a PM2 ecosystem file (ecosystem.config.js):

module.exports = {
  apps: [{
    name: 'example-app',
    script: 'app.js',
    env_file: '/var/www/example.com/.env',
    instances: 2,
    exec_mode: 'cluster',
    max_memory_restart: '500M',
  }]
};

Then:

pm2 start ecosystem.config.js

PM2 useful commands

Command Description
pm2 list List all processes
pm2 logs Show live logs
pm2 logs --lines 100 Show last 100 lines
pm2 monit Real-time CPU/memory monitor
pm2 restart example-app Restart an app
pm2 reload example-app Zero-downtime reload
pm2 stop example-app Stop an app
pm2 delete example-app Remove from PM2
pm2 save Save process list
pm2 startup Generate startup script

WebSocket support

If your app uses WebSockets, ensure the upgrade headers are passed through. The config above already includes them. For socket.io, add these to your Nginx config:

location /socket.io/ {
    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_set_header X-Real-IP $remote_addr;
    proxy_read_timeout 86400;
}

What's next