WebSocket
WebSocket (RFC 6455) is a protocol that layers a full-duplex, framed message channel over one TCP connection, so either side can send at any time. An HTTP/1.1 Upgrade request opens it; after the 101 it is no longer HTTP. ws:// is plain, wss:// is the same protocol over TLS.
Also known as WebSockets.
Full Explanation
WebSocket is an IETF protocol (RFC 6455). The RFC defines it as "an opening handshake followed by basic message framing, layered over TCP", in which "each side can, independently from the other, send data at will" (section 1.2). It exists because HTTP had no way for a server to push. The RFC's own use cases are games, stock tickers, and "multiuser applications with simultaneous editing" (section 1.1). Cloudflare names live chat and gaming as what its customers run over it (Cloudflare WebSockets). WebSocket is not HTTP. "Its only relationship to HTTP is that its handshake is interpreted by HTTP servers as an Upgrade request" (section 1.7). Once the handshake completes, it becomes an independent TCP-based protocol with its own framing. It is not polling. It is also not HTTP keep-alive. Keep-alive reuses one connection for successive request/response pairs, but the client still has to ask each time. The name also covers the browser API. That API is specified separately, as the WHATWG WebSockets Standard. The API is deliberately narrower than the wire protocol: it exposes no ping or pong frames, and its handshake refuses redirects. ws:// is the plain scheme on port 80. wss:// is the same protocol over TLS on port 443 (section 1.7).
How it works
"The protocol has two parts: a handshake and the data transfer" (section 1.2). The handshake borrows HTTP, so one port can serve both. Everything after it is a different wire protocol.
- Open. The client sends an HTTP/1.1 request. The RFC requires that "the method of the request MUST be GET, and the HTTP version MUST be at least 1.1". The request carries Upgrade: websocket, Connection: Upgrade, and a Sec-WebSocket-Key. That key is "a nonce consisting of a randomly selected 16-byte value that has been base64-encoded", fresh for each connection. The request also carries Sec-WebSocket-Version: 13 (section 4.1). 13 is the version RFC 6455 defines, but it is not the only value the field may carry. A server that does not understand the requested version must answer with a Sec-WebSocket-Version header listing the versions it does support, plus an error such as 426 Upgrade Required (section 4.4). A browser client MUST also send Origin. A non-browser client MAY (section 4.1).
- Accept. The server agrees with 101 Switching Protocols and a Sec-WebSocket-Accept header. It concatenates the client key with the fixed GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11, then returns a base64-encoded SHA-1 hash (160 bits) of that concatenation in its handshake (section 1.3). "Any status code other than 101 indicates that the WebSocket handshake has not completed and that the semantics of HTTP still apply" (section 1.3).
- Frames. "On the wire, a message is composed of one or more frames" (section 1.2). Each frame carries an opcode: %x0 continuation, %x1 text, %x2 binary, and the control frames %x8 close, %x9 ping, %xA pong (section 5.2).
- Masking. "A client MUST mask all frames that it sends to the server", "the server MUST close the connection upon receiving a frame that is not masked", and "a server MUST NOT mask any frames that it sends to the client" (section 5.1). Masking is obfuscation, not encryption. The 32-bit key is "contained completely within the frame" (section 5.3). Clients mask "whether or not the WebSocket Protocol is running over TLS" (section 5.1). Its purpose is to protect infrastructure. While the protocol was being written, an experiment poisoned caching proxies deployed in the wild. It did this by upgrading a connection and then sending bytes that looked like a GET for a known resource. So "the defense adopted is to mask all data from the client to the server, so that the remote script (attacker) does not have control over how the data being sent appears on the wire" (section 10.3).
- Negotiation. The client may offer subprotocols (Sec-WebSocket-Protocol) and extensions (Sec-WebSocket-Extensions) (section 1.3). The usual compression opt-in is permessage-deflate (RFC 7692). It defines server_no_context_takeover, client_no_context_takeover, server_max_window_bits and client_max_window_bits precisely "to help endpoints manage per-connection resource usage". These parameters exist because carrying the LZ77 window between messages costs memory on every open connection (RFC 7692, section 7).
- Keepalive and close. "A Ping frame may serve either as a keepalive or as a means to verify that the remote endpoint is still responsive" (section 5.5.2). Either peer may start the close. The RFC requires that "if an endpoint receives a Close frame and did not previously send a Close frame, the endpoint MUST send a Close frame in response" (section 5.5.1). After that exchange, the connection is closed.
Over HTTP/2 and HTTP/3 there is no 101. That is because multiplexed HTTP "does not allow connection-wide header fields or status codes, such as the Upgrade and Connection request-header fields or the 101 (Switching Protocols) response code". The handshake becomes an Extended CONNECT whose :protocol pseudo-header "MUST have a value of "websocket"". The session then runs over a single stream of the shared connection, using that stream's flow control rather than a TCP connection of its own. Sec-WebSocket-Key and Sec-WebSocket-Accept are not used, "as that functionality has been superseded by the :protocol pseudo-header field" (RFC 8441, section 5). It is opt-in per connection. A client MAY use Extended CONNECT only "upon receipt of SETTINGS_ENABLE_CONNECT_PROTOCOL with a value of 1" from the server (RFC 8441, section 3). RFC 9220 ports the same mechanism, pseudo-header and setting to HTTP/3.
Why it matters for a CDN
The CDN's main lever, caching, does not apply. After the 101, the bytes become an independent, full-duplex stream. That stream belongs to one client and one origin, not a reusable object with a cache key. What a CDN can still do is proxy it. Cloudflare, for example, "supports proxied WebSocket connections without additional configuration" once WebSockets is switched on for the zone. In that role, the edge terminates the client's TLS and lands the client on a nearby point of presence. It subjects the opening request to WAF, DDoS and rate limiting rules, and forwards the channel to the origin.
The cost is the connection. A WebSocket is long-lived and stateful. Each one holds a slot at the edge and a slot at one origin, and it cannot be moved once open. Metering therefore is not per object. It also differs by vendor rather than following one rule. Cloudflare "recognizes only the initial upgrade request per WebSocket connection as an HTTP request". It counts the whole session as "a single long-lived HTTP request" and measures bandwidth sent from Cloudflare to the client. Fastly bills differently: "we base billing for WebSockets on a combination of bandwidth and connection time". Connection time there is "measured for each connection in usage minutes (rounded up to whole minutes)". Akamai instead caps volume: "the number of concurrent clients connected through Akamai to customer origin via WebSocket is limited and isn't configurable". Capacity planning for WebSocket traffic means sizing for concurrency and hold time, not requests and cache hit ratio.
What CDNs do
- Cloudflare. "WebSockets are supported on all Cloudflare plans" and proxying needs no extra configuration. Even so, the WebSockets toggle on the Network page, or the zone setting via the API, is what enables connections to the origin. The WAF is listed compatible, with a caveat. The opening 101 request is subject to managed rules, custom rules and rate limiting rules, "however, once a connection has been established, the WAF does not perform any further inspections". SSL is compatible. Argo Smart Routing is not. Idle connections are closed, and a custom idle timeout is available to Enterprise customers. Releasing new code to the network "may restart servers, which terminates WebSockets connections" (Cloudflare WebSockets).
- Fastly. Fastly supports the protocol, but "use of the WebSocket protocol is disabled by default". A superuser on an eligible account must enable it on the Products page. "WebSockets is not compatible with shielding or the Fastly Next-Gen WAF." Only the Name, Address, Enable TLS and Override Host origin settings are honoured for a WebSocket host. Origin certificates "must be signed by a public certification authority"; self-signed certificates are unsupported. Also, vcl_log "will run at the time the request is accepted rather than when the connection ends" (Fastly WebSockets).
- Akamai. Akamai offers the feature "as is": "your WebSocket upgrade request may be declined and existing WebSocket connections dropped". The concurrent-client limit is fixed. Clean closure is not guaranteed, "because the proxy is not aware of WebSocket frames and will close the TCP connection if either end is closed". Customer implementations "must support connection retries and fall back to long polling"; Akamai does not do this for you. Enabling it narrowly matters: "if you enable WebSockets for specific matches, response body inspection for the given Application Security product won't work for the corresponding requests" (Akamai WebSockets support).
Watch out for
- Every hop has to cooperate. "For a WebSocket connection to work over HTTP in the clear, all network middleboxes along the way must support WebSockets" (Akamai). A proxy or balancer that only understands ordinary HTTP will not pass the Upgrade through.
- Inspection stops at the handshake. Cloudflare's WAF does not inspect after the connection is established. Akamai loses response body inspection on WebSocket-enabled matches. Fastly's Next-Gen WAF is not compatible at all. The application owns payload validation.
- Origin is not authentication. A browser must send it. A non-browser client may send anything. The RFC is explicit that "the intent is not to prevent non-browsers from establishing connections but rather to ensure that trusted browsers under the control of potentially malicious JavaScript cannot fake a WebSocket handshake" (section 10.2). The browser handshake is a fetch with credentials mode "include" (WHATWG WebSockets, opening handshake). Cookies ride along, so a cross-site page can open an authenticated socket. That is prevented only if the server checks Origin itself and answers 403 (section 10.2).
- Browsers cannot send ping frames. The WHATWG standard says of Ping and Pong: "these are not currently exposed in the API". A user agent's own pings must not be used to serve the server's needs (WHATWG WebSockets, Ping and Pong frames). A browser heartbeat has to be an application-level message. Only server-side or native clients can use real control frames.
- A redirect fails the handshake. The browser builds the handshake request with redirect mode "error" (WHATWG WebSockets, opening handshake). So an edge rule that answers an upgrade with a 301 or 302 breaks the connection, instead of being followed. The same section maps ws to http and wss to https for fetching. That mapping is why HSTS applies to a WebSocket URL.
- No mid-connection rebalancing. An open socket cannot be moved. Behind an L7 load balancer you need session affinity, so "all requests from the same client are routed to the same origin server". Without that affinity, "a WebSocket reconnect may land on a different origin that does not have the session state" (Cloudflare).
- Idle connections and edge restarts end sessions. Cloudflare closes a connection when no data flows in either direction, and it advises a client-side heartbeat. It also warns that network code releases can restart servers. Treat disconnection as normal, not exceptional.
Best practice
- Use wss:// in production. RFC 6455 is direct about it: "WebSocket implementations MUST support TLS and SHOULD employ it when communicating with their peers" (section 10.6). An encrypted stream is not at the mercy of every middlebox that has to understand cleartext WebSocket. If the CDN re-encrypts to the origin, keep a publicly signed certificate there, as Fastly requires.
- Enable WebSocket only on the paths that need push. Akamai recommends enabling it "only for the URL that requires it, and not for the entire configuration". This also limits the request inspection you are giving up.
- Heartbeat at the layer you actually control. Use an application-level message from browser clients, or ping and pong control frames from server-side or native clients. Akamai suggests using ping and pong to keep the connection established, rather than raising the read timeout.
- Design for reconnect. Build retries and a long polling fallback into the application, since Akamai will not do it for you. Keep session state off the socket, so a reconnect landing on a different edge or origin still works.
- Bound per-connection resources. An implementation "SHOULD impose a limit on frame sizes and the total message size after reassembly from multiple frames" (section 10.4). Use permessage-deflate for text-heavy messages, but budget the compression context memory it holds open per connection.
- Instrument concurrency, connection lifetime and reconnect rate. Read the vendor's own metering rules before sizing: one long-lived request at Cloudflare, bandwidth plus connection minutes at Fastly, a fixed concurrency ceiling at Akamai.
Examples
# WebSocket upgrade request
GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
# Server response
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
# Nginx: proxy WebSocket
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 86400; # Keep alive for 24h
}
Frequently Asked Questions
WebSocket (RFC 6455) is a protocol that layers a full-duplex, framed message channel over one TCP connection, so either side can send at any time. An HTTP/1.1 Upgrade request opens it; after the 101 it is no longer HTTP. ws:// is plain, wss:// is the same protocol over TLS.
# WebSocket upgrade request
GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
# Server response
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
# Nginx: proxy WebSocket
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 86400; # Keep alive for 24h
}
Yes. WebSocket is also known as WebSockets. WebSocket (RFC 6455) is a protocol that layers a full-duplex, framed message channel over one TCP connection, so either side can send at any time. An HTTP/1.1 Upgrade request opens it; after the 101 it is no longer HTTP. ws:// is plain, wss:// is the same protocol over TLS.
Related CDN concepts include:
- HTTP/1.1 — HTTP/1.1 is the text-based version of HTTP, first published in January 1997 and defined today …
- HTTP/2 — Version of HTTP that carries many concurrent request/response streams over one TCP connection and compresses …
- Keep-Alive — Reusing one TCP connection for many HTTP request/response exchanges instead of opening a new connection …
- TCP (TCP) — TCP is the connection-oriented transport specified in RFC 9293: it delivers application data as one …