1. Install Node.js
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt install -y nodejs
node -v
2. Deploy your app
adduser --system --group --home /srv/app app
sudo -u app git clone https://github.com/you/yourapp /srv/app/current
cd /srv/app/current && sudo -u app npm ci --omit=dev
3. Run it with PM2
npm install -g pm2
sudo -u app pm2 start /srv/app/current/index.js --name app -i max # cluster mode, one worker per vCPU
sudo -u app pm2 save
pm2 startup systemd -u app --hp /srv/app # run the printed command
Your app should listen on 127.0.0.1:3000 (or another local port), never on 0.0.0.0.
4. Nginx reverse proxy
apt install -y nginx
cat > /etc/nginx/sites-available/app <<'EOF'
server {
listen 80;
server_name example.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_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
EOF
ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
5. HTTPS
apt install -y certbot python3-certbot-nginx
certbot --nginx -d example.com
6. Firewall and updates
ufw allow OpenSSH && ufw allow 'Nginx Full' && ufw enable
Deploy a new version with git pull && npm ci --omit=dev && pm2 reload app — zero-downtime in cluster mode. Logs: pm2 logs app. Take a snapshot before major upgrades, and put the Joy CDN in front for static assets.