# How do you scale WebSockets to millions of concurrent connections?

Scaling WebSockets is a memory, file-descriptor and fan-out problem before it is a CPU problem. This guide walks through the limits per node, how to spread connections across a cluster, how to publish messages to the right sockets, and how to test the result.

Canonical: https://kitecorp.org/guides/scaling-websockets-to-millions-of-connections
Updated: 2026-10-12

## What limits a single WebSocket server?

Three ceilings, hit in this order. File descriptors: every connection is one, and the default limit on most Linux images is 1024 per process; raise it with `ulimit` or `LimitNOFILE` to the hundreds of thousands and also raise `fs.file-max` system-wide. Memory: each idle connection costs kernel buffers plus whatever your runtime keeps per socket, typically tens of kilobytes; at 500,000 connections that is tens of gigabytes before any application state, so the per-connection footprint is the first thing to measure and trim. Ephemeral ports matter only on the client or proxy side (one source address has about 28,000 usable ports by default), which is why load tests from a single machine lie and why a proxy in front of your servers needs multiple source addresses. CPU becomes the limit only once the first three are solved and traffic per connection rises.

## How do you spread connections across a cluster?

Behind a load balancer that supports long-lived connections (a layer-4 balancer, or a layer-7 one with WebSocket upgrade support and idle timeouts raised well above your ping interval) and with each node holding only its own sockets. Do not try to make a node aware of every connection; make it aware of its own and let a shared layer route messages (next section). Sticky routing is unnecessary for new connections and harmful for balance; what you need instead is a reconnection protocol: the client keeps a cursor or last-seen id, reconnects to any node, and asks for what it missed. On Kubernetes this means a Deployment with a generous `terminationGracePeriodSeconds`, a preStop hook that stops accepting and drains, and a pod disruption budget so a rollout moves connections gradually rather than all at once.

## How do you publish a message to the right sockets?

Through a publish/subscribe layer between the producers and the socket nodes. Each node subscribes to the topics its sockets care about (a room, a user, a tenant) and forwards messages to the local sockets on that topic; producers publish to the topic without knowing where the sockets are. The subscription index is the thing that goes hot: a topic with a million subscribers or a user with a thousand devices needs sharding by topic across the pub/sub layer and fan-out in stages. Redis Pub/Sub and NATS handle the common case; Kafka fits when ordering and replay matter more than latency. Keep the message small and the topic key cheap to compute, because the fan-out loop runs for every message times every matching socket.

## How do you survive a node restart?

By planning for it, because it happens every deploy. Drain before you die: stop accepting new connections, tell connected clients to reconnect with a short jittered delay, and only then exit. Make the client's reconnect cheap and idempotent: a resume token, a cursor, an idempotency key on anything it re-sends. Size the rest of the fleet so one node's connections fit elsewhere, and rate-limit reconnections at the balancer so a restart does not become a thundering herd. Test a rolling restart under load, not just a single node, because the second node to restart receives the first node's refugees.

## How do you load test it?

With many client machines, each holding a realistic number of connections, sending and receiving at the rates your product sees, and churning (connecting and disconnecting) at the rate a launch or a live event produces. Run against the real stack: the real balancer, the real pub/sub layer, the real pod disruption budget. Push until something breaks and record which ceiling it was: descriptors, memory, pub/sub throughput, a hot shard. That breaking point and the signal that preceded it are the capacity plan. Then restart a node during the test, because that is the test that fails in production.

Kitecorp runs this work as part of [real-time infrastructure](/services/real-time-infrastructure) engagements.

## How to start

Send a message at https://kitecorp.org/contact or email admin@kitelang.cloud. Pricing follows a discovery call.
