Reconnection (Always Forget)
- •Connections drop silently—TCP FIN may never arrive; don't assume
onclosefires - •Exponential backoff: 1s, 2s, 4s, 8s... cap at 30s—prevents thundering herd on server recovery
- •Add jitter:
delay * (0.5 + Math.random())—prevents synchronized reconnection storms - •Track reconnection state—queue messages during reconnect, replay after
- •Max retry limit then surface error to user—don't retry forever silently
Heartbeats (Critical)
- •Ping/pong frames at protocol level—browser doesn't expose; use application-level ping
- •Send ping every 30s, expect pong within 10s—no pong = connection dead, reconnect
- •Server should ping too—detects dead clients, cleans up resources
- •Idle timeout in proxies (60-120s typical)—heartbeat must be more frequent
- •Don't rely on TCP keepalive—too infrequent, not reliable through proxies
Connection State
- •
readyState: 0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED—check before sending - •Buffer messages while CONNECTING—send after OPEN
- •
bufferedAmountshows queued bytes—pause sending if backpressure building - •Multiple tabs = multiple connections—coordinate via BroadcastChannel or SharedWorker
Authentication
- •Token in URL query:
wss://host/ws?token=xxx—simple but logged in access logs - •First message auth: connect, send token, wait for ack—cleaner but more round trips
- •Cookie auth: works if same origin—but no custom headers in WebSocket
- •Reauthenticate after reconnect—don't assume previous session valid
Scaling Challenges
- •WebSocket connections are stateful—can't round-robin between servers
- •Sticky sessions: route by client ID to same server—or use Redis pub/sub for broadcast
- •Each connection holds memory—thousands of connections = significant RAM
- •Graceful shutdown: send close frame, wait for clients to reconnect elsewhere
Nginx/Proxy Config
code
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s;
- •Without these headers, upgrade fails—connection closes immediately
- •
proxy_read_timeoutmust exceed your ping interval—default 60s too short - •Load balancer health checks: separate HTTP endpoint, not WebSocket
Close Codes
- •1000: normal closure; 1001: going away (page close)
- •1006: abnormal (no close frame received)—usually network issue
- •1008: policy violation; 1011: server error
- •4000-4999: application-defined—use for auth failure, rate limit, etc.
- •Always send close code and reason—helps debugging
Message Handling
- •Text frames for JSON; binary frames for blobs/protobuf—don't mix without framing
- •No guaranteed message boundaries in TCP—but WebSocket handles framing for you
- •Order preserved per connection—messages arrive in send order
- •Large messages may fragment—library handles reassembly; set max message size server-side
Security
- •Validate Origin header on handshake—prevent cross-site WebSocket hijacking
- •Same-origin policy doesn't apply—any page can connect to your WebSocket server
- •Rate limit per connection—one client can flood with messages
- •Validate every message—malicious clients can send anything after connecting
Common Mistakes
- •No heartbeat—connection appears alive but is dead; messages go nowhere
- •Reconnect without backoff—hammers server during outage, prolongs recovery
- •Storing state only in connection—lost on reconnect; persist critical state externally
- •Huge messages—blocks event loop; stream large data via chunking
- •Not handling
bufferedAmount—memory grows unbounded if client slower than server