Skip to content

Websocket Sticky Sessions

DodaTech 4 min read

title: "WebSocket Sticky Sessions" description: "Understand sticky sessions for WebSocket load balancing, why they are needed, and how to configure them with different proxy solutions." weight: 20 date: 2026-06-28 lastmod: 2026-06-28 tags: ["apis", "websocket"]


Sticky sessions (session affinity) ensure that all WebSocket messages from a client go to the same server instance. This is critical for stateful WebSocket applications running behind a load balancer.

## What You'll Learn

- Why WebSocket needs sticky sessions
- Sticky session configuration in NGINX
- Sticky sessions with HAProxy
- Cookie-based vs IP-based affinity
- Alternatives to sticky sessions

## Why It Matters

Without sticky sessions, load balancers may route consecutive WebSocket frames from the same client to different servers, breaking the connection. Understanding sticky sessions is essential for scaling WebSocket applications.

## Real-World Use

A multiplayer gaming platform uses NGINX with sticky sessions to route WebSocket connections. Each game session is tied to a specific server, ensuring consistent state and low-latency communication for all players in a match.

## Flow Chart

```mermaid
flowchart LR
    A[Client] --> B[Load Balancer]
    B --> C{Sticky Session?}
    C -->|Yes| D[Server A]
    C -->|Yes| E[Server A]
    C -->|No| F[Server A / Server B]
    D --> G[Consistent State]
    F --> H[State Loss]

Code Examples

Example 1: NGINX Sticky Sessions for WebSocket

upstream websocket_backend {
    # Sticky session using cookies
    sticky cookie srv_id expires=1h path=/;

    server backend1.example.com:8080;
    server backend2.example.com:8080;
    server backend3.example.com:8080;
}

server {
    listen 443 ssl;
    server_name ws.example.com;

    location /ws {
        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;
        
        # Timeout settings for long-lived connections
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
}

Expected output: NGINX routes WebSocket connections to the same backend using a sticky cookie, with long timeouts for persistent connections.

Example 2: HAProxy Sticky Sessions

frontend ws_frontend
    bind *:443 ssl crt /etc/ssl/certs/server.pem
    mode tcp
    default_backend ws_backend

backend ws_backend
    mode tcp
    
    # Sticky session based on source IP
    balance source
    hash-type consistent
    
    # Alternative: cookie-based stickiness
    # cookie SERVERID insert indirect nocache
    
    server backend1 backend1:8080 check
    server backend2 backend2:8080 check
    server backend3 backend3:8080 check
    
    # Health check for WebSocket
    option httpchk GET /health
    http-check expect status 200

Expected output: HAProxy uses source IP-based stickiness to route WebSocket connections consistently, with consistent hashing for minimal redistribution on server changes.

Example 3: Application-Level Sticky Sessions with Redis

const WebSocket = require('ws');
const Redis = require('ioredis');

class StickySessionManager {
  constructor() {
    this.redis = new Redis();
    this.sessionMap = new Map();
  }

  async assignServer(clientId) {
    const serverId = process.env.SERVER_ID || `server-${Math.random().toString(36).slice(2)}`;
    await this.redis.set(`session:${clientId}`, serverId, 'EX', 3600);
    return serverId;
  }

  async getServer(clientId) {
    return await this.redis.get(`session:${clientId}`);
  }

  async releaseSession(clientId) {
    await this.redis.del(`session:${clientId}`);
  }
}

// WebSocket server with session affinity
const wss = new WebSocket.Server({ port: 8080 });
const sessionManager = new StickySessionManager();

wss.on('connection', async (ws, req) => {
  const clientId = req.headers['x-client-id'];
  
  if (clientId) {
    await sessionManager.assignServer(clientId);
    console.log(`Client ${clientId} assigned to this server`);
  }

  ws.on('close', async () => {
    if (clientId) {
      await sessionManager.releaseSession(clientId);
    }
  });
});

// Load balancer routing logic
async function routeRequest(clientId) {
  const serverId = await sessionManager.getServer(clientId);
  if (serverId) {
    return `http://${serverId}:8080`;
  }
  // Round-robin fallback
  return selectNextServer();
}

Expected output: Application-level sticky session tracking using Redis, enabling custom routing logic in the load balancer.

Common Mistakes

Mistake Explanation
Relying on IP-based stickiness behind NAT Multiple users behind the same NAT appear as one IP, causing incorrect routing
Using short timeouts for sticky cookies WebSocket connections can last hours; set cookie TTL appropriately
Not configuring consistent hashing Without consistent hashing, adding/removing servers redistributes all connections
Forgetting health checks Unhealthy servers with active sessions cause connection drops; implement graceful failover
Using HTTP-only sticky methods for TCP mode HTTP sticky methods require HTTP mode; use TCP mode for raw WebSocket proxying

Practice Questions

  1. Why do WebSocket connections need sticky sessions?
  2. How does cookie-based stickiness differ from IP-based stickiness?
  3. What is consistent hashing and why is it useful for WebSocket?
  4. How do you implement sticky sessions with NGINX?
  5. What are alternatives to sticky sessions for scaling WebSocket?

Challenge

Set up a three-node WebSocket server cluster with NGINX sticky sessions. Implement a chat application that maintains per-user state on each server. Test that reconnecting clients are routed to the same server and maintain their state.

FAQ

Can I scale WebSocket without sticky sessions?

Yes, use a shared state store (Redis, database) for session data. Any server can handle any client, but this adds latency for state lookups.

What happens when a sticky server goes down?

Clients lose their session. Use a shared session store so clients can reconnect to any available server.

Does HTTP/2 affect sticky sessions?

HTTP/2 multiplexing complicates sticky sessions. Use HTTP/1.1 for WebSocket upgrade or configure the load balancer for HTTP/2.

How do I test sticky session configuration?

Connect multiple WebSocket clients and verify they maintain affinity to the same backend using server-side logs or response headers.

What is the performance cost of sticky sessions?

Sticky sessions reduce load balancing flexibility. Some servers may become overloaded while others are idle. Monitor and rebalance as needed.

Can I use DNS-based stickiness?

DNS resolution happens once per connection, so DNS effectively provides stickiness during the connection lifetime, but it is unreliable for long-lived connections.

Mini Project

Deploy a WebSocket chat application behind HAProxy with cookie-based sticky sessions. Set up three backend servers, implement a session state store in Redis, and create a dashboard showing which server each client is connected to.

What's Next

Learn about WebSocket load balancing strategies

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro