WebSocket Protocol: Real-Time Bidirectional Communication
Intermediate
The modern web demands real-time interactivity. Users expect chat messages to appear instantly, stock prices to update without refreshing the page, and collaborative documents to reflect edits from every participant in real time. Traditional HTTP, designed around a strict request-response model, was never built for these use cases. The WebSocket protocol emerged to solve this fundamental mismatch, establishing persistent, full-duplex communication channels over a single TCP connection. Defined in RFC 6455 and standardized by the IETF in December 2011, WebSocket has become the backbone of real-time web communication.
What Is the WebSocket Protocol and Why Was It Created?
WebSocket is a communication protocol that provides full-duplex, bidirectional data exchange between a client and a server over a single, long-lived TCP connection. Unlike HTTP, where a client must send a request to receive a response, WebSocket allows both endpoints to send messages independently and simultaneously at any time after the connection is established.
The protocol originated from work on the HTML5 specification. In 2008, Ian Hickson, then editor of the HTML5 spec at the WHATWG (Web Hypertext Application Technology Working Group), proposed a mechanism called "TCPConnection" to enable browser-based applications to open raw TCP sockets. This initial proposal evolved significantly through community feedback and security reviews. By 2009, the concept had been refined into a distinct protocol with its own upgrade handshake mechanism, and the IETF's HyBi (Hypertext Bidirectional) working group took over formal standardization.
The protocol went through 17 draft iterations before being published as RFC 6455 in December 2011. The companion JavaScript API was standardized separately by the W3C. Together, they replaced a collection of workarounds that developers had been using for years to simulate real-time communication: HTTP long polling, where the server holds a request open until data is available; HTTP streaming, where the server sends data in chunks over a kept-alive connection; and various Flash-based socket bridges. Each of these "Comet" techniques introduced significant overhead, complexity, and scalability challenges that WebSocket was designed to eliminate.
How Does the WebSocket Handshake Work?
Every WebSocket connection begins with an HTTP/1.1 Upgrade handshake. This design was a deliberate choice: by bootstrapping on HTTP, WebSocket connections can traverse existing HTTP infrastructure including proxies, load balancers, and firewalls without requiring new ports or protocols at the network level.
| Metric | Edge Processing | Origin Processing | Improvement |
|---|---|---|---|
| Latency (p50) | 8–15 ms | 120–300 ms | 15–20x faster |
| Cache Hit Ratio | 85–95% | N/A | Reduces origin load |
| TLS Handshake | 1–5 ms (nearby PoP) | 50–150 ms | 10–30x faster |
| Bandwidth Cost | $0.01–0.04/GB | $0.05–0.12/GB | 3–5x cheaper |
| Availability | 99.99% (multi-PoP) | 99.9% (single DC) | 10x fewer outages |
The client initiates the handshake by sending a standard HTTP GET request with specific upgrade headers:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
The Sec-WebSocket-Key header contains a base64-encoded 16-byte random nonce generated by the client. This is not for authentication or encryption; it exists to prevent caching proxies from accidentally replaying old WebSocket handshakes. The server must prove it received this key by computing a response value.
If the server accepts the upgrade, it responds with HTTP 101 Switching Protocols:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
The Sec-WebSocket-Accept value is computed by concatenating the client's Sec-WebSocket-Key with the magic GUID string 258EAFA5-E914-47DA-95CA-C5AB0DC85B11, taking the SHA-1 hash of the result, and base64-encoding it. This magic string is permanently defined in RFC 6455, Section 4.2.2. After the server sends this response, the HTTP connection is replaced by a WebSocket connection over the same underlying TCP socket.
What Does the WebSocket Frame Structure Look Like?
Once the handshake completes, all communication occurs through WebSocket frames rather than HTTP messages. The frame format is designed for minimal overhead. Each frame begins with a compact header that can be as small as 2 bytes for small server-to-client messages, or up to 14 bytes for the largest client-to-server messages.
The frame header contains several critical fields:
| Field | Size | Purpose |
|---|---|---|
| FIN bit | 1 bit | Indicates whether this is the final fragment of a message. Setting FIN to 1 means the message is complete; 0 means more fragments follow. |
| RSV1-3 | 3 bits | Reserved for extensions. The permessage-deflate extension (RFC 7692) uses RSV1 to signal compressed payloads. |
| Opcode | 4 bits | Defines the frame type: 0x1 for text (UTF-8), 0x2 for binary, 0x8 for close, 0x9 for ping, 0xA for pong, 0x0 for continuation. |
| MASK bit | 1 bit | Must be 1 for all client-to-server frames. Server-to-client frames must not be masked. |
| Payload length | 7 bits, 7+16 bits, or 7+64 bits | If 0-125, that is the length. If 126, the following 2 bytes are the length. If 127, the following 8 bytes are the length (up to 2^63 bytes). |
| Masking key | 0 or 4 bytes | Present only when MASK bit is set. Each byte of payload is XORed with the key in a rotating 4-byte pattern. |
The masking requirement for client-to-server frames is a security measure. Without it, a malicious webpage could craft byte sequences that, when seen by an intermediary proxy, resemble valid HTTP requests. This "cache poisoning" attack vector was identified during the protocol's development, and masking was added specifically to prevent it. The 4-byte masking key makes it computationally infeasible for an attacker to predict how payload bytes will appear on the wire.
What Are WebSocket Close Codes and How Do They Work?
WebSocket defines a clean shutdown procedure using close frames (opcode 0x8). Either side can initiate a close by sending a close frame containing an optional two-byte status code and a UTF-8 reason string. The other side must respond with its own close frame before the underlying TCP connection is terminated.
RFC 6455 defines several standard close codes:
| Code | Name | Meaning |
|---|---|---|
| 1000 | Normal Closure | The connection fulfilled its purpose and is being closed normally. |
| 1001 | Going Away | An endpoint is going away, such as a server shutting down or a browser navigating to a new page. |
| 1002 | Protocol Error | The connection is being terminated due to a protocol violation. |
| 1003 | Unsupported Data | The endpoint received a type of data it cannot accept (e.g., binary when it only handles text). |
| 1006 | Abnormal Closure | Reserved. Indicates the connection was closed abnormally without a close frame. Never sent on the wire. |
| 1008 | Policy Violation | The message violates the endpoint's policy. A generic code when 1003 or 1009 do not apply. |
| 1009 | Message Too Big | The message exceeds the endpoint's size limit. |
| 1011 | Internal Error | The server encountered an unexpected condition that prevented it from fulfilling the request. |
| 1015 | TLS Handshake Failure | Reserved. Indicates a TLS failure. Never sent on the wire; only used in APIs. |
Codes in the range 3000-3999 are reserved for libraries, frameworks, and applications. The range 4000-4999 is reserved for private use. Applications commonly define custom close codes in these ranges for application-specific error handling.
What Is the Difference Between ws:// and wss://?
WebSocket defines two URI schemes: ws:// for unencrypted connections and wss:// for connections secured with TLS (Transport Layer Security). This mirrors the http:// and https:// distinction. The default port for ws:// is 80, and for wss:// it is 443, again matching their HTTP counterparts.
When using wss://, the TLS handshake occurs before the WebSocket upgrade handshake. The client first establishes a TLS connection to the server, then sends the HTTP Upgrade request through the encrypted tunnel. This means the WebSocket handshake headers, including the Sec-WebSocket-Key, are never visible to intermediaries.
In production environments, wss:// is strongly recommended for several reasons. First, many corporate firewalls, transparent proxies, and mobile carrier gateways inspect and sometimes modify or block unencrypted WebSocket traffic because it does not conform to standard HTTP patterns. Second, browsers enforce mixed content rules: a page served over HTTPS cannot establish a ws:// connection. Third, the TLS layer provides authentication (the client verifies the server's certificate) and integrity protection in addition to confidentiality.
How Does WebSocket Compare to HTTP Polling and Server-Sent Events?
Before WebSocket, developers used several techniques to approximate real-time communication within HTTP's request-response constraints. Understanding how these alternatives work illuminates why WebSocket was needed and when simpler approaches remain appropriate.
HTTP Short Polling is the simplest approach: the client sends a request at regular intervals (e.g., every 2 seconds), and the server responds immediately with current data or an empty response. This creates significant overhead: each poll involves a full HTTP request-response cycle with headers that often exceed the actual payload size. For an application polling every 2 seconds with 800 bytes of headers per round trip, that is 1.4 MB of pure header overhead per hour per client, most of which carries no useful data.
HTTP Long Polling improves on this by having the server hold the request open until new data is available or a timeout occurs. The client immediately issues a new request after receiving each response. While this reduces unnecessary network traffic, it still carries the overhead of HTTP headers on every response, and each "push" from the server requires a new request-response cycle. Under high message volume, long polling degenerates into something worse than short polling because of connection setup overhead.
Server-Sent Events (SSE), standardized as part of the HTML5 specification, provide a unidirectional streaming channel from server to client over a standard HTTP connection. SSE connections use the text/event-stream MIME type and support automatic reconnection, event IDs for resumable streams, and named event types. SSE is well-suited for scenarios where the server pushes data and the client rarely needs to send messages (using standard HTTP requests when it does). However, SSE is limited to text data (no binary), supports only server-to-client flow, and is constrained by browser connection limits per domain (typically 6 for HTTP/1.1).
WebSocket provides true bidirectional communication with minimal framing overhead. Once established, a WebSocket connection adds only 2-14 bytes per message compared to hundreds of bytes of HTTP headers. Both endpoints can send messages at any time, and the protocol supports both text and binary data natively.
How Do CDNs Support WebSocket Connections?
Content Delivery Networks were originally designed to cache and serve static content from edge servers close to end users. WebSocket traffic, by its nature, cannot be cached: it represents real-time, bidirectional communication streams that are unique to each client session. Despite this fundamental difference, most major CDN providers now support WebSocket proxying at their edge locations, and this support has become an important part of CDN real-time capabilities.
When a CDN edge server receives a WebSocket upgrade request, it establishes a persistent connection to the client and a corresponding persistent connection to the origin server (or to a regional midtier). The edge acts as a transparent proxy, forwarding frames in both directions. This provides several benefits even though the content is not cached:
- Reduced latency: The client connects to a nearby edge server rather than the origin, which may be thousands of kilometers away. The TLS handshake, which can add 1-3 round trips, is performed with the edge rather than the distant origin.
- Connection optimization: The CDN can maintain a pool of persistent connections between edge and origin, multiplexing multiple client WebSocket sessions over fewer backend connections. This reduces connection setup overhead and connection count on the origin server.
- DDoS protection: The CDN can apply rate limiting, connection limits, and attack detection at the edge before malicious traffic reaches the origin.
- Protocol upgrade: Some CDNs support upgrading
ws://connections from clients towss://connections to the origin, or vice versa, handling TLS termination at the edge.
Cloudflare was one of the first major CDNs to support WebSocket proxying, initially for Enterprise customers in 2014 and later expanding to all plans. AWS CloudFront added WebSocket support in 2018 through its origin request policy configuration. Fastly supports WebSocket through its pass-through (non-caching) mode. Akamai supports WebSocket via its Edge Compute and Ion product lines.
CDN WebSocket support does come with important considerations. Idle timeout policies vary by provider, typically ranging from 60 seconds to 24 hours. Many CDNs count WebSocket connections against concurrent connection limits. Billing models differ as well: some charge per connection-hour, others per data transferred, and some apply different rates for WebSocket traffic compared to standard HTTP. Understanding these policies is essential when architecting real-time features that flow through CDN edge infrastructure.
What Are WebSocket Extensions and Subprotocols?
The WebSocket protocol supports two mechanisms for extending its capabilities: extensions and subprotocols.
Extensions modify the WebSocket framing layer itself. They are negotiated during the handshake via the Sec-WebSocket-Extensions header. The most widely deployed extension is permessage-deflate (RFC 7692), which compresses message payloads using the DEFLATE algorithm. When negotiated, each message is compressed before framing and decompressed after deframing, using the RSV1 bit in the frame header to indicate which messages are compressed. Permessage-deflate can reduce bandwidth usage by 60-80% for text-heavy payloads like JSON, though it adds CPU overhead and can increase latency for very small messages where the compression dictionary setup exceeds any savings.
Subprotocols define application-level message formats and semantics on top of the basic WebSocket framing. They are negotiated via the Sec-WebSocket-Protocol header. Common subprotocols include STOMP (Simple Text Oriented Messaging Protocol, used for message broker communication), WAMP (Web Application Messaging Protocol, providing RPC and pub/sub patterns), and GraphQL over WebSocket (graphql-ws and graphql-transport-ws, used for GraphQL subscriptions). When the client sends multiple subprotocol options, the server selects one and confirms it in the handshake response.
What Are the Security Considerations for WebSocket?
WebSocket introduces several security considerations that differ from traditional HTTP security. The initial handshake includes an Origin header that the server should validate to prevent cross-origin WebSocket hijacking, a vulnerability where a malicious page opens a WebSocket connection to a legitimate service using the user's cookies. Unlike standard HTTP CORS, there is no browser-enforced cross-origin restriction on WebSocket connections; the server must implement origin validation itself.
Authentication presents another challenge. Since WebSocket connections begin as HTTP, the initial handshake can carry cookies and HTTP authentication headers. However, once upgraded, the connection cannot use HTTP-level authentication for ongoing messages. Applications typically authenticate during the handshake or send authentication tokens as the first WebSocket message after connection. Token-based authentication with periodic renewal is recommended to handle session expiration on long-lived connections.
The masking mechanism described earlier protects against cache poisoning attacks through intermediary proxies, but it does not provide confidentiality or integrity. For those properties, wss:// (WebSocket over TLS) must be used. Additionally, servers should validate all incoming message data just as rigorously as HTTP request data: injection attacks, oversized messages, and malformed payloads are all potential attack vectors.
What Are Common Use Cases for the WebSocket Protocol?
WebSocket's combination of low latency, low overhead, and bidirectional communication makes it the preferred protocol for several categories of applications:
- Chat and messaging: Applications like Slack, Discord, and WhatsApp Web use WebSocket for real-time message delivery. Messages from any participant are pushed to all connected clients within milliseconds.
- Live financial data: Stock trading platforms, cryptocurrency exchanges, and financial dashboards use WebSocket to stream price updates, order book changes, and trade executions. The Coinbase Pro API, for example, streams real-time market data over WebSocket with an average latency under 10 milliseconds.
- Collaborative editing: Applications like Google Docs and Figma use WebSocket to synchronize editing operations between multiple users in real time, typically using operational transformation (OT) or conflict-free replicated data types (CRDTs) over the WebSocket channel.
- Online gaming: Multiplayer browser games use WebSocket for game state synchronization, player movement updates, and action broadcasting. The protocol's binary frame support allows efficient serialization of game data.
- IoT and monitoring: Device telemetry dashboards, server monitoring tools (like Grafana Live), and IoT control panels use WebSocket to stream sensor data and accept control commands.
- Live sports and media: Live score updates, auction platforms, and live streaming chat overlays leverage WebSocket for event-driven data delivery to large audiences.
Despite its strengths, WebSocket is not always the right choice. For scenarios where the server primarily pushes data and the client rarely sends messages, Server-Sent Events offer a simpler implementation with built-in reconnection and event ID tracking. For infrequent data updates, standard HTTP requests with appropriate caching headers may be more efficient than maintaining persistent connections. The choice between these approaches should be driven by the communication pattern, message frequency, and latency requirements of the specific application. Understanding where WebSocket fits within the broader TCP/IP protocol stack helps architects make informed decisions.