Ludo Game Load Balancing: Nginx, Sticky Sessions, Health Checks
Load balancing is the routing layer that distributes thousands of concurrent Ludo game connections across your server fleet without players noticing. Unlike stateless REST APIs where any server can handle any request, WebSocket-based multiplayer Ludo games have state — a player's WebSocket connection is bound to a specific game server instance. This creates a fundamental challenge: how do you balance connections fairly while keeping each player's session on the same server throughout a match? This guide covers Nginx configuration for WebSocket-aware load balancing, sticky session implementation, active health checks, and zero-downtime deployment strategies that keep Ludo matches running through server upgrades.
Nginx Upstream Configuration for WebSocket
Nginx's upstream module distributes WebSocket connections using the IP hash algorithm, which consistently routes
the same client IP to the same backend server. This satisfies the sticky session requirement for Ludo's stateful
WebSocket connections. The key configuration nuance is the Upgrade and Connection header
passing, which enables Nginx to properly handle the WebSocket handshake and keep-alive connections.
# /etc/nginx/conf.d/ludo-game.conf
upstream ludo_game_backend {
# IP hash ensures same client IP always hits same backend (sticky sessions)
ip_hash;
# Game server instances — scale by adding more lines
server 10.0.1.10:3000 max_fails=3 fail_timeout=30s;
server 10.0.1.11:3000 max_fails=3 fail_timeout=30s;
server 10.0.1.12:3000 max_fails=3 fail_timeout=30s;
keepalive 64; # Keep connections alive to upstream — reduces latency
}
server {
listen 443 ssl http2;
server_name api.ludokingapi.site;
ssl_certificate /etc/ssl/ludokingapi.site.crt;
ssl_certificate_key /etc/ssl/ludokingapi.site.key;
# REST API endpoints — standard proxy
location /api/ {
proxy_pass http://ludo_game_backend;
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;
# HTTP 1.1 needed for keep-alive to upstream
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache_bypass $http_upgrade;
}
# WebSocket endpoint — upgrade and persist connection
location /socket.io/ {
proxy_pass http://ludo_game_backend;
proxy_http_version 1.1;
# CRITICAL: WebSocket upgrade headers
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;
# Disable buffering — WebSocket frames must flow without delay
proxy_buffering off;
proxy_read_timeout 86400; # Allow 24hr WebSocket sessions
# Increase send timeout for high-throughput game servers
proxy_send_timeout 300;
}
# Health check endpoint (no caching)
location /health {
proxy_pass http://ludo_game_backend/health;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
}
Active Health Checks with Nginx Plus or ModSecurity
Open-source Nginx does not natively support active health checks — it marks a server as down only after a failed request, which can strand in-progress Ludo games. Nginx Plus or third-party modules like nginx_upstream_check_module add periodic health probes that proactively remove failed servers from the pool. Alternatively, run a lightweight health endpoint on each game server that Nginx polls every 5 seconds.
// Health check endpoint to attach to your game server
// GET /health — used by load balancer for active probing
app.get('/health', async (req, res) => {
try {
// Check Redis connectivity
await redis.ping();
// Check PostgreSQL connectivity
await db.query('SELECT 1');
// Report active WebSocket connections count
const activeConnections = wss ? wss.clients.size : 0;
const maxConnections = parseInt(process.env.MAX_CONNECTIONS || '1000', 10);
const health = {
status: 'healthy',
uptime: process.uptime(),
connections: activeConnections,
capacity: `${Math.round(activeConnections / maxConnections * 100)}%`,
region: process.env.REGION || 'unknown',
version: process.env.APP_VERSION || 'dev'
};
// Warn if approaching capacity
if (activeConnections >= maxConnections * 0.9) {
health.status = 'degraded';
health.reason = 'High connection count';
}
res.status(health.status === 'healthy' ? 200 : 503).json(health);
} catch (err) {
res.status(503).json({
status: 'unhealthy',
error: err.message,
uptime: process.uptime()
});
}
});
// Kubernetes readiness probe
app.get('/ready', (req, res) => {
// Readiness checks: is this server ready to accept NEW connections?
const isReady = process.uptime() > 10; // Wait 10s after start before marking ready
res.status(isReady ? 200 : 503).json({ ready: isReady });
});
Graceful Shutdown and Zero-Downtime Deployments
When deploying a new game server version, you must migrate existing players off old servers without interrupting
their matches. A graceful shutdown sequence waits for active WebSocket sessions to end naturally (up to a
timeout), then stops accepting new connections and drains existing ones. Nginx's max_fails and
fail_timeout parameters automatically route new players away from the draining server.
// Graceful shutdown handler for Ludo game server
async function gracefulShutdown(signal) {
console.log(`\n${signal} received. Starting graceful shutdown...`);
// Step 1: Stop accepting new connections
server.close(async () => {
console.log('HTTP server closed (no new connections)');
});
// Step 2: Notify all connected players of imminent server restart
if (wss && wss.clients) {
wss.clients.forEach(client => {
if (client.readyState === 1) { // WebSocket.OPEN
client.send(JSON.stringify({
type: 'SERVER_SHUTDOWN',
message: 'Server restarting in 30 seconds. Reconnecting...',
reconnectIn: 30000
}));
}
});
}
// Step 3: Wait for game sessions to complete (max 60 seconds)
await sleep(60000);
// Step 4: Force close remaining WebSocket connections
if (wss && wss.clients) {
wss.clients.forEach(client => client.terminate());
}
// Step 5: Close database and Redis connections
await db.end();
await redis.quit();
console.log('Graceful shutdown complete.');
process.exit(0);
}
// Set shutdown handlers
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
// Force exit after timeout (safety net)
setTimeout(() => {
console.error('Forced shutdown after timeout.');
process.exit(1);
}, 90000);
WebSocket Connection Limits and Fairness
Without connection limits, a single IP could saturate a game server's WebSocket capacity, blocking other players.
Configure Nginx's limit_conn_zone and per-server connection limits to ensure fair access. For Ludo
specifically, limit to one active game per player account to prevent players from creating multiple simultaneous
sessions that could be used for unfair practices.
FAQ
Each player's WebSocket connection is bound to a specific game server instance that holds their game state in memory. If a player were routed to a different server mid-match, they would have no game state there and would be unable to participate. Sticky sessions ensure the same server handles all requests from the same client IP throughout the game.
IP hash is the preferred algorithm for WebSocket-based Ludo games because it provides natural sticky sessions without additional configuration. Least connections is an alternative that routes new players to the least-loaded server, but it does not guarantee session affinity.
Nginx passes the Upgrade and Connection headers when configured with
proxy_set_header Upgrade $http_upgrade and proxy_set_header Connection "upgrade".
This allows the HTTP 101 Switching Protocols response to flow through Nginx to establish the WebSocket
tunnel.
With graceful shutdown, the server notifies all players 30 seconds before closing, giving them time to save their game state to Redis and reconnect to a new server. The new server retrieves the persisted state and resumes the match from where it left off.
Configure Nginx limit_conn_zone with a key based on $remote_addr or a
authenticated player ID token. Set a reasonable limit (e.g., 5 connections per IP) to prevent abuse while
allowing legitimate multi-device usage.
Set Up Production Load Balancing for Your Ludo Platform
Get help configuring Nginx, health checks, and zero-downtime deployments for your Ludo game servers.
Contact Us on WhatsApp