WebSockets Real-Time Apps 2026: The Complete Problem-Solving Guide
By Huzi
File #540 · 4 min read
A single Node.js process happily holds tens of thousands of open sockets — until it cannot, and you add a second one. Then the statefulness you forgot about bites: User A is connected to Node 1, User B to Node 2, and a message A sends never arrives at B.
The quick answer: Everything about running WebSockets in production is the story of that problem and its side effects. The fixes stack in order — sticky sessions to start, the Redis adapter as the real answer, then backpressure caps, 30-second heartbeats and replay-on-reconnect.
Sticky sessions: the quick fix
Pin each client to one backend so its socket state stays where it was created. In Nginx:
upstream ws_nodes {
ip_hash;
server 10.0.1.10:3001;
server 10.0.1.11:3001;
}
It works in an afternoon, but it is a half-answer: the node that dies takes all its sticky users down with it, and hot nodes form whenever the hash lands unevenly. Treat sticky sessions as scaffolding, not architecture.
The Redis adapter: the real answer
Socket.io's Redis adapter turns the fleet into one logical server — an emit on any node publishes through Redis, and every node delivers to its own local sockets:
import { createAdapter } from "@socket.io/redis-adapter";
import { createClient } from "redis";
const pub = createClient({ url: "redis://localhost:6379" });
const sub = pub.duplicate();
await Promise.all([pub.connect(), sub.connect()]);
io.adapter(createAdapter(pub, sub));
Rooms now span every node, and adding capacity becomes a deploy instead of a redesign.
Backpressure: the cost nobody budgets for
A slow client is not a harmless client. A phone on a weak link that stops reading its window fills your send buffer on the server, and naive loops keep writing until memory pressure takes the whole node down.
Watch ws.bufferedAmount before sending; cap a per-connection outbound queue; when the cap is hit, drop the message, close the socket, or degrade — a chat can shed typing indicators before it sheds actual messages.
Heartbeats and honest reconnection
NATs drop idle connections after a minute or two, so ping every 30 seconds and terminate anything that does not pong — otherwise your presence counts are fiction, full of dead laptops in sleep mode.
On the client, reconnect with exponential backoff — one second doubling to a cap, plus jitter — and re-authenticate on every reconnect, because the token that opened the original socket may have expired mid-outage.
Remember also that pub/sub is fire-and-forget: if a node is restarting, the message is gone. Where delivery matters, persist the message before fanning out and replay the tail on reconnect — the difference between a chat that feels real and one that silently loses the sentence written during the deploy.
Persist-before-fan-out is also how a travel file should be built — the confirmation saved before the family group chat hears anything — and it is the discipline behind every booking the HTG Travels desk issues: the paper exists before the message does.
| The problem | The fix |
|---|---|
| Messages lost across nodes | The Socket.io Redis adapter — one logical server |
| Sticky deaths and hot nodes | Treat ip_hash sessions as scaffolding, not architecture |
| Slow clients filling the buffer | Watch ws.bufferedAmount, cap the outbound queue, degrade gracefully |
| Dead laptops in presence counts | Ping every 30 seconds, terminate the silent |
| Expired tokens on reconnect | Exponential backoff with jitter, re-authenticate every time |
| Messages lost during restarts | Persist before fan-out, replay the tail on reconnect |
Pro tip: Decide the backpressure policy in code review, not at 3 a.m. — agree in advance what gets dropped, closed or degraded when a client stops reading, because a chat that sheds typing indicators before messages survives the night.
Presence indicators feel decorative until a family group chat in Gaza shows one member last seen days ago and every grey tick becomes a held breath — may the missing come back online.
When an airline cancels the 2 a.m. flight and a WhatsApp group of passengers starts buzzing, our desk is the other kind of always-on: we rebook, reroute and answer at hours no call centre admits to. That is HTG Travels — the connection that holds when everyone else's drops. Save +92 317 5054959 before you need it.




