WebSockets vs HTTP: When to Use Each

websockets http networking real-time api-design

Imagine two ways to get information from a friend. You could send letters back and forth — you write a question, post it, wait for a reply, then write another question. Or you could pick up the phone and have a real conversation, where both of you can speak whenever you want without waiting for the other person to “ask first.” HTTP works like the letters. WebSockets work like the phone call.

That analogy captures the core of the WebSockets vs HTTP decision. For most of the web — loading pages, fetching data from APIs, submitting forms — the letter model works perfectly well. But some applications need a live, open connection where the server can send you data the instant something happens: a new chat message arrives, a stock price changes, a live score updates. That is where WebSockets earn their place.

This guide explains how both protocols actually work, what makes them different, and how to choose between them — starting from the basics.

What Are WebSockets and HTTP?

When you visit any website, your browser and the server exchange data using HTTP — the Hypertext Transfer Protocol. Every interaction follows the same pattern: your browser sends a request (“give me this page”), the server sends back a response (“here it is”), and the exchange is complete. If something changes on the server a moment later — a new message arrives, a score updates — the server has no way to tell you. You have to ask again.

WebSockets change that model. A WebSocket connection starts as an ordinary HTTP request, but this one asks to switch protocols. If the server agrees, the connection upgrades from HTTP into a persistent, two-way channel. Once that channel is open, both sides can send messages at any time — the server can push data without waiting for a request, and the client can send data back just as freely.

The technical terms: HTTP is stateless and request-response — every exchange starts with the client, ends with the server’s reply, and is then forgotten. WebSockets are persistent and full-duplex — one connection stays open, and either side can speak at any time. Most web applications only need HTTP. WebSockets are for the specific cases where HTTP’s ask-and-answer model becomes a bottleneck.

How HTTP Works

HTTP handles the vast majority of web traffic, and for good reason: it matches the way most web applications work. The client decides what it wants, asks for it, receives it, and the job is done.

The request-response cycle. Every HTTP interaction follows the same steps: the client opens (or reuses) a TCP connection, sends a request that includes a method (GET, POST, PUT, DELETE), a URL, and headers, the server sends a response with a status code and optional body, and the connection either closes or waits for the next request. Each operation is independent of every other.

const response = await fetch('https://api.example.com/prices', {
  method: 'GET',
  headers: { 'Authorization': 'Bearer ' + token }
});
const prices = await response.json();

This is clean and easy to follow: one request, one response, done. If prices change a minute later, the client needs to ask again — the server has no way to volunteer that update.

Statelessness is a feature, not a limitation. HTTP’s stateless design — where the server remembers nothing between requests — might sound like a weakness. In practice it is one of HTTP’s biggest strengths. Because any request is self-contained, any server in your infrastructure can handle it. Load balancers distribute requests freely without needing sticky sessions. CDNs cache GET responses at the edge and serve millions of clients without touching the origin server. Retrying a failed read operation is safe because each request carries everything the server needs. That simplicity is why HTTP scales to billions of requests per day with comparatively straightforward infrastructure.

Modern versions of HTTP pushed the model further. HTTP/2 multiplexes multiple request-response pairs over one TCP connection and compresses headers — significant savings for APIs with many small responses. HTTP/3 uses QUIC (UDP-based) to eliminate head-of-line blocking under packet loss. The entire ecosystem — CDNs, load balancers, reverse proxies, monitoring tools — is built around HTTP. Every intermediary between a client and server understands it natively.

The HTTP Status Codes reference explains what each response code means and when to use it. The MDN HTTP overview is the authoritative starting point for the protocol itself.

How WebSockets Work

The WebSocket connection begins with a regular HTTP GET — but with a special header that asks to switch protocols:

GET /live HTTP/1.1
Host: api.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

The server responds with 101 Switching Protocols. From that moment, the TCP connection no longer speaks HTTP — it speaks the WebSocket protocol (RFC 6455), and either side can send a message at any time.

Three things change after that handshake:

  • The connection stays open. The overhead of establishing a connection happens once, not per message.
  • Both sides can initiate. The server pushes data without waiting for a request. The client sends whenever it wants. This is the defining capability that separates WebSockets from HTTP.
  • Messages are lean. Each WebSocket message carries only 2–10 bytes of framing overhead, compared to hundreds of bytes of headers on every HTTP request.

