WebSocket is a protocol that gives a browser or any other client one long-lived, two-way channel to a server, so either side can send a message at any moment without a new HTTP request. It is defined in RFC 6455 (December 2011): the client sends an HTTP/1.1 GET with an Upgrade header, the server answers 101 Switching Protocols, and both sides then exchange small binary frames over the same TCP connection.
The handshake borrows HTTP, so it inherits cookies and HTTP authentication, but the frames that follow are not HTTP, so most controls that protect ordinary requests stop applying after the upgrade. This post covers the handshake, the frame layout, ping, pong and close, compression, WebSocket over HTTP/2 and HTTP/3, the weakness classes and the checks you can run on your own endpoints, all checked against the RFCs, the WHATWG standard and the official nginx documentation.
Key takeaways
- RFC 6455 (Standards Track, December 2011) defines WebSocket. RFC 7936 (July 2016), RFC 8307 (January 2018) and RFC 8441 (September 2018, WebSocket over HTTP/2) update it. RFC 9220 (June 2022) covers HTTP/3 and RFC 7692 (December 2015) defines
permessage-deflate. - The server proves it understood the handshake by returning the base64 SHA-1 of the client's random key joined to a fixed GUID. That proof is not authentication.
- Every client to server frame must be masked with a fresh random 32 bit key. Masking protects proxies from forged HTTP; it is not encryption. Only
wss://gives confidentiality. - The protocol authenticates nobody and leaves the decision about which web origins may connect to the server. RFC 6455 says a server that does not validate Origin "will accept connections from anywhere".
- A frame can declare a payload up to 2^63 bytes. Size limits, re-authentication and per-message authorisation are all yours to build.
What WebSocket is for, its layer and its standards
Before WebSocket, a page that wanted live updates had to poll or hold long-polling requests open. RFC 6455's abstract describes the goal as two-way communication for browser applications without opening multiple HTTP connections. Chat, collaborative editing, trading screens, games and live device consoles are the typical users.
WebSocket is an application layer protocol over TCP. Section 1.7 calls it "an independent TCP-based protocol" whose only link to HTTP is that HTTP servers interpret its handshake as an Upgrade request. With RFC 8441 and RFC 9220 a WebSocket can instead run inside one HTTP/2 or HTTP/3 stream, with the same frames. Its security model is the browser origin model, and section 1.6 warns that for clients that are not browsers "the origin model is not useful, as the client can provide any arbitrary origin string".
| Document | Date | What it does | Relationship |
|---|---|---|---|
| RFC 6455, The WebSocket Protocol | December 2011 | Handshake, framing, closing, security considerations, IANA registries | Base standard; obsoletes no earlier RFC |
| RFC 7692, Compression Extensions for WebSocket | December 2015 | permessage-deflate; RSV1 becomes the "Per-Message Compressed" bit | Registered extension |
| RFC 7936 | July 2016 | Subprotocol registry procedures | Updates RFC 6455 |
| RFC 8307 | January 2018 | Well-known URIs for ws and wss | Updates RFC 6455 |
| RFC 8441 | September 2018 | WebSocket over HTTP/2 via Extended CONNECT | Updates RFC 6455 |
| RFC 9220 | June 2022 | The same mechanism for HTTP/3 | Builds on RFC 8441 |
| WHATWG WebSockets Living Standard | Last updated 19 October 2026 when fetched | Browser WebSocket API; browser client handshake runs through Fetch | Replaces part of the RFC client handshake for browsers |
How the opening handshake works
Client Server
| TCP connect (plus TLS for wss://) |
|---------------------------------------------->|
| GET /chat HTTP/1.1 |
| Host: server.example.com |
| Upgrade: websocket |
| Connection: Upgrade |
| Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== |
| Origin: http://example.com |
| Sec-WebSocket-Protocol: chat, superchat |
| Sec-WebSocket-Version: 13 |
|---------------------------------------------->|
| checks path, Origin, version, credentials
| HTTP/1.1 101 Switching Protocols |
| Upgrade: websocket |
| Connection: Upgrade |
| Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
| Sec-WebSocket-Protocol: chat |
|<----------------------------------------------|
| ===== OPEN: frames in both directions ===== |
| masked frames ----------------------------->|
|<----------------------------- unmasked frames|
| Close (1000) ------------------------------->|
|<------------------------------- Close (1000) |
| server closes TCP first |
The values are RFC 6455's own section 1.3 examples. Section 4.1 requires a GET over HTTP/1.1 or later with Host, Upgrade: websocket, Connection: Upgrade, Sec-WebSocket-Version: 13, and a Sec-WebSocket-Key that is a random 16 byte nonce, base64 encoded and fresh per connection. Browsers must send Origin; other clients may. Sec-WebSocket-Protocol and Sec-WebSocket-Extensions are optional, and the RFC explicitly allows cookies and Authorization. That last point is the hinge of most WebSocket security: the handshake is the only part of the conversation where ordinary HTTP machinery can check credentials. Section 4.1 also limits a client to one connection in the CONNECTING state per host and port.
Sec-WebSocket-Accept, and what it is not
The server appends the GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 to the key exactly as received, hashes the result with SHA-1 and returns it base64 encoded. For dGhlIHNhbXBsZSBub25jZQ== the answer is s3pPLMBiTxaQ9kYGzzhZRbK+xOo=, and recomputing it gives the same value. The client fails the connection if the status is not 101, if Upgrade or Connection are wrong, if the accept value does not match, or if the server picks an extension or subprotocol it never offered.
Section 1.3 says the point is that the server proves it received a WebSocket handshake, so a form post or scripted request is never mistaken for one. Section 1.6 adds that, when written, Sec- headers could not be set by script through XMLHttpRequest. The exchange is not authentication, integrity or secrecy: key, GUID and algorithm are public. Section 10.8 notes the handshake "doesn't depend on any security properties of SHA-1".
Errors, subprotocols and ports
If the version is unsupported, section 4.2.2 says the server sends an error "such as 426 Upgrade Required" with a Sec-WebSocket-Version header; the section 4.4 example uses 400. A rejected Origin should get 403 (section 10.2). The server may also answer 401 or redirect with 3xx before accepting.
The server picks at most one offered subprotocol. The IANA subprotocol registry held 70 entries when fetched, including mqtt, sip, xmpp, amqp, coap and the OCPP versions used by electric vehicle chargers. The extension registry held two: permessage-deflate and bbf-usp-protocol.
ws defaults to port 80 and wss to port 443; WebSocket has no port of its own. Section 1.8 noted that port 443 connections were "significantly more likely to succeed" through intermediaries. Because it shares the HTTPS port, it is easy to forget a web server is also a WebSocket server.
How a frame looks on the wire
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
| Field | Size | Meaning |
|---|---|---|
| FIN | 1 bit | Final fragment of a message |
| RSV1, RSV2, RSV3 | 1 bit each | Must be 0 unless an extension defines them; RSV1 is used by permessage-deflate |
| Opcode | 4 bits | Continuation, text, binary, close, ping or pong; unknown values fail the connection |
| MASK | 1 bit | Always 1 from client to server, always 0 from server to client |
| Payload length | 7 bits | 0 to 125 is the length; 126 means a 16 bit length follows; 127 means a 64 bit length follows (top bit 0) |
| Extended payload length | 0, 16 or 64 bits | Network byte order; minimal encoding required |
| Masking key | 0 or 32 bits | Present only when MASK is 1 |
| Payload data | Variable | Extension data, then application data |
| Opcode | Type | Class |
|---|---|---|
| 0x0 | Continuation | Data |
| 0x1 | Text (UTF-8) | Data |
| 0x2 | Binary | Data |
| 0x3 to 0x7 | Reserved | Data |
| 0x8 | Close | Control |
| 0x9 | Ping | Control |
| 0xA | Pong | Control |
| 0xB to 0xF | Reserved | Control |
The IANA opcode registry matches this table. Control frames carry 125 bytes of payload or less and are never fragmented. Section 5.7's examples are handy when reading a capture of your own traffic:
Unmasked text "Hello" (server to client): 81 05 48 65 6c 6c 6f
Masked text "Hello" (client), key 37 fa 21 3d:
81 85 37 fa 21 3d 7f 9f 4d 51 58
Fragmented "Hel" + "lo": 01 03 48 65 6c / 80 02 6c 6f
0x81 is FIN plus opcode 1; 0x85 is mask bit set plus length 5.
Fragmentation
A fragmented message starts with the real opcode and FIN clear, continues with opcode 0, and ends with opcode 0 and FIN set. Control frames may be slipped between fragments so a ping is not stuck behind a large message. Frame boundaries carry no meaning without an extension, and intermediaries may split or join frames, so an application must treat the message, not the frame, as the unit. Text must be valid UTF-8 once reassembled; invalid text normally ends the connection with 1007.
Why client frames are masked
Each client frame carries a masking key that must come from "a strong source of entropy" and be unpredictable from frame to frame. Byte i of the payload is XORed with key byte i mod 4, and the same operation unmasks it. A server must close the connection on an unmasked client frame (it may use 1002), and a client must close on a masked server frame.
Section 10.3 explains why. During design, a paper RFC 6455 cites as "Talking to Yourself for Fun and Profit" (2010) showed that a page could upgrade a connection to a server it controlled and send bytes that some transparent proxies parsed as a new HTTP request for a popular script, caching the attacker's response for everyone behind the proxy. With an unpredictable key, script cannot choose the bytes on the wire. So masking is not encryption (the key is in the frame), it applies even inside TLS (section 5.1), and only the client direction needs it because the threat is a client forging a request.
Ping, pong and the closing handshake
Either side may send a ping (0x9) at any time; the receiver must answer with a pong (0xA) carrying the same data, and may answer only the latest of several pings. Unsolicited pongs work as one way heartbeats. The WHATWG standard does not expose ping or pong to page script and says browsers "must not use pings or unsolicited pongs to aid the server", so liveness and idle timeouts are a server and proxy decision.
A close frame (0x8) may carry a 2 byte status code and a UTF-8 reason that clients must not show to users. After sending close, an endpoint sends no more data. The receiver replies with its own close, then the server closes TCP immediately and the client should wait for it.
| Code | IANA name | Meaning | On the wire? |
|---|---|---|---|
| 1000 | Normal Closure | Purpose fulfilled | Yes |
| 1001 | Going Away | Server shutting down or page left | Yes |
| 1002 | Protocol error | For example an unmasked client frame | Yes |
| 1003 | Unsupported Data | Data type it cannot accept | Yes |
| 1005 | No Status Rcvd | Local only: close had no code | Never |
| 1006 | Abnormal Closure | Local only: no close frame at all | Never |
| 1007 | Invalid frame payload data | For example invalid UTF-8 | Yes |
| 1008 | Policy Violation | Generic refusal that hides detail | Yes |
| 1009 | Message Too Big | Over the endpoint's limit | Yes |
| 1010 | Mandatory Ext. | Client needed an extension the server refused | Yes, client only |
| 1011 | Internal Error | Server hit an unexpected condition | Yes |
| 1012, 1013 | Service Restart, Try Again Later | Added to the registry after RFC 6455 | Yes |
| 1014 | Described as an invalid upstream response at a gateway, similar to HTTP 502 | Gateway error | Yes |
| 1015 | TLS handshake | Local only: TLS failed | Never |
Codes 3000 to 3999 are registered by libraries and applications (IANA lists 3000 Unauthorized, 3003 Forbidden and 3008 Timeout) and 4000 to 4999 are private use. The WHATWG API lets script close only with 1000 or 3000 to 4999 and caps the reason at 123 bytes. Your server should be sending 1008 and 1009 when it enforces policy and limits; a rise in 1006 in client telemetry usually means a proxy timeout or crash is dropping connections.
Extensions: how permessage-deflate works
RFC 7692 compresses data messages with DEFLATE; control frames are never compressed. RSV1 is set on the first frame of each compressed message, so a sender can still send some messages uncompressed. The sender strips the trailing 00 00 ff ff bytes of a sync-flushed block and the receiver restores them. Four parameters bound memory:
| Parameter | Effect |
|---|---|
server_no_context_takeover / client_no_context_takeover | That side resets its compressor after every message, so earlier messages are not a dictionary for later ones |
server_max_window_bits / client_max_window_bits | Caps the LZ77 window at 8 to 15 bits (256 to 32,768 bytes) |
The WHATWG standard has browsers send a user agent defined offer, with permessage-deflate; client_max_window_bits as its example, so the server decides whether compression is used. RFC 7692's security section is one sentence: "There is a known exploit when history-based compression is combined with a secure transport [CRIME]."
WebSocket over HTTP/2 (RFC 8441) and HTTP/3 (RFC 9220)
HTTP/2 has no connection-wide Upgrade or 101, so RFC 8441 adds Extended CONNECT. The server advertises SETTINGS_ENABLE_CONNECT_PROTOCOL (0x8) as 1, and only then may the client open a stream with :method = CONNECT and :protocol = websocket. Connection, Upgrade and the key and accept exchange are dropped; Origin, version, subprotocol and extension headers remain, in lowercase. The server answers 200, and the stream then carries normal RFC 6455 frames. END_STREAM is an orderly close and RST_STREAM with CANCEL is an abort.
Server -> SETTINGS: ENABLE_CONNECT_PROTOCOL = 1
Client -> HEADERS: :method=CONNECT :protocol=websocket :scheme=https
:path=/chat :authority=server.example.com
sec-websocket-version=13 origin=http://www.example.com
Server -> HEADERS: :status=200 sec-websocket-protocol=chat
Both <-> DATA frames carrying WebSocket frames, END_STREAM to close
The server must not tunnel to the :authority host as with classic CONNECT. RFC 8441 argues this is safe because XMLHttpRequest can neither use CONNECT nor create pseudo-headers, and keeps all RFC 6455 security considerations except the SHA-1 note. RFC 9220 reuses the setting value 0x08 in the HTTP/3 registry, says a server should answer 501 to an unknown :protocol, maps closes to the stream FIN and aborts to H3_REQUEST_CANCELLED, and adds no new security considerations.
Operationally, a WebSocket may now be one stream among many on a shared connection, so per-connection limits may not map one to one. Support varies by client, server and proxy. Our HTTP/2 hardening guide covers the HTTP/2 settings.
The browser side: what the WHATWG standard changes
The WHATWG standard replaces part of the RFC client handshake so "CSP, cookies, HSTS, and other Fetch-related protocols are handled in a single location". The handshake request uses credentials mode "include", so the browser attaches its cookies for the WebSocket server whichever page opened the socket, and redirect mode "error", so a 3xx fails the connection. If script asked for subprotocols, the server must pick one. The API exposes readyState, bufferedAmount, protocol, extensions, binaryType and close(), but no way to set handshake headers, which is why applications authenticate WebSockets with cookies, a token in the URL, or a first message.
Versions and history
| Date | Milestone |
|---|---|
| 2010 | Cache poisoning research through upgraded connections leads to client masking (RFC 6455 section 10.3) |
| Before December 2011 | HyBi drafts 00 to 08 used version values 0 to 8 (IANA: "Interim"); 9 to 12 reserved and never used |
| December 2011 | RFC 6455, version 13, plus six IANA registries |
| December 2015 | RFC 7692 permessage-deflate |
| July 2016, January 2018 | RFC 7936 and RFC 8307 update RFC 6455 |
| September 2018 | RFC 8441, WebSocket over HTTP/2 |
| June 2022 | RFC 9220, WebSocket over HTTP/3 |
| 19 October 2026 | Latest WHATWG standard update when fetched |
Every pre-13 version is obsolete. RFC 6455's negotiation example shows a server listing 13, 8 and 7; a server still accepting 7 or 8 runs code paths with no current client. What is deployed is version 13 over HTTP/1.1, permessage-deflate offered by browsers, and Extended CONNECT where both ends support it.
Security properties: what WebSocket does and does not guarantee
| Property | From WebSocket itself? | Where it comes from |
|---|---|---|
| Confidentiality and integrity | No | TLS via wss://; section 10.6 says implementations must support TLS and should use it |
| Server authentication | No | TLS certificate validation |
| Client authentication | No; section 10.5 prescribes no method | Cookies, HTTP auth, tokens or TLS client certificates at the handshake |
| Per-message authorisation | No | Your application |
| Which web pages may connect | Information only | Browser sends Origin; server decides |
| Replay protection | No | TLS, plus your application |
| Size and rate limits | No | Library and proxy configuration (section 10.4) |
| Protecting proxies from forged HTTP | Yes | Client masking |
The protocol hands you a pipe; who may open it and what travels through it are your decisions. The WebSocket security guide and how to test a WebSocket API cover the application side.
Weakness classes, as concepts
Cross-site WebSocket hijacking through missing Origin checks
A user logged in to your app visits a hostile page, whose script opens a WebSocket to your server. The browser attaches your cookies, so the server sees a valid session; without an Origin check it accepts, and the hostile page can act as the user and read replies. The design allows it because WebSocket was meant to cross origins and leaves the decision to the server, which RFC 6455 says "will accept connections from anywhere" if it does not validate Origin. The defence is an exact allowlist with 403 for anything else, paired with authentication a cross-site page cannot reuse, since section 10.1 notes non-browser clients "can send fake Origin header fields". See authorisation and Origin checks.
Authentication only at the handshake
The server checks credentials at the upgrade and never again. Logout, password reset, role removal or token expiry leaves the socket open for hours. After the 101 there is no per-message HTTP request to check, and RFC 6455 defines no re-authentication. Tie each socket to a revocable session, re-check authorisation for sensitive messages, and close sockets with 1008 or an agreed 3000 range code when the session changes.
Message size and resource exhaustion
Section 10.4 names both patterns: a single frame of size 2^60 or an endless fragmented message, either able to exhaust memory in a server that buffers whole messages. Idle connections cost sockets and, with compression, a window per direction. The handshake is parsed by HTTP code before any WebSocket limit applies: CVE-2024-37890, published on cve.org on 17 June 2024, describes a Node.js ws server crashing on a request with more headers than its maxHeadersCount, fixed in 8.17.1, 7.5.10, 6.2.3 and 5.2.4 (our explainer). Set maximum frame, reassembled and decompressed message sizes, close with 1009, and cap connections and idle time.
Compression side channels
With context takeover, the compressor remembers earlier data. If a secret and attacker-influenced data share a compression context, the compressed length reveals how well a guess matches the secret, and length is visible through TLS. That is the CRIME class RFC 7692 cites. Do not compress messages that mix secrets with user-controlled content, negotiate no context takeover, or disable compression on sensitive endpoints.
Intermediary confusion and upgrade desynchronisation
Proxies that misunderstand Upgrade treat post-upgrade bytes as HTTP; masking is the protocol's answer, and servers must reject unmasked client frames. A related reverse proxy class: a proxy that switches to tunnel mode on the request alone, without waiting for the backend's 101, may pass raw client bytes to the backend while believing it still enforces HTTP rules, so blocked or authenticated paths can be reached. The nginx documentation describes the safe rule: tunnel only when the client asked to switch and the backend returned 101. Upgrade and Connection are hop-by-hop, so each proxy decides; forward them only on real WebSocket paths.
Cleartext, tokens in URLs and trusted content
ws:// exposes the handshake, cookies, URL tokens and every message, and section 10.6 notes TLS benefit "depends greatly on the strength of the algorithms negotiated" (see what TLS is, the TLS 1.3 handshake and what stays visible on the wire). Because browsers cannot set handshake headers, tokens often go in the query string, where they land in proxy and error logs; prefer a cookie with an Origin check, a short-lived single-use ticket, or a first-message credential. Finally, sections 10.1 and 10.7 require validating all incoming data; injection and authorisation bugs live in message handlers.
Implementation bugs in WebSocket libraries
Few teams write frame parsers. The library that parses untrusted lengths, opcodes, UTF-8 and compressed data is an ordinary dependency, patched by upgrading. The ws header denial of service, CVE-2021-32640 and CVE-2024-37890 come from one popular library.
Secure configuration
Application and library settings
- Allowlist origins by exact scheme, host and port; return 403 otherwise, and decide deliberately how to treat requests with no Origin.
- Authenticate before the 101, tie sockets to revocable sessions and close them when sessions end.
- Authorise every message.
- Limit frame, message and decompressed size, closing with 1009.
- Limit rate and connections per user, per IP and globally.
- Set timeouts: handshake, idle, and server pings with a pong deadline.
- Decide on compression per endpoint, or require no context takeover.
- Serve
wss://only outside localhost. - Accept only version 13, and require your subprotocol name if you use one.
- Log time, client address, Origin, identity, path, outcome and close code, never tokens.
Reverse proxy settings with nginx
The nginx documentation explains that hop-by-hop Upgrade and Connection headers must be passed explicitly, and that since 1.3.13 nginx tunnels when the backend returns 101. Its documented configuration:
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
location /chat/ {
proxy_pass http://backend;
# proxy_http_version 1.1; # needed before nginx 1.29.7
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
}
proxy_http_version defaults to 1.1 since 1.29.7 (1.0 before, which cannot upgrade). By default nginx closes the tunnel if the backend sends nothing for 60 seconds (proxy_read_timeout 60s); raise it only on WebSocket locations, or have the backend send pings, as the documentation suggests. Keep the Upgrade headers out of non-WebSocket locations.
Check commands for your own endpoint
Only run these against systems you own or are authorised to test.
1. Handshake. From the curl manual: -i includes response headers (curl 8.10.0 renamed its long form to show-headers; the short form still works), -H adds a header, -m sets a time limit, and the http1.1 option forces HTTP/1.1.
curl -i -s -m 5 --http1.1 \
-H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
-H "Origin: https://app.example.com" \
https://ws.example.com/socket
Expect 101 Switching Protocols with Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=. A Sec-WebSocket-Extensions line here means the server chose an extension the request never offered. curl may hold the connection until the -m limit; that is expected.
2. Origin enforcement. Print only the status code with -o and the write-out option -w (the http_code variable):
curl -s -o /dev/null -w '%{http_code}\n' -m 5 --http1.1 \
-H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
-H "Origin: https://not-our-site.example" \
https://ws.example.com/socket
Expect 403. A 101 means any website can connect. Repeat without Origin, and with a valid test session cookie, and compare against your policy.
3. Old versions. Set the version header to 8; expect 426 or 400 with Sec-WebSocket-Version: 13.
4. TLS. From the OpenSSL s_client documentation:
openssl s_client -connect ws.example.com:443 -servername ws.example.com -alpn http/1.1 < /dev/null
5. Running nginx configuration. nginx -T tests and dumps the configuration, per the nginx command line reference:
sudo nginx -T 2>/dev/null | grep -n -E "proxy_set_header (Upgrade|Connection)|proxy_http_version|proxy_read_timeout"
6. Long-lived connections. From the ss manual (-H no header, -t TCP, -n numeric, state and port filters):
ss -Htn state established '( sport = :https )' | wc -l
A count that only rises points to missing idle timeouts or connection caps.
When to use WebSocket and when to use something else
| Option | Direction | Runs over | Security properties | Operational cost | Best for |
|---|---|---|---|---|---|
| WebSocket | Two way, any time | TCP, or one HTTP/2 or HTTP/3 stream | TLS via wss://; auth and Origin checks are yours | Stateful long-lived connections; proxy timeouts to tune | Chat, collaboration, games, live control |
| Server-Sent Events (WHATWG HTML) | Server to client | A normal HTTP response, text/event-stream | Ordinary HTTP request and controls; reconnects with Last-Event-ID | Simple; passes anything that passes HTTP | Notifications, feeds, progress |
| WebTransport (W3C Candidate Recommendation Snapshot, 30 July 2026) | Multiple streams, out of order delivery, unreliable datagrams | A session over HTTP/3 or HTTP/2 | Requires https URLs; newer stack | Needs client and server support; UDP open for HTTP/3 | Low-latency media and games |
| gRPC streaming | Server, client or bidirectional typed streams | gRPC framework | Per-call metadata such as authentication details; ordering within a call | gRPC tooling on both sides | Service to service streams |
| Long polling | Simulated two way | HTTP | Normal HTTP controls per poll | Higher overhead and latency | Fallback |
If the browser only listens, Server-Sent Events keeps your HTTP controls. If both sides talk freely, WebSocket is the mature default. For datagrams or many independent streams, check WebTransport support. For services you control end to end, see our gRPC security considerations guide.
Common mistakes
- Treating the key and accept exchange, or masking, as security.
- No Origin check on a cookie-authenticated socket, or treating Origin as authentication.
- Checking identity once and never closing sockets when sessions end.
- Limiting compressed size but not decompressed size.
- Raising proxy timeouts globally instead of on WebSocket locations.
- Forwarding
Upgradeon every path. - Treating frames as messages.
- Long-lived tokens in WebSocket URLs.
- Leaving pre-13 versions enabled.
- Leaving the WebSocket library out of the dependency inventory.
FAQ
What port does WebSocket use?
None of its own. ws:// defaults to 80 and wss:// to 443, the HTTP and HTTPS ports, and a URI may name any port.
Is WebSocket encrypted?
Only with wss://, which runs the handshake and frames inside TLS. Masking is not encryption; the key travels in the frame.
How does the WebSocket handshake work?
The client sends a GET with Upgrade: websocket, Connection: Upgrade, version 13 and a random key. The server replies 101 with Sec-WebSocket-Accept, the base64 SHA-1 of the key plus a fixed GUID, and both sides switch to frames.
Why are WebSocket client frames masked?
So browser script cannot choose the bytes proxies see. 2010 research showed controllable bytes after an upgrade could poison caching proxies.
What is the maximum WebSocket message size?
A frame may declare up to 2^63 bytes and fragmented messages have no protocol limit. Real limits come from your configuration; close with 1009 when exceeded.
What is cross-site WebSocket hijacking?
A hostile page opens a WebSocket to your server from a logged-in user's browser, which attaches your cookies. Without an Origin allowlist the server accepts it as that user.
What does close code 1006 mean?
"Abnormal Closure": the connection ended without a close frame. It is reported locally, never sent, and usually means a timeout, network drop or crash.
Can WebSocket run over HTTP/2 or HTTP/3?
Yes, via Extended CONNECT with :protocol = websocket: RFC 8441 for HTTP/2 and RFC 9220 for HTTP/3. The server answers 200, and support varies.
WebSocket vs Server-Sent Events: which should I use?
Server-Sent Events if only the server pushes, since it is ordinary HTTP with built-in reconnect. WebSocket if the client also sends often.
Where Safeguard fits
Safeguard does not sit in your WebSocket traffic: it does not proxy, inspect or filter frames, or enforce Origin checks and limits. It helps with the software that implements the protocol and the endpoints you expose. Software composition analysis with full transitive inventory, generally available, finds the WebSocket, HTTP/2, HTTP/3 and TLS libraries in your repositories and images, and SBOM generation in SPDX and CycloneDX, generally available, records them. Vulnerability management with EPSS and KEV enrichment and reachability analysis, both generally available, show which projects carry an affected version and whether the vulnerable path is called (using SBOMs to find affected products). Safeguard's first-party DAST runs scoped dynamic tests against verified targets you own; ask which endpoint types your plan covers. Policies and gates, generally available, can stop a build carrying a known vulnerable version.
Fix it without waiting for upstream
Most WebSocket vulnerabilities live not in the RFC but in the library that parses frames, handshakes and compressed messages, and in the HTTP/2, HTTP/3 and TLS code beneath it. Those are dependencies you build and ship.
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
These demos show scanning and assistant workflows, not traffic inspection. You can download Safeguard and follow the CLI docs to try them.
This demo sets up the Safeguard runner on your own machine, approves it with its device code and then scans from the terminal. Pointed at the repository or image behind a real-time service, the same scan lists the WebSocket library and the HTTP and TLS libraries under it, with versions and known vulnerabilities.
Click through it at your own pace, or open it full screen.
This demo is a real CLI session: ask it to fix the lodash CVE in a repository, and it reads the finding and advisory, asks before each command, moves lodash to 4.17.21, runs npm install and npm test with approval, and prints a summary without pushing anything. The same flow applies when the package to bump is a WebSocket library after an advisory like CVE-2024-37890.
Click through it at your own pace, or open it full screen.
This demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. Once connected, you can ask which of your projects include a particular WebSocket library version and get the answer from your own scan data.
Click through it at your own pace, or open it full screen.
Quick check
Your WebSocket server checks the session cookie at the handshake. Does that stop a hostile website from using a logged-in user's session?
No. The browser attaches your cookies to a handshake started by any page. The server must also compare Origin with an allowlist and refuse others with 403, as RFC 6455 section 10.2 recommends.
Client frames are masked with a random key. Is a ws:// connection safe from eavesdroppers?
No. The key travels in the same frame, so anyone who sees it can unmask the data. Masking protects proxies; confidentiality needs TLS via wss://.
Sources
- RFC 6455: The WebSocket Protocol, December 2011
- RFC 7692: Compression Extensions for WebSocket, December 2015
- RFC 7936: Clarifying Registry Procedures for the WebSocket Subprotocol Name Registry, July 2016
- RFC 8307: Well-Known URIs for the WebSocket Protocol, January 2018
- RFC 8441: Bootstrapping WebSockets with HTTP/2, September 2018
- RFC 9220: Bootstrapping WebSockets with HTTP/3, June 2022
- IETF Datatracker: RFC 6455
- WHATWG WebSockets Living Standard
- IANA WebSocket Protocol Registries
- WHATWG HTML Standard: Server-sent events
- W3C WebTransport, Candidate Recommendation Snapshot, 30 July 2026
- gRPC core concepts
- nginx: WebSocket proxying
- nginx: ngx_http_proxy_module
- nginx: command-line parameters
- curl man page
- OpenSSL: openssl-s_client
- ss(8) Linux manual page
- CVE-2024-37890 record
Last reviewed: 23 October 2026. Support for WebSocket over HTTP/2 and HTTP/3 varies by browser, server and proxy; check your software's documentation before relying on it.