When a server restarts, many clients can lose their connection together. If they reconnect immediately—or all wait the same fixed delay—the recovery traffic can arrive as another burst.
The WebSocket protocol recommends a randomized initial delay and increasing backoff after abnormal closures. It does not prescribe one delay that is safe for every application. RFC 6455, section 7.2.3 describes the recovery concern.
Start with a Bounded Delay Policy
This pure function gives each failed attempt a delay from an expanding window. The one-second initial ceiling and thirty-second cap are illustrative parameters:
export function reconnectDelay(attempt, random = Math.random) {
if (!Number.isInteger(attempt) || attempt < 0) {
throw new RangeError("attempt must be a non-negative integer");
}
const sample = random();
if (!Number.isFinite(sample) || sample < 0 || sample >= 1) {
throw new RangeError("random must return a value in [0, 1)");
}
const ceiling = Math.min(30_000, 1000 * 2 ** Math.min(attempt, 20));
return Math.floor(sample * ceiling);
}
This is a delay calculation, not a complete reconnecting socket. Jitter spreads attempts statistically; it cannot guarantee a particular recovery duration or connection rate.
Give Reconnection One Owner
A socket error can be followed by close. If both handlers independently schedule a reconnect, one failure can create multiple sockets. Route the transition through one scheduler and allow at most one pending reconnect timer.
A useful lifecycle has these states:
connecting → open → disconnected → waiting → connecting
└──────── failure ───────────────┘
any state → stopped
Store the active socket identity. Ignore late callbacks from an older socket after it has been replaced. On a deliberate stop, cancel the pending timer, remove owned listeners and close the current socket.
Do not reset the attempt count merely because open fired. A server that repeatedly accepts and immediately drops connections can otherwise keep every client at the shortest retry delay. Reset after an application-defined stable period or successful readiness exchange.
Define Which Closures Are Retryable
wasClean describes aspects of connection closure; it is not your application’s retry policy. A deliberate logout should stop. A temporary service restart may be retryable. An authorization failure may require reauthentication rather than another identical attempt.
Keep the policy explicit and make the interface honest about its state. A disconnected UI should not continue presenting stale data as live.
Decide what happens to unsent messages. An unbounded in-memory queue can grow during an outage, while silently discarding writes can mislead the user. Operations that are retried need identifiers and server-side duplicate handling appropriate to their semantics.
Protect the Upgrade Boundary
An ordinary Express route limiter does not automatically cover every HTTP upgrade path. With the Node ws library, authentication and admission checks can run in the server’s upgrade handler before handleUpgrade. The ws examples show that integration point.
Apply capacity and rate controls where connections enter the system, considering shared networks and per-account limits as well as IP addresses. Established sockets still consume resources; they are not free merely because the handshake completed.
Restore the Application, Not Just the Transport
For a dashboard, a fresh authoritative snapshot may be enough. For ordered notifications, reconnect with a sequence cursor and replay within the server’s retention window. If the cursor is too old, define a full resynchronization path.
Test simultaneous disconnects, failed handshakes, rapid open/close cycles, navigation away and cancellation during backoff. Verify that each client has at most one live attempt and that the recovered interface reflects current server state.
These checks establish recovery behavior. They do not establish a production capacity figure without a measured workload.

Sardar Mukhshaf
Full-Stack & DevOps Engineer
I build scalable web applications, secure backend systems, and production-ready cloud infrastructure. My engineering focus spans interface design, API architecture, deployment pipelines, and observability. Follow for more deep dives into system architecture, performance engineering, and emerging technologies.
More from the practice
Keep the useful ideas close.
Follow new articles with your preferred RSS reader.
Open the RSS feed ↗