A WebSocket client in JavaScript:

const ws = new WebSocket('wss://api.example.com/live');

ws.onopen = () => {
  ws.send(JSON.stringify({ action: 'subscribe', channel: 'BTC-USD' }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  updatePriceTicker(data.price, data.timestamp);
};

ws.onclose = (event) => {
  console.log('Disconnected:', event.code, event.reason);
  scheduleReconnect();
};

ws.onerror = (error) => {
  console.error('WebSocket error:', error);
};

Notice the onclose handler calls scheduleReconnect(). That is not optional — WebSocket connections break. Network changes, server restarts, NAT router idle timeouts — all can silently close the connection. Every production WebSocket client needs reconnection logic with exponential backoff: start at 1 second, cap at 30, add random jitter to prevent every client from reconnecting at the same moment after a server restart. The WebSocket API on MDN documents the full event model and the built-in ping/pong frames that detect broken connections before the application notices.

A minimal Node.js server using the ws library:

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws, req) => {
  const userId = authenticateRequest(req);
  if (!userId) {
    ws.close(4001, 'Unauthorized');
    return;
  }

  ws.on('message', (message) => {
    const data = JSON.parse(message);
    if (data.action === 'subscribe') registerSubscription(ws, data.channel);
  });

  ws.on('close', () => cleanupSubscriptions(ws));
});

Server-side resource cost. Each open WebSocket connection holds a file descriptor and memory on the server — roughly 40 KB per connection in Node.js. Ten thousand concurrent connections means about 400 MB just for socket state, before any application logic. Horizontal scaling also becomes more complex: because each connection is pinned to one server instance, routing server-pushed events to the right instance requires a shared message bus (Redis Pub/Sub is the standard choice). These are real, manageable costs — but they are costs that HTTP-only systems do not carry.

WebSockets vs HTTP: Side-by-Side

DimensionHTTPWebSocket
Connection modelRequest-response, statelessPersistent, bidirectional
Who can send firstClient onlyEither side
Per-message overheadFull headers on every request2–10 byte frame after handshake
CachingBuilt-in (CDN, browser, proxy)No caching
Load balancingStateless — any server handles any requestSession-aware — connection pinned to one server
Firewall/proxy supportUniversalRequires Upgrade header passthrough
AuthenticationPer-request headersHandshake only — token expiry needs extra handling
Browser supportUniversalAll modern browsers
Best forREST APIs, page loads, file I/OChat, live data, collaborative tools, gaming

The authentication row is easy to overlook. With HTTP, every request carries its own Authorization header. Token expiry is handled automatically — the next request fails with a 401 and the client refreshes the token. With WebSockets, auth happens at the handshake only. If a user’s JWT expires mid-session, the server must detect it and close the connection, or the application must proactively refresh the token over the open channel. The JWT guide covers how token-based auth works and where the WebSocket session boundary creates a different problem from standard HTTP flows.

Proxy configuration is the most common deployment surprise. Older proxies and some CDN setups strip the Upgrade header, which causes the WebSocket handshake to fail silently. If you run behind Nginx, you need explicit WebSocket passthrough configuration:

location /ws {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 3600;
}

Without proxy_http_version 1.1 and the Upgrade header forwarding, the WebSocket client receives a 200 or 400 from the proxy instead of the expected 101 Switching Protocols. The proxy_read_timeout line prevents Nginx from closing idle-but-alive connections at its default 60-second timeout.

When HTTP Is the Right Choice

HTTP is the right default for most applications. It should be your starting point, and you should only move to WebSockets when you have a concrete reason.

REST APIs and CRUD operations. Fetching data, creating records, updating state — these are client-initiated operations that fit HTTP’s request-response model exactly. CDNs can cache read responses and serve thousands of clients without touching the origin server. No persistent connection is needed.

File uploads and downloads. HTTP multipart uploads support resumable transfers through Range headers and integrate cleanly with CDN offloading for large files. Binary data is technically possible over WebSockets, but the entire upload management ecosystem — progress tracking, retry, CDN offload — exists for HTTP, not WebSockets.

