BRYME TECH
SEPTEMBER 2026 · THE TOOL DESKPractical technology. No theatre.
THE BRYME

Practical guide · verified against the real thing

WebSockets not connecting: the debugging path from handshake to heartbeat

In one line: When a socket won't open, dies mid-stream or silently stalls: read the handshake first, then the close code, then the network between you — and add the heartbeat you were missing.

A WebSocket is a plain HTTP conversation that upgrades into a persistent two-way connection (that's the whole trick, formalised in RFC 6455). Every failure therefore lives in one of three phases: the handshake (the upgrade never completes), the stream (connected, then messages fail), or the silence (no errors, just a dead connection nobody noticed). Diagnose by phase.

Phase one: the handshake that never completes

Open the browser's developer tools, Network tab, filter WS, and reload: the socket attempt appears as its own row. Failure modes and what they mean: HTTP 404/405 on the socket request — wrong path or the server doesn't accept upgrades there; 403 — the server's origin check rejected your page (WebSockets are not bound by the browser's same-origin policy; the server enforces its own allowlist, and your localhost dev origin is the classic rejection); 401 — auth missing: unlike regular requests, a WebSocket's credentials ride the initial request or the URL, so a token that works on the API may never reach the socket layer; connection reset / mixed content — an ws:// socket from an https:// page is blocked by the browser, full stop; the page must speak wss://. And the one that isn't your fault: some corporate proxies and networks simply refuse the upgrade — testing from another network (phone hotspot) settles "is it them or the firewall" in one minute.

Phase two: connected, then gone — read the close code

Sockets close with a numeric code, visible in the console and the WS row's timeline, and the numbers are the message: 1000 normal goodbye; 1006 the useful one — the connection died abnormally without a proper close (network drop, proxy timeout, process crash); 1011 the server hit an error while handling your session. 1006 after a few quiet minutes is almost always an idle timeout somewhere on the path: which brings the fix to you, not them.

Phase three: the heartbeat you were missing

Production sockets that must survive real networks implement a heartbeat: the client pings every N seconds, the server pongs, and either side that hears nothing for a timeout window closes and reconnects. Without it, half-open connections sit silently dead on both ends — the app shows "connected" while nothing flows. The same reconnect needs exponential backoff with jitter (1s, 2s, 4s…, plus randomness so a thousand clients don't stampede the server at second one). If your socket library offers ping/pong and auto-reconnect settings, the debugging in this paragraph is the reason they exist — set them deliberately rather than accepting defaults you can't state out loud.

Logging that actually helps

Log the phase transitions, not a message flood: opened, closed (with code), reconnect attempt number, and every error object whole. The pattern "opens fine, 1006 every four minutes behind one specific proxy" falls out of four log lines and points at the idle timeout immediately. WebSockets share the plain-HTTP road all the way to the upgrade — which means the generic networking triage (DNS, resolution, reachability) still applies as phase zero, and the server-side cousins of these failures live in the deployment-failure field notes.

Sources

Next

Related on this desk.