Node.js
Node.js Production Deployment Complete Guide
Deploy Node.js applications to production with PM2, Nginx reverse proxy, SSL, structured logging, monitoring, and zero-downtime deployments.
What you'll learn
- Install Node.js 22 LTS via nvm on Ubuntu 24.04
- Configure PM2 for cluster mode and auto-restart
- Set up Nginx as a reverse proxy with SSL
- Implement zero-downtime deployments with PM2 reload
- Configure environment variables securely
- Monitor application health and set up log rotation
- Automate deployments with GitHub Actions
Node.js Installation
Bash
1# Install nvm
2curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
3source ~/.bashrc
4
5# Install Node.js 22 LTS
6nvm install 22
7nvm use 22
8nvm alias default 22
9
10# Install pnpm
11npm install -g pnpm pm2
12
13node --version # v22.x.x
14pm2 --versionPM2 Process Manager
JavaScript
1# ecosystem.config.js
2module.exports = {
3 apps: [{
4 name: 'myapp',
5 script: 'dist/server.js',
6 instances: 'max', // one per CPU core
7 exec_mode: 'cluster',
8 max_memory_restart: '512M',
9 env_production: {
10 NODE_ENV: 'production',
11 PORT: 3000,
12 },
13 log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
14 error_file: '/var/log/myapp/error.log',
15 out_file: '/var/log/myapp/out.log',
16 merge_logs: true,
17 }],
18};
19
20# Start
21pm2 start ecosystem.config.js --env production
22
23# Save and enable on boot
24pm2 save
25pm2 startup # follow the printed command
26
27# Monitor
28pm2 monit
29pm2 status
30pm2 logs myapp --lines 100Nginx Reverse Proxy
NGINX
1# /etc/nginx/sites-available/myapp
2server {
3 listen 80;
4 server_name myapp.example.com;
5 return 301 https://$host$request_uri;
6}
7
8server {
9 listen 443 ssl;
10 http2 on;
11 server_name myapp.example.com;
12
13 ssl_certificate /etc/letsencrypt/live/myapp.example.com/fullchain.pem;
14 ssl_certificate_key /etc/letsencrypt/live/myapp.example.com/privkey.pem;
15
16 client_max_body_size 10M;
17
18 location / {
19 proxy_pass http://127.0.0.1:3000;
20 proxy_http_version 1.1;
21 proxy_set_header Upgrade $http_upgrade;
22 proxy_set_header Connection 'upgrade';
23 proxy_set_header Host $host;
24 proxy_set_header X-Real-IP $remote_addr;
25 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
26 proxy_set_header X-Forwarded-Proto $scheme;
27 proxy_read_timeout 30s;
28 }
29}
30
31sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
32sudo nginx -t && sudo systemctl reload nginxEnvironment Variables
Bash
1# /etc/myapp.env — owner root, mode 600
2DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
3JWT_SECRET=your-256-bit-secret
4NODE_ENV=production
5PORT=3000
6
7# Load in ecosystem.config.js
8require('dotenv').config({ path: '/etc/myapp.env' });
9
10# Or pass via PM2
11pm2 start ecosystem.config.js --env production --node-args "--env-file=/etc/myapp.env"Zero-downtime Deployment
Bash
1# deploy.sh — run on server after pulling new code
2#!/bin/bash
3set -e
4
5cd /opt/myapp
6
7# Pull latest code
8git pull origin main
9
10# Install dependencies
11pnpm install --frozen-lockfile
12
13# Build
14pnpm build
15
16# Run migrations
17pnpm prisma migrate deploy
18
19# Reload — PM2 replaces workers one at a time (zero downtime)
20pm2 reload ecosystem.config.js --env production
21
22echo "Deployment complete"Monitoring & Logs
Bash
1# PM2 log rotation
2pm2 install pm2-logrotate
3pm2 set pm2-logrotate:max_size 100M
4pm2 set pm2-logrotate:retain 7
5pm2 set pm2-logrotate:compress true
6
7# Health check endpoint in your app
8app.get('/health', (req, res) => {
9 res.json({ status: 'ok', uptime: process.uptime(), timestamp: new Date().toISOString() });
10});
11
12# Monitor with Nginx
13location = /health {
14 proxy_pass http://127.0.0.1:3000/health;
15 access_log off;
16}
17
18# Sentry error tracking
19import * as Sentry from '@sentry/node';
20Sentry.init({ dsn: process.env['SENTRY_DSN'], tracesSampleRate: 0.1 });