Authentication flows. Login, token refresh, logout — infrequent, client-initiated, and benefiting from HTTP’s stateless request isolation. There is no reason to add a persistent connection for operations that happen a handful of times per session.

Near-real-time with polling. Polling every 5–30 seconds handles many notification and dashboard use cases without WebSocket complexity. A deployment status page, a background job completion check, an admin dashboard refreshing every 30 seconds — all work fine with HTTP polling. If the update frequency is low enough, polling is simpler and cheaper than WebSocket infrastructure. Use the curl command guide to test your HTTP endpoints and inspect headers during development.

When WebSockets Are Worth It

WebSockets justify their operational overhead when two conditions are both true: the server needs to initiate data delivery (not just respond to client requests), and that needs to happen frequently — below a few seconds between updates.

Chat applications. Messages arrive from other users — the server initiates delivery to each recipient. HTTP polling works, but it adds latency equal to the polling interval and creates wasteful traffic even during quiet periods. A busy group chat is the textbook WebSocket case: the server pushes each message to all subscribers the moment it arrives.

Live financial data. Price tickers, order book updates, trade confirmations. These arrive at 10–100 updates per second per symbol. Polling at that rate creates enormous backend load and still adds latency equal to the polling interval. WebSockets deliver each update the instant it is ready.

Collaborative editing. Google Docs-style concurrent editing requires every participant’s changes to reach all others within milliseconds. Sub-second latency makes HTTP polling impractical — you cannot poll fast enough without generating more load than the content justifies.

Multiplayer games. Player positions, game events, state changes — high-frequency, bidirectional, low-latency. HTTP’s request-response contract simply does not fit this model. The client cannot ask for every event fast enough, and the server cannot wait to be asked.

Live dashboards with sub-second freshness. One hundred concurrent viewers polling every second is 100 requests per second against your backend. WebSockets reduce that load to the actual number of server-initiated events — typically far fewer than one per viewer per second.

A rough threshold. If the server needs to push data more than once every 5 seconds, or if sub-second latency for server-initiated events matters, WebSockets are the right tool.

Server-Sent Events: A Simpler Middle Ground

Before reaching for WebSocket infrastructure, consider Server-Sent Events (SSE). SSE is HTTP-based — no Upgrade handshake required, works through existing CDN and proxy infrastructure, and auto-reconnects on drop. The limitation is that SSE is one-directional: the server can send to the client, but the client cannot send messages back. For notification streams, news feeds, log tails, and status updates, that is all you need.

const evtSource = new EventSource('/api/live-status');

evtSource.onmessage = (event) => {
  const update = JSON.parse(event.data);
  renderStatus(update);
};
// EventSource reconnects automatically on drop — no error handler needed

If the client does not need to send data back to the server, SSE gives you server push over plain HTTP with far simpler deployment. Evaluate it before adding WebSocket infrastructure.

Scaling WebSocket Servers

HTTP and WebSocket servers scale differently, and understanding that difference before you commit to WebSockets matters.

An HTTP server measures load in requests per second. Each request arrives, gets processed, and returns a response. State is external — in the database or cache. Scaling means adding servers behind a load balancer, with no coordination between instances needed.

A WebSocket server measures load in concurrent connections. Every connected client holds a persistent file descriptor and memory allocation. Rough concurrency limits per server process:

  • Node.js: 10,000–50,000 concurrent connections (memory-bound, ~40 KB/connection)
  • Go: 100,000–500,000+ (goroutines are cheap)
  • Rust (Tokio): similar to Go, often higher under memory pressure

Horizontal scaling requires solving message routing. If server A holds the WebSocket connection for user X, and a message for user X is processed by server B, server B must forward that message to server A so it can push to the socket. The standard solution is Redis Pub/Sub: each server subscribes to channels keyed by user or room ID, and publishes events to those channels. This is infrastructure that HTTP-only systems never need — factor it into the decision before committing.

Common Pitfalls

Not handling disconnects. WebSocket connections break silently. Without an onclose handler that triggers reconnection, users are left staring at stale data with no indication anything went wrong. Implement reconnect with exponential backoff — start at 1 second, cap at 30, add random jitter to prevent a thundering herd when the server restarts.

