WebSockets provide a bidirectional, full-duplex communication channel between client and server. Once established, both sides can send messages independently. The protocol upgrades from HTTP to a long-lived TCP connection.
This page is about the patterns that make WebSocket applications work in production — not the protocol details, but the operational concerns.
Client → HTTP GET with Upgrade headers → Server
Server → HTTP 101 Switching Protocols → Client
Connection upgraded to WebSocket
... message exchange ...
Either side → close frame → Other side
The HTTP-to-WebSocket upgrade requires:
Upgrade: websocketConnection: UpgradeSec-WebSocket-Key (handshake security)Most clients/servers handle this automatically.
WebSocket authentication is done during the upgrade. Several patterns:
If your app already uses cookies for auth, they apply to the upgrade request. Simple.
new WebSocket('wss://api/ws', ['token.your-jwt-here'])
The token is in the Sec-WebSocket-Protocol header. Cleaner than URL parameters.
wss://api/ws?token=...
Works but logged everywhere; not great for sensitive tokens.
Connect first, then send an auth message:
{ "type": "auth", "token": "..." }
Server keeps the connection open only after successful auth. Useful for protocols where the upgrade itself is unauthenticated.
What happens when the server sends faster than the client consumes? Or vice versa?
The TCP layer provides some backpressure (the OS buffers fill; sends block). Above that:
For real-time but loss-tolerant streams (cursor positions, presence), drop on overflow. For streams where every message matters, slow down or disconnect.
Long-idle WebSocket connections may be closed by NAT, proxies, or network equipment. Heartbeats keep the connection alive and detect failures.
Client → ping (every 30s) → Server
Server → pong → Client
If a pong is missed, reconnect. Specific timeouts depend on your infrastructure.
WebSocket has built-in ping/pong frames; some libraries handle this automatically.
Always assume disconnects happen. Reconnection logic:
The "resync state" step is application-specific. Some patterns:
Broadcasting to many connected clients. The challenge: each connection holds a TCP socket; servers can hold thousands but not millions.
For very high fan-out:
Socket.IO adds features (auto-reconnect, fallbacks to long-polling, rooms) at the cost of being its own protocol on top of WebSocket. Use vanilla WebSocket unless Socket.IO's features are specifically needed.
If clients connect to one server and you have multiple servers, the connection is server-pinned. Load balancers need sticky sessions or the connection breaks.
For stateless WebSocket usage (using a pub/sub backend for shared state), any server can handle any connection — sticky sessions optional.
Each connection consumes memory. Estimate ~10-50 KB per idle connection plus per-message buffers. 100K concurrent connections = 1-5 GB just in baseline memory.
Each TCP connection consumes a file descriptor. Tune ulimit -n and OS limits accordingly.