Deployment and Production
Deploying WebSocket applications requires special considerations beyond traditional web applications. In this lesson, we'll cover everything you need to know about taking your real-time application to production.
Production Considerations
Before deploying, ensure your application is production-ready:
Key Differences: WebSocket applications maintain persistent connections, require proper proxying, need process management, and have different scaling requirements than traditional HTTP applications.
Setting Up PM2 for Process Management
PM2 is a production process manager for Node.js applications that keeps your WebSocket server running:
# Install PM2 globally
npm install -g pm2
# Start your application
pm2 start server.js --name "websocket-server"
# View running processes
pm2 list
# View logs
pm2 logs websocket-server
# Monitor resources
pm2 monit
Create a PM2 ecosystem file for better configuration (ecosystem.config.js):
module.exports = {
apps: [{
name: 'websocket-server',
script: './server.js',
instances: 1,
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm Z',
max_memory_restart: '1G',
watch: false,
autorestart: true,
max_restarts: 10,
min_uptime: '10s'
}]
};
Start with the ecosystem file:
pm2 start ecosystem.config.js
pm2 save
pm2 startup
Tip: Use pm2 save and pm2 startup to ensure your application restarts automatically after server reboots.
Configuring Nginx as WebSocket Proxy
Nginx can proxy WebSocket connections to your Node.js server. Create an Nginx configuration:
# /etc/nginx/sites-available/websocket-app
upstream websocket_backend {
server localhost:3000;
keepalive 64;
}
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
# WebSocket upgrade headers
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Preserve client information
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;
# Timeout settings
proxy_connect_timeout 7d;
proxy_send_timeout 7d;
proxy_read_timeout 7d;
}
}
Enable the site and restart Nginx:
sudo ln -s /etc/nginx/sites-available/websocket-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
Warning: The proxy_read_timeout must be longer than your WebSocket ping/pong interval to prevent timeouts.
Enabling SSL/TLS for Secure WebSockets (WSS)
Production WebSocket servers should use wss:// (WebSocket Secure). Install Certbot for Let's Encrypt SSL certificates:
# Install Certbot
sudo apt install certbot python3-certbot-nginx
# Obtain SSL certificate
sudo certbot --nginx -d yourdomain.com
# Auto-renewal is configured automatically
sudo certbot renew --dry-run
Update your Nginx configuration for HTTPS:
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://websocket_backend;
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;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}
Update your client code to use WSS:
const socket = io('https://yourdomain.com', {
transports: ['websocket', 'polling'],
secure: true
});
Environment Configuration
Use environment variables for configuration. Create a .env file:
NODE_ENV=production
PORT=3000
REDIS_HOST=localhost
REDIS_PORT=6379
CORS_ORIGIN=https://yourdomain.com
JWT_SECRET=your-secret-key-here
DATABASE_URL=postgresql://user:pass@localhost:5432/dbname
Load environment variables in your application:
require('dotenv').config();
const PORT = process.env.PORT || 3000;
const REDIS_HOST = process.env.REDIS_HOST || 'localhost';
const io = require('socket.io')(server, {
cors: {
origin: process.env.CORS_ORIGIN,
credentials: true
}
});
Implementing Health Checks
Add health check endpoints for monitoring:
const express = require('express');
const app = express();
app.get('/health', (req, res) => {
const health = {
uptime: process.uptime(),
timestamp: Date.now(),
status: 'ok',
connections: io.engine.clientsCount
};
res.json(health);
});
app.get('/ready', async (req, res) => {
try {
// Check Redis connection
await redisClient.ping();
// Check database connection
await db.query('SELECT 1');
res.status(200).json({ status: 'ready' });
} catch (error) {
res.status(503).json({ status: 'not ready', error: error.message });
}
});
const server = app.listen(PORT);
const io = require('socket.io')(server);
Graceful Shutdown
Implement graceful shutdown to close connections properly:
let isShuttingDown = false;
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);
async function gracefulShutdown() {
if (isShuttingDown) return;
isShuttingDown = true;
console.log('Starting graceful shutdown...');
// Stop accepting new connections
server.close(() => {
console.log('HTTP server closed');
});
// Notify all connected clients
io.emit('server:shutdown', {
message: 'Server is shutting down, please reconnect'
});
// Give clients time to disconnect
setTimeout(() => {
// Close all Socket.io connections
io.close(() => {
console.log('Socket.io server closed');
});
// Close Redis connection
redisClient.quit();
// Exit process
process.exit(0);
}, 5000);
}
Monitoring with Prometheus
Install Prometheus metrics for monitoring:
npm install prom-client
Set up metrics collection:
const promClient = require('prom-client');
// Create a Registry
const register = new promClient.Registry();
// Add default metrics
promClient.collectDefaultMetrics({ register });
// Custom metrics
const connectedClients = new promClient.Gauge({
name: 'websocket_connected_clients',
help: 'Number of connected WebSocket clients',
registers: [register]
});
const messagesTotal = new promClient.Counter({
name: 'websocket_messages_total',
help: 'Total number of WebSocket messages',
labelNames: ['event'],
registers: [register]
});
// Update metrics
io.on('connection', (socket) => {
connectedClients.inc();
socket.on('disconnect', () => {
connectedClients.dec();
});
socket.onAny((event) => {
messagesTotal.inc({ event });
});
});
// Metrics endpoint
app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});
Logging Best Practices
Use structured logging for production:
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
// Log in production
if (process.env.NODE_ENV === 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
// Use logger
io.on('connection', (socket) => {
logger.info('Client connected', {
socketId: socket.id,
userId: socket.user?.id
});
});
Deployment Checklist
Pre-Deployment Checklist:
- ✓ Environment variables configured
- ✓ SSL certificates installed (WSS)
- ✓ Nginx configured as reverse proxy
- ✓ PM2 configured with ecosystem file
- ✓ Health check endpoints implemented
- ✓ Graceful shutdown implemented
- ✓ Logging configured
- ✓ Metrics collection enabled
- ✓ CORS properly configured
- ✓ Rate limiting enabled
- ✓ Firewall rules configured
- ✓ Backup strategy in place
Exercise: Deploy a Socket.io application to a production server. Configure Nginx as a reverse proxy with SSL/TLS enabled. Set up PM2 for process management. Implement health checks and graceful shutdown. Add Prometheus metrics and create a dashboard to monitor connected clients and message throughput. Test the deployment by connecting from multiple clients and monitoring the logs.