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
- Deploy a Go binary as a systemd service
- Set up automated backups with restic
- Install fail2ban for SSH protection