Network & Real-Time

What Is a WebSocket Connection? How It Works

WebSocket is a protocol that enables real-time, two-way communication between a browser and a server over a single, persistent TCP connection. Unlike HTTP where every interaction requires a new request, WebSocket lets both sides push messages to each other at any time.

The Problem WebSocket Solves

Traditional HTTP is request-response: the browser asks, the server answers, the connection closes. This works well for loading pages and fetching data, but it's terrible for real-time features.

Before WebSocket, developers used workarounds like polling (requesting new data every few seconds) and long-polling (keeping a request open until new data arrives). Both waste resources and add latency.

TechniqueHow It WorksLatencyServer Load
PollingRequest every N secondsN seconds averageHigh (many requests)
Long-pollingRequest, wait, respond, repeat~0ms (when ready)Medium (1 connection/client)
Server-Sent EventsOne-way server → browser push~0msLow (read-only)
WebSocketPersistent two-way channel~0msLow (1 connection/client)

How WebSocket Works

Step 1: The HTTP Upgrade Handshake

A WebSocket connection starts as an HTTP request. The browser sends an upgrade request:

// Browser → Server (HTTP request)
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

The server responds with 101 Switching Protocols:

// Server → Browser
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Step 2: Persistent Bidirectional Channel

After the handshake, the TCP connection stays open and switches to the WebSocket protocol. Both sides can now send "frames" (messages) to each other at any time:

// Browser sends message
Browser → Server: { type: 'message', text: 'Hello' }
// Server sends update to ALL connected clients
Server → Browser 1: { type: 'message', user: 'Alice', text: 'Hello' }
Server → Browser 2: { type: 'message', user: 'Alice', text: 'Hello' }
Server → Browser 3: { type: 'message', user: 'Alice', text: 'Hello' }

Step 3: Connection Lifecycle

CONNECTINGHandshake in progress, not yet ready
OPENConnection established, messages can be sent/received
CLOSINGClose handshake initiated (either side can start it)
CLOSEDConnection terminated — reconnect if needed

WebSocket vs HTTP

PropertyHTTPWebSocket
CommunicationRequest → Response (one direction)Full duplex (both directions simultaneously)
ConnectionNew connection per request (HTTP/1.1) or multiplexed (HTTP/2)Single persistent connection
Who initiates messagesOnly client can initiateEither side can send at any time
Header overheadFull headers on every request (~500-800 bytes)Tiny frames after handshake (~2-6 bytes header)
LatencyNew connection overhead per requestNear-zero (connection already open)
StateStateless by defaultStateful — connection persists
ProtocolHTTP/1.1, HTTP/2, HTTP/3ws:// or wss:// (after HTTP upgrade)
Port80 (http) / 443 (https)80 (ws) / 443 (wss) — same ports
CachingYes (GET responses)No — live stream data
Load balancersSimple (any server)Sticky sessions required (or pub/sub)

JavaScript: Basic WebSocket Client

// Browser — basic WebSocket client
const ws = new WebSocket('wss://api.example.com/chat');
// Connection opened
ws.addEventListener('open', () => {
console.log('Connected');
ws.send(JSON.stringify({ type: 'join', room: 'general' }));
});
// Message received from server
ws.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
});
// Error handling
ws.addEventListener('close', (event) => {
console.log('Disconnected, code:', event.code);
// Reconnect after 3 seconds
setTimeout(connect, 3000);
});
// Send a message
ws.send(JSON.stringify({ type: 'message', text: 'Hello!' }));
// Close gracefully
ws.close(1000, 'Done');

Node.js Server (with ws library)

// npm install ws
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws) => {
ws.on('message', (data) => {
const msg = JSON.parse(data.toString());
// Broadcast to all connected clients
wss.clients.forEach((client) => {
if (client.readyState === client.OPEN) {
client.send(JSON.stringify(msg));
}
});
});
});

When to Use WebSocket

Use WebSocket for...
  • Real-time chat and messaging apps
  • Live collaborative editing (docs, whiteboards)
  • Multiplayer games (position, state sync)
  • Live sports scores, stock tickers, crypto prices
  • Real-time dashboards and monitoring
  • Push notifications with immediate delivery
Use HTTP instead for...
  • REST APIs and data fetching (one-shot operations)
  • File uploads and downloads
  • Updates less frequent than every 30 seconds
  • Cacheable responses (GET requests)
  • One-way streaming (SSE is simpler)
  • Simple request-response patterns

Common WebSocket Issues

Connections dropping unexpectedly

Implement ping/pong heartbeats. The ws library lets you configure pingInterval. Many proxies and load balancers close idle connections after 60 seconds. Send a ping every 30 seconds to keep the connection alive.

Load balancer issues (connections stuck to wrong server)

WebSocket requires sticky sessions (all messages from one client go to the same server). Alternatively, use Redis pub/sub or a message broker so any server can relay messages to any client. Nginx supports WebSocket proxy with proxy_pass and Upgrade headers.

Memory leaks from unclosed connections

Always listen for the "close" event and clean up client-side state. On the server, track connected clients in a Set and delete them on disconnect. Never store WebSocket connections in global state without cleanup.

wss:// failing in production

wss:// (secure WebSocket) requires TLS. Your reverse proxy (Nginx, Caddy) must be configured to forward WebSocket upgrades correctly. For Nginx: include proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";

Frequently Asked Questions

What is the difference between WebSocket and Socket.IO?

WebSocket is a standard browser API and protocol (RFC 6455). Socket.IO is a JavaScript library that uses WebSocket internally but adds: automatic reconnection, room/namespace support, fallback to HTTP long-polling when WebSocket is blocked, and its own message encoding. Socket.IO clients and servers are tightly coupled — you can't use a plain WebSocket client with a Socket.IO server. For new projects, raw WebSocket with a small library like ws (Node.js) is often sufficient.

Does WebSocket work behind firewalls and proxies?

Usually yes, because WebSocket uses the same ports as HTTP (80) and HTTPS (443). Most corporate firewalls allow these. However, some older proxy servers may not support the HTTP Upgrade mechanism and will drop WebSocket connections. wss:// (secure WebSocket, port 443) has the highest firewall compatibility since HTTPS traffic is rarely blocked.

How many WebSocket connections can a server handle?

A single server can typically handle tens of thousands of concurrent WebSocket connections. Each connection consumes a file descriptor and some memory (~10-50KB per connection depending on your implementation). Node.js with the ws library can handle 100,000+ concurrent connections on a single process with proper tuning. For millions of connections, use horizontal scaling with a pub/sub system like Redis to distribute messages across servers.

Is WebSocket supported in all browsers?

Yes. WebSocket has been supported in all major browsers since 2012 (IE 10+, Chrome 16+, Firefox 11+, Safari 6+). As of 2026, browser support is effectively universal. The WebSocket API is available as a global in both browsers and Node.js (version 22+ includes WebSocket natively).

Key Takeaways

  • WebSocket is a persistent, full-duplex protocol — both sides can send messages at any time.
  • It starts as an HTTP request then upgrades to a persistent TCP connection.
  • Use it for real-time features: chat, live data, collaborative editing, games.
  • Use HTTP (REST) for data fetching, file transfers, and anything that doesn't need live updates.
  • Implement heartbeats and reconnect logic — connections can drop for network reasons.
  • wss:// (port 443) has the best firewall compatibility — always use it in production.

Related Resources