Many web interactions fit HTTP's request-response model: a client asks for a resource, the server returns a response, and the exchange ends. Some applications need a different communication pattern.
In a chat room, the server should deliver a message as soon as another participant sends it. In a multiplayer game, both sides exchange frequent state changes. In a collaborative editor, users continuously send operations while receiving operations from everyone else.
Repeated HTTP requests can imitate this behavior, but they add request headers, repeated scheduling work, and delay between updates. WebSocket provides a persistent, message-oriented channel on which the client and server can send independently at any time.
The protocol has two distinct phases:
WebSocket begins with HTTP-compatible negotiation, but it is not an HTTP request-response protocol after the classic upgrade succeeds. Understanding that transition is the key to understanding WebSockets.
A WebSocket connection is full-duplex. The client can send while the server sends in the opposite direction; neither side must wait for a request before transmitting.
It is also long-lived. Applications commonly keep one connection open for minutes or hours and exchange many messages through it.
WebSocket is message-oriented at the application boundary. An application sends a text or binary message and the peer receives the corresponding message. This differs from using a raw TCP byte stream, where the application must define how one message ends and another begins.
Underneath, WebSocket normally relies on TCP. It therefore inherits reliable, in-order byte delivery for the lifetime of a connection. WebSocket adds message boundaries and protocol control frames; it does not replace TCP's transport behavior.
WebSocket is a transport for application messages, not a complete application protocol. It does not define:
A system must define those semantics above WebSocket. Sending JSON does not make the JSON fields self-explanatory to the protocol.
ws and wss URLsWebSocket uses two URI schemes:
ws is an unencrypted WebSocket connection. Its default port is 80.
wss protects the connection with TLS. Its default port is 443. In a normal wss connection, the client establishes TCP, completes the TLS handshake, and then performs the WebSocket opening handshake inside the encrypted channel.
Production browser applications should use wss. Besides protecting message contents, TLS protects authentication credentials and the handshake from observation or modification. A page loaded over HTTPS is also normally prevented from opening an insecure ws connection because that would create active mixed content.
The URL path and query string identify an application endpoint:
The path is sent during the opening handshake. Once connected, however, WebSocket has no built-in concept of additional URLs, methods, or status codes for individual messages.
The classic WebSocket handshake uses an HTTP/1.1 GET request with upgrade headers. Suppose a browser wants to connect to a chat service:
Several fields have protocol-specific roles.
Upgrade: websocket asks the server to switch protocols after this HTTP exchange.
Connection: Upgrade marks Upgrade as applying to this HTTP/1.1 connection rather than being an end-to-end header for unrelated intermediaries.
Sec-WebSocket-Version: 13 selects the protocol version standardized by RFC 6455.
Sec-WebSocket-Key is a fresh, randomly generated 16-byte value encoded with Base64. It is not a password, session key, or encryption key.
Origin identifies the origin of browser code attempting the connection. A server can use it to reject connections initiated by untrusted websites.
The last two headers are optional. Sec-WebSocket-Protocol offers application-level subprotocols, while Sec-WebSocket-Extensions offers protocol extensions.
If the server accepts the handshake, it returns:
Status 101 Switching Protocols confirms the transition. The selected subprotocol and extensions must come from the client's offers; the server cannot invent choices the client did not propose.
After the response headers end, the connection stays open. The next bytes are WebSocket frames, not another HTTP request or response.
For an unencrypted ws connection, the TLS step is absent.
Sec-WebSocket-Accept Is CalculatedThe server proves that it understood the WebSocket handshake rather than treating the request as ordinary HTTP. It takes the Sec-WebSocket-Key text, appends a fixed GUID, hashes the resulting bytes with SHA-1, and Base64-encodes the hash:
The client calculates the same value and rejects a response that does not match.
This mechanism prevents an ordinary HTTP response from being mistaken for a WebSocket handshake. It does not authenticate the server, authenticate the user, or provide confidentiality. TLS and application authentication serve those purposes.
Before the protocol switch, the server can return an ordinary HTTP response such as:
Authentication failure, an unacceptable origin, an unsupported version, or a missing required subprotocol can all prevent the upgrade. A response without a valid 101 and matching acceptance value does not establish a classic WebSocket connection.
Loading simulation...
A WebSocket subprotocol defines the meaning of application messages carried by the connection. For example:
The ordered list says that the client supports both versions and prefers chat.v2. If the server selects it, the response contains exactly one choice:
Both endpoints can then interpret messages using the chat.v2 contract. A named, versioned subprotocol is useful when multiple clients and servers evolve independently.
An extension changes WebSocket protocol processing itself. The common permessage-deflate extension compresses message payloads with DEFLATE:
Compression can save bandwidth for large, repetitive text messages. It can also consume CPU and memory, increase latency for small messages, and expand attacker-controlled compressed input into much larger data. It should be enabled with message-size limits and deliberate resource settings rather than treated as free optimization.
Subprotocol negotiation and extension negotiation happen only during the opening handshake. They cannot be silently changed halfway through an established connection.
Applications work with messages, but the wire carries frames. One message can occupy one frame or be fragmented across several frames.
Every frame starts with a two-byte base header. Extra length fields, a masking key, and payload data can follow it.
The fields have the following meanings:
| Field | Purpose |
|---|---|
FIN | Marks the final frame of a message |
RSV1–RSV3 | Reserved for negotiated extensions; otherwise zero |
Opcode | Identifies continuation, text, binary, close, ping, or pong |
MASK | Says whether a four-byte masking key is present |
| Payload length | Gives the payload size directly or selects an extended length |
| Masking key | Transforms client-to-server payload bytes |
| Payload data | Carries application or control data |
The seven-bit length indicator is encoded as follows:
For the 64-bit form, the most significant bit must be zero, so valid protocol lengths use at most 63 bits. In practice, an endpoint must enforce a much smaller configured limit before allocating memory. A declared multi-gigabyte message should not cause an eager multi-gigabyte allocation.
Consider these bytes sent by a server:
0x81 is binary 10000001:
0x05 says the frame is unmasked and has a five-byte payload. The remaining bytes are UTF-8 for:
The complete message therefore needs only two bytes of WebSocket framing overhead.
Opcode 0x1 begins a text message. Its completed payload must be valid UTF-8. JSON is a popular application format, but WebSocket itself neither requires nor parses JSON.
Opcode 0x2 begins a binary message. The protocol treats its bytes as opaque. The application might interpret them as an image chunk, a serialized data structure, or a custom binary format.
A receiver must not guess the type from the payload. The opcode defines whether a message is text or binary.
Loading simulation...
Every RFC 6455 frame sent from a client to a server is masked, even when the connection uses TLS. Server-to-client frames are not masked.
For each frame, the client chooses a fresh, unpredictable four-byte masking key. It transforms every payload byte:
The receiver applies the same operation to recover the original payload because XOR is reversible.
For example, a client can encode Hello with masking key 37 fa 21 3d:
The high bit in 0x85 is the MASK bit, while the lower seven bits contain length 5. The next four bytes are the key, followed by the five transformed payload bytes.
Masking is not encryption. Anyone who sees the frame also sees its masking key and can recover the payload. Its purpose is to prevent malicious client-controlled bytes from looking like another protocol to intermediaries that might incorrectly inspect or cache the traffic.
A server must reject an unmasked client frame. A client must reject a masked server frame. These rules make the direction of a conforming frame visible in a packet capture.
Most applications send each message as one frame, but WebSocket permits fragmentation. A fragmented text message might look like:
Together, the frames form one text message:
Only the first data frame uses the text or binary opcode. Later pieces use opcode 0x0, meaning continuation. FIN=1 on the final continuation completes the message.
Fragmentation lets a sender begin transmitting without first buffering an entire large message. It also permits control frames to be handled without waiting for that large message to finish.
Control frames can appear between fragments:
The ping is processed immediately and is not part of the text payload.
Unless an extension says otherwise, fragments from two different data messages cannot be interleaved on one connection. Fragmentation therefore does not create independent parallel streams. All bytes still travel through the same ordered transport connection.
WebSocket defines three control frame types. A control frame has a payload of at most 125 bytes and is never fragmented.
Opcode 0x9 is Ping. An endpoint receiving a Ping must respond with a Pong carrying the same application data unless it has already received a Close frame.
Opcode 0xA is Pong. It can answer a Ping, and an endpoint can also send an unsolicited Pong.
Ping and Pong can:
They do not prove that the application is healthy. A library might answer Ping while its application thread is stalled. If business-level health matters, the application can define its own heartbeat message and expected response.
The standard browser WebSocket API does not expose methods for sending protocol-level Ping frames. Browser networking code handles received Pings internally. Servers commonly initiate protocol pings, while browser applications can use an application-level heartbeat when required.
Opcode 0x8 begins the closing handshake. A Close payload can contain a two-byte status code followed by a short UTF-8 reason.
Frequently encountered status codes include:
Values 1005 and 1006 describe observed outcomes and must not be placed in a Close frame. In particular, 1006 means the connection ended abnormally without a Close frame being received.
When an endpoint receives Close and has not sent one, it replies with Close. Once both sides have sent and received Close, the underlying transport connection is closed.
This exchange lets each endpoint distinguish an intentional protocol shutdown from a cable break, process crash, or timeout. It is still wise to impose a closing timeout: a peer or network failure may prevent the reply from arriving.
The stable browser API exposes an event-driven WebSocket object:
The browser performs the handshake, validates the response, unmasks or decodes frames, reassembles fragmented messages, validates text, and exposes complete messages to the script.
Four ready states describe the lifecycle:
An application should call send() only while the connection is open and should remove connection-specific state when it closes.
A useful message contract has an explicit type and versioned fields:
The server might respond:
type supports dispatch, requestId correlates an application response with a command, and a server-assigned sequence can help detect missed events after reconnecting.
This is application design, not WebSocket framing. The entire JSON document normally occupies one WebSocket text message; it should not be split into several WebSocket messages merely because TCP might divide the bytes into packets.
WebSocket preserves message order on one live connection because its frames use an ordered transport. If a sender transmits messages A, B, and C, a conforming receiver does not deliver C before A on that connection.
That guarantee has important limits.
Calling send() usually means the message entered a local buffer. It does not prove that the server processed or stored it. A TCP acknowledgment proves receipt by the peer's TCP stack, not completion of an application operation.
If a connection disappears immediately after a client sends a command, the client may not know whether the server acted on it. Blindly retrying can create a duplicate. Important commands therefore need application-level identifiers, acknowledgments, and idempotent processing.
WebSocket also provides no automatic reconnection. A browser application must create a new WebSocket object. A sensible retry policy uses exponential backoff with random jitter:
Jitter prevents thousands of disconnected clients from reconnecting at the same instant after a server restart.
A new connection has no built-in memory of the old one. If the application needs gap-free recovery, the client must send a cursor or last processed sequence, and the server must retain replayable events or return a fresh snapshot.
A network connection has finite buffers. If an application produces messages faster than the peer or network can consume them, queued data grows.
This creates backpressure: the receiver's limited capacity must eventually slow the sender. TCP handles pressure at the byte-stream level, but an application or library can still buffer a large number of WebSocket messages in memory before noticing.
In the browser API, bufferedAmount reports bytes queued by calls to send() but not yet transmitted:
This is a signal, not a complete flow-control policy. The application must decide whether to pause a producer, combine replaceable updates, drop stale data, or close a client that cannot keep up.
Servers need per-connection queue limits. One slow client should not accumulate unbounded data and exhaust memory for every other client.
Large inbound messages need limits as well. Incremental frame parsing does not make the final reassembled message safe to hold. Enforce maximum compressed and decompressed sizes, and reject oversized messages with an appropriate close code such as 1009.
A WebSocket connection is an open route into an application for as long as it remains established. Its security checks must be as deliberate as those on ordinary API endpoints.
Use wss in production. Without TLS, message contents and credentials are exposed, and an on-path party can modify traffic. Masking provides no confidentiality.
The opening handshake can use cookies and standard HTTP authentication mechanisms. Browser JavaScript, however, cannot attach an arbitrary Authorization header through the WebSocket constructor.
Common browser designs authenticate an existing secure session cookie during the handshake or send a short-lived credential in the first application message. If authentication happens after opening, the server should permit no sensitive actions before it succeeds and should enforce a short authentication deadline.
Long-lived connections also outlive many credentials. The application needs a policy for expired sessions, revoked users, and permission changes rather than assuming the handshake decision remains valid forever.
OriginWebSocket handshakes are not protected by ordinary CORS response headers in the same way as fetch() calls. Browsers can initiate cross-origin WebSocket connections and include an Origin header.
A cookie-authenticated server that accepts any origin may be vulnerable to cross-site WebSocket hijacking: a malicious page opens a connection using the victim's ambient credentials. A browser-facing server should compare Origin against an explicit allowlist and reject unexpected origins before upgrading.
Origin is a browser security signal, not proof of client identity. Non-browser clients can set any value, so authentication and authorization remain necessary.
After connection authentication, authorize each operation. Joining one tenant's channel must not imply permission to subscribe to every tenant.
Validate message types, field sizes, identifiers, encodings, and state transitions. Apply rate limits per user and connection. Do not assume that a client follows the user interface's intended sequence of actions.
Compression changes resource and confidentiality risks. Tiny compressed inputs can expand substantially, and compression ratios can sometimes reveal information when attacker-controlled and secret data share compression context.
Disable compression where it has little value, avoid mixing secrets with attacker-controlled content in the same compressed context, and configure decompressed-size, memory, and CPU limits.
A production connection often passes through a reverse proxy or load balancer. For classic HTTP/1.1 WebSockets, every intermediary on the path must support and forward the upgrade correctly.
After upgrading, the intermediary must keep both directions open and must not treat the traffic as a sequence of ordinary HTTP messages. Common operational failures include:
Heartbeat intervals should be shorter than the smallest relevant idle timeout, but not so frequent that millions of connections generate wasteful traffic.
A long-lived connection also consumes a file descriptor, memory for protocol state and buffers, and often application subscription state. Capacity planning should use concurrent connections and fan-out rate, not only HTTP requests per second.
When several WebSocket servers run behind a load balancer, each connection remains attached to the process or gateway that owns it. Messages for clients connected to other instances need an application-level routing or publish-subscribe path. WebSocket defines the edge connection; it does not distribute events between servers.
During a graceful deployment, a server can stop accepting new connections, send code 1001 to existing clients, allow a bounded drain period, and close the transport. Clients should reconnect with jitter rather than immediately overwhelming the replacement instances.
The 101 Switching Protocols exchange describes the widely used HTTP/1.1 bootstrap. HTTP/2 and HTTP/3 do not use that hop-by-hop upgrade mechanism in the same form.
RFC 8441 defines an extended CONNECT mechanism for carrying WebSocket over an HTTP/2 stream. RFC 9220 applies the approach to HTTP/3. When both endpoints and intermediaries support the relevant mechanism, a WebSocket connection can run as a stream inside a shared HTTP/2 or HTTP/3 connection.
The WebSocket messages and frames remain conceptually the same after establishment, but the bootstrap and underlying transport arrangement differ. Software must negotiate and support these modes; a wss URL alone does not guarantee that WebSocket will use HTTP/2 or HTTP/3.
Browser developer tools commonly show the opening request, selected subprotocol, close status, and a list of sent and received messages. This is the fastest place to determine whether a problem occurs before or after the upgrade.
If the handshake fails, inspect:
If the connection opens and later disappears, inspect idle duration, Ping/Pong behavior, Close code and reason, server logs, proxy timeouts, and whether a deployment occurred.
Wireshark can decode unencrypted WebSocket frames and expose FIN, opcode, masking, payload length, and control frames. For wss, a packet capture sees TLS records unless the session is decrypted with suitable keys. Even without decryption, TCP resets, retransmissions, connection duration, and which endpoint initiated transport closure remain visible.
Keep transport failure separate from application failure. A connection can be open while authentication has failed, or healthy Ping/Pong traffic can continue while the application stops processing messages.
WebSocket is a strong fit when both endpoints need to send frequently and independently with low per-message overhead. Chat, collaborative editing, multiplayer interaction, interactive terminals, and rapidly changing bidirectional control channels are common examples.
It is less compelling when exchanges are naturally independent request-response operations, updates are rare, or communication only needs to flow from the server toward the client. A persistent full-duplex channel adds connection state, heartbeats, reconnection logic, backpressure handling, and deployment concerns. Those costs are justified when the interaction model needs them, not merely because an application wants to appear “real-time.”
WebSocket is not repeated HTTP over one connection. After a classic successful upgrade, bytes are WebSocket frames rather than HTTP requests and responses.
WebSocket is not a raw TCP socket exposed to browser code. It provides text and binary messages, masking rules, control frames, and browser security constraints.
Masking is not encryption. The masking key travels with every client frame and is intended for protocol safety around intermediaries.
Sec-WebSocket-Key is not authentication. Its derived response proves that the server understood the WebSocket handshake.
One send() does not correspond to one TCP packet. TCP can split or combine bytes independently of WebSocket message boundaries.
A successful send() does not prove application processing. Important operations need application-level acknowledgment and idempotency.
Ping/Pong does not guarantee business logic is healthy. It primarily shows that the protocol path can respond.
WebSocket does not reconnect or replay missed data automatically. Recovery semantics belong to the application.
Persistent does not mean permanent. Networks, proxies, devices, deployments, and credentials can all end a connection.
Full-duplex does not create independent ordered streams. One WebSocket connection still has a single ordered transport path.
WebSocket is not automatically the best real-time mechanism. Its bidirectional power comes with state and operational cost.
WebSocket creates a persistent, full-duplex, message-oriented connection. A classic connection begins with an HTTP/1.1 upgrade, then switches to compact text, binary, continuation, Ping, Pong, and Close frames. Production traffic should use TLS through wss.
Clients mask every frame; servers do not, and masking is not encryption. Fragmentation spreads one message across frames while allowing control frames between fragments. TCP preserves order on a live connection, but WebSocket adds no processing acknowledgments, reconnection, replay, or deduplication.
Production systems need backpressure limits, origin validation, authentication, heartbeats, graceful shutdown, and jittered reconnects. WebSocket is most useful when both sides need frequent, independent communication.
HTTP-compatible negotiation opens a long-lived framed channel whose delivery semantics must be completed by the application.
5 quizzes