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
| Aspect | Development | Production |
|---|---|---|
| Start command | node app.js or npm start | PM2, systemd, or supervisor |
| Auto-restart | Manual (nodemon) | Automatic (process manager) |
| Concurrency | Single process | Cluster mode (multi-core) |
| Error handling | Show in console | Log to file/service, alert admin |
| Environment | NODE_ENV=development | NODE_ENV=production |
| Security | Minimal (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);
});
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
Need a developer-friendly server?
Use an UnderHost Cloud VPS for SSH, Git, Node.js, Python, Laravel, Docker, cron, and custom development workflows.





















