WebSockets are easy with one server and hard with many. The moment you add a second instance, a message sent to a client on server A can't reach a client on server B. Here's how to solve it.
The Multi-Server Problem
Clients hold persistent connections to whichever instance a load balancer routed them to. Broadcasting requires every instance to know about every message — that's what a message broker provides.
Fan-Out With Redis Pub/Sub
// Every server subscribes to a channel and rebroadcasts to its own clients
redisSub.subscribe("chat");
redisSub.on("message", (_, payload) => {
io.emit("message", JSON.parse(payload)); // to local sockets
});
// Publishing a message reaches all servers
function broadcast(msg) {
redisPub.publish("chat", JSON.stringify(msg));
}Sticky Sessions and Health
- Enable sticky sessions on the load balancer so a connection stays on one instance.
- Send heartbeats/pings to detect and clean up dead connections.
- Cap connections per instance and scale horizontally behind the broker.
Don't Want to Run It Yourself?
Managed services (Pusher, Ably, Supabase Realtime) handle fan-out and scaling for you — a smart choice until real-time is core to your product.