Skipping heartbeats. TCP connections can appear open from one side after the other has gone away — especially through NAT devices. Send periodic ping frames using the WebSocket protocol’s built-in ping/pong mechanism. If no pong arrives within the timeout, close and reconnect.

Putting auth tokens in the WebSocket URL. URLs are logged by servers, proxies, and browser history. wss://api.example.com/ws?token=SECRET exposes the token in plaintext. Send credentials in the first message after the connection opens, or use a short-lived session token the server validates on connect and discards.

Under-provisioning for connection count. An HTTP server tuned for request throughput will not automatically handle tens of thousands of persistent WebSocket connections. Capacity planning for WebSocket infrastructure uses connection count as the primary metric, not requests per second. Run load tests before production — connection-scale failures tend to be sudden rather than gradual.

Using WebSockets for infrequent updates. A settings form that saves every few minutes, a dashboard refreshing every 30 seconds — neither needs WebSockets. Each additional protocol in your stack adds monitoring gaps, configuration surface, and failure modes. Match the tool to the actual requirement.

Frequently Asked Questions

What is the main difference between WebSockets and HTTP?

HTTP is a request-response protocol: the client sends a request, the server responds, and the exchange is complete. The server can only send data when the client asks for it. WebSockets open a persistent, two-way channel: either side can send messages at any time without a new request. Think of HTTP as exchanging letters and WebSockets as a phone call that stays open for the duration of the session.

Does HTTP/2 make WebSockets obsolete?

No. HTTP/2 adds multiplexing and header compression, but the fundamental model is still request-response — the client initiates every exchange. HTTP/2 server push was deprecated by Chrome in 2022 and Edge in 2023 due to implementation complexity and limited real-world benefit. RFC 8441 defines WebSocket bootstrapping over HTTP/2 connections as an efficiency optimization, not a replacement for the WebSocket protocol. WebSockets remain the standard for true bidirectional, low-latency communication.

When should I use WebSockets instead of HTTP polling?

When the server needs to push data frequently — more than once every 5 seconds — or when sub-second latency for server-initiated events matters. Polling every 30 seconds works fine for a deployment status page. Polling every 100 milliseconds for a live price ticker creates enormous backend load and still adds latency equal to the polling interval. Below about 5 seconds per server-initiated update, WebSockets outperform polling on both network efficiency and user experience.

Is wss:// required in production?

Yes. wss:// is WebSocket over TLS — the same relationship HTTPS has to HTTP. Plain ws:// sends all data unencrypted. Modern browsers block unencrypted WebSocket connections from HTTPS pages under the mixed content policy, so ws:// would fail in most production browser contexts regardless. Treat wss:// as mandatory: it protects session tokens and message content from any network intermediary, even when your data appears non-sensitive.

How do I handle authentication over a long-lived WebSocket connection?

With HTTP, every request carries its own Authorization header, so token expiry is handled naturally — the next request fails with a 401 and the client refreshes the token. With WebSockets, authentication happens only at the handshake. After that, the connection is open and the server cannot re-check credentials per-message without application-level logic. The server should track token expiry and close the connection when a token expires, or the application should send a token-refresh message over the open channel. The JWT guide covers the token lifecycle and explains how session boundaries differ between HTTP and WebSocket connections.

Conclusion

WebSockets vs HTTP is a protocol selection problem with a clear answer for most cases. HTTP is the right default — stateless, cacheable, and supported by every piece of infrastructure between the client and server. WebSockets earn their operational overhead when the server must push data frequently, at low latency, without waiting for a client request to trigger it.

Start with HTTP. Move to WebSockets when polling creates a real problem — the boundary is usually around one server-initiated update every five seconds. And before going straight to WebSockets, check whether Server-Sent Events satisfy the requirement: if the client does not need to send data back, SSE delivers server push over plain HTTP with far simpler deployment.

For testing what your HTTP endpoints actually send and receive, the curl command guide covers request headers, authentication patterns, and response inspection. For the full rules on URL encoding and query parameters — which apply to both HTTP requests and the WebSocket handshake URL — the guide covers encoding edge cases and practical browser behavior.