UnderHost
Knowledgebase Docs

Node.js Deployment Best Practices: Dev to Prod

Deploy Node.js applications to production. Process management, reverse proxies, environment setup, performance optimization, monitoring,

On this page

Deploying Node.js to production requires more than just `node app.js` in a terminal. Production deployments need process management (auto-restart on crash), reverse proxying (SSL/HTTP), clustering (multi-core utilization), environment configuration, and monitoring. This guide covers production-grade Node.js deployment.

Deployment Overview

Production deployment checklist:**

  • ✅ Process manager (PM2, Forever, systemd)
  • ✅ Reverse proxy (Nginx, Apache)
  • ✅ SSL/HTTPS certificate
  • ✅ Environment variables (.env or systemd)
  • ✅ Logging (syslog, file, centralized)
  • ✅ Health checks and monitoring
  • ✅ Database backups and recovery
  • ✅ Graceful shutdown handling

Development vs Production

AspectDevelopmentProduction
Start commandnode app.js or npm startPM2, systemd, or supervisor
Auto-restartManual (nodemon)Automatic (process manager)
ConcurrencySingle processCluster mode (multi-core)
Error handlingShow in consoleLog to file/service, alert admin
EnvironmentNODE_ENV=developmentNODE_ENV=production
SecurityMinimal (dev only)HTTPS, firewalls, secrets in env vars

Process Managers (PM2, Forever)

PM2 (Recommended):**

# Install globally
npm install -g pm2

# Start app
pm2 start app.js --name "myapp" --instances 4

# Make it start on boot
pm2 startup
pm2 save

# Monitor
pm2 monit
pm2 logs myapp

Forever (Alternative):**

# Install globally
npm install -g forever

# Start app (restarts on crash)
forever start -u myuser app.js

# Monitor
forever logs
forever stopall

Systemd (System-Level):**

# Create /etc/systemd/system/nodeapp.service
[Unit]
Description=Node.js App
After=network.target

[Service]
User=www-data
WorkingDirectory=/var/www/nodeapp
ExecStart=/usr/bin/node /var/www/nodeapp/app.js
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

# Start service
systemctl daemon-reload
systemctl enable nodeapp
systemctl start nodeapp

Reverse Proxy Setup

Nginx reverse proxy to Node.js on localhost:3000:**

server {
    listen 80;
    server_name yourdomain.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_cache_bypass $http_upgrade;
        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;
    }
}

Benefits:**

  • Node.js runs on localhost (not exposed to internet)
  • Nginx handles SSL/HTTPS
  • Nginx can compress responses
  • Nginx can serve static files directly (faster)
  • Can load balance to multiple Node processes

Cluster Mode (Multi-Core)

Use all CPU cores instead of single core:**

# Start app on all cores
pm2 start app.js -i max  # 'max' means use all cores

# Or specify number
pm2 start app.js -i 4   # Use 4 cores even if server has 8

Manual clustering in app.js:**

const cluster = require('cluster');
const os = require('os');

if (cluster.isMaster) {
    // Fork workers (one per CPU core)
    for (let i = 0; i < os.cpus().length; i++) {
        cluster.fork();
    }

    // Restart on crash
    cluster.on('exit', (worker, code, signal) => {
        cluster.fork();
    });
} else {
    // Start Express app in each worker
    require('./server.js');
}

Production Configuration

Environment setup:**

# .env (production)
NODE_ENV=production
PORT=3000
DATABASE_URL=mysql://user:pass@prod-db.example.com/mydb
REDIS_URL=redis://prod-cache.example.com:6379
LOG_LEVEL=info
API_KEY=prod-secret-key-123

Load in app.js:**

require('dotenv').config();

// Verify all required vars exist
const required = ['DATABASE_URL', 'API_KEY'];
required.forEach(key => {
    if (!process.env[key]) {
        console.error(`Missing environment variable: ${key}`);
        process.exit(1);
    }
});

Monitoring & Logging

Monitor process health:**

# Health check endpoint in Express
app.get('/health', (req, res) => {
    res.json({
        status: 'ok',
        uptime: process.uptime(),
        memory: process.memoryUsage()
    });
});

# External monitoring
curl https://yourdomain.com/health

Log aggregation:**

# Log to file
pm2 start app.js --error /var/log/app-error.log --out /var/log/app-out.log

# Use Winston logging
npm install winston
const winston = require('winston');
const logger = winston.createLogger({
    level: 'info',
    format: winston.format.json(),
    transports: [
        new winston.transports.File({ filename: '/var/log/app.log' })
    ]
});

logger.info('Application started');

Zero-Downtime Deployments

Deploy new code without stopping app:**

# 1. Deploy new code to separate directory
git clone repo /var/www/myapp-v2

# 2. Install dependencies
cd /var/www/myapp-v2
npm install

# 3. Test new version
npm test

# 4. Gracefully reload old version
pm2 reload myapp --update-env

# 5. Switch traffic to new version (blue-green deployment)

Graceful shutdown in app.js:**

const server = app.listen(3000);

// Handle graceful shutdown
process.on('SIGTERM', async () => {
    console.log('Shutting down gracefully...');

    // Stop accepting new connections
    server.close(() => {
        console.log('Server closed');
        process.exit(0);
    });

    // Wait max 30 seconds
    setTimeout(() => process.exit(1), 30000);
});
Production Node.js = process management + monitoring + automation

Never run Node.js with bare `node app.js` in production. Use PM2 or systemd for auto-restart, Nginx for reverse proxy, and monitoring for reliability.

Related: Node.js fundamentals | Process management | Reverse proxy setup | Performance optimization

Was this article helpful?

Need a developer-friendly server?

Use an UnderHost Cloud VPS for SSH, Git, Node.js, Python, Laravel, Docker, cron, and custom development workflows.

Related articles

Back to Developer Tools