Loading learning content...
How does a browser, speaking HTTP to a server, suddenly switch to an entirely different protocol? How does WebSocket coexist with billions of HTTP requests flowing through proxies, load balancers, and CDNs designed for HTTP? The answer lies in one of the most elegant protocol designs in modern networking: the WebSocket handshake.
The handshake is deceptively simple—a single HTTP request followed by a single HTTP response, and suddenly the connection is no longer HTTP at all. But within this apparent simplicity lies careful engineering: cryptographic security measures, compatibility considerations, and precise choreography between client and server.
Understanding the handshake is essential for troubleshooting connection failures, implementing WebSocket servers, and appreciating how new protocols can be deployed over existing infrastructure without breaking the internet.
By the end of this page, you will understand every component of the WebSocket handshake—the client's opening handshake request, the server's response, the security key exchange, and how the connection transitions from HTTP semantics to WebSocket framing. You'll be able to read raw handshake traffic and diagnose common connection failures.
WebSocket's handshake leverages an existing HTTP feature: the Upgrade header. HTTP/1.1 (RFC 2616, later RFC 7230) defines a mechanism allowing clients to request a protocol switch on an existing connection. This wasn't designed specifically for WebSocket—it's a general facility that WebSocket elegantly exploits.
The Upgrade Flow:
Connection: Upgrade and Upgrade: websocket headers101 Switching ProtocolsThis design is ingenious for several reasons:
The HTTP Upgrade mechanism isn't exclusive to WebSocket. HTTP/2 originally used it for cleartext upgrades (h2c), and it's been proposed for other protocols. However, WebSocket remains the most widely deployed use of HTTP Upgrade. Most HTTP/2 deployments use ALPN (TLS negotiation) instead of Upgrade headers.
When a browser executes new WebSocket('ws://example.com/chat'), it constructs and sends an HTTP GET request with specific headers. Let's examine each component of a typical client handshake:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Extensions: permessage-deflate
Let's break down each header:
| Header | Value (Example) | Purpose | Required |
|---|---|---|---|
GET /chat HTTP/1.1 | Request line | Must be GET; path becomes WebSocket endpoint | Yes |
Host | example.com | Standard HTTP host header; required in HTTP/1.1 | Yes |
Upgrade | websocket | Requests upgrade to WebSocket protocol | Yes |
Connection | Upgrade | Indicates connection-level header follows | Yes |
Sec-WebSocket-Key | Base64 random value | Security nonce; prevents caching attacks | Yes |
Sec-WebSocket-Version | 13 | WebSocket protocol version (13 is final RFC) | Yes |
Origin | Connecting page origin | Security; servers can reject cross-origin | Browser-required |
Sec-WebSocket-Protocol | Subprotocol list | Application-level protocol negotiation | Optional |
Sec-WebSocket-Extensions | Extension list | Compression, framing extensions | Optional |
Critical Details:
The Request Method Must Be GET: POST, PUT, or other methods are not permitted. This aligns with the semantics—we're 'getting' a WebSocket connection, not submitting data.
HTTP Version Must Be 1.1 or Higher: HTTP/1.0 doesn't support persistent connections or the Upgrade mechanism. Clients must not attempt WebSocket over HTTP/1.0.
The Sec-WebSocket-Key: This 16-byte, base64-encoded random value is crucial to WebSocket security. We'll examine its purpose in detail shortly, but understand that the server must perform a specific transformation on this key and return the result. This prevents various attacks involving proxies and caching.
The Version Number: As of the final RFC 6455, the WebSocket version is 13. Earlier draft versions used different numbers (8, 13, etc.). Servers should reject unknown versions with a Sec-WebSocket-Version header indicating supported versions.
Headers beginning with 'Sec-' cannot be set by JavaScript using XMLHttpRequest or fetch(). Only the browser can set these headers. This is a security feature preventing malicious scripts from forging WebSocket handshakes. When you see a 'Sec-WebSocket-Key' header, you know it came from a genuine browser WebSocket implementation, not a forged request.
Upon receiving a valid WebSocket handshake request, the server must respond with a specific HTTP response to complete the upgrade:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Extensions: permessage-deflate
Let's examine each component:
| Header | Value (Example) | Purpose | Required |
|---|---|---|---|
101 Switching Protocols | Status line | HTTP status indicating protocol switch | Yes |
Upgrade | websocket | Confirms upgrade to WebSocket | Yes |
Connection | Upgrade | Confirms connection-level upgrade | Yes |
Sec-WebSocket-Accept | Computed value | Proves server understands WebSocket | Yes |
Sec-WebSocket-Protocol | Chosen subprotocol | Server's choice from client's list | If client sent list |
Sec-WebSocket-Extensions | Accepted extensions | Extensions server will use | Optional |
The 101 Status Code:
HTTP status code 101 specifically means 'Switching Protocols.' It's the only valid response for a successful WebSocket upgrade. Any other status code (200, 400, 404, etc.) indicates failure—the connection remains HTTP, and no WebSocket communication occurs.
Subprotocol Selection:
If the client specified Sec-WebSocket-Protocol: chat, superchat, the server must choose at most one from the list or omit the header entirely. The server cannot choose a protocol not offered by the client, and if it sends a Sec-WebSocket-Protocol header, it must exactly match one of the client's offerings.
Subprotocols are application-level concerns—they define what messages mean and how to structure them. Examples include:
graphql-ws for GraphQL subscriptionsmqtt for IoT messagingstomp for message queuingExtension Negotiation:
Extensions modify WebSocket framing behavior. The most common is permessage-deflate (RFC 7692), which compresses message payloads. The server's accepted extensions must be a subset of (or equal to) what the client offered.
The instant after the server sends the final byte of its 101 response, the connection is no longer HTTP. Any subsequent data is WebSocket frames. The server can immediately begin sending frames; it doesn't need to wait for the client. Similarly, the client can begin sending frames immediately after receiving the 101 response.
The Sec-WebSocket-Key and Sec-WebSocket-Accept headers constitute WebSocket's defense against a specific class of attacks. Understanding this mechanism reveals thoughtful protocol design.
The Problem: Cache Poisoning and Cross-Protocol Attacks
Before WebSocket, attacks existed where malicious content could be injected into HTTP caches by exploiting how intermediary proxies handled different protocols. A server speaking a non-HTTP protocol might send data that looks like an HTTP response to a confused proxy, poisoning its cache.
WebSocket's key exchange prevents this by ensuring:
The Algorithm:
1234567891011121314
1. Take the Sec-WebSocket-Key from the client request Example: dGhlIHNhbXBsZSBub25jZQ== 2. Concatenate with the 'magic' GUID: 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 Result: dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11 3. Compute the SHA-1 hash of the concatenated string 4. Base64 encode the 20-byte hash 5. Result is the Sec-WebSocket-Accept value: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Why This Magic GUID?
The GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 is hardcoded in RFC 6455. It's essentially a random value that:
What This Proves:
When a client receives a correct Sec-WebSocket-Accept value, it has proof that:
Sec-WebSocket-Key the client sentYou might wonder why WebSocket uses SHA-1, which is considered cryptographically weak for some applications. Here, SHA-1's role is not to provide cryptographic security (that's TLS's job) but merely to prove computation occurred. The key exchange isn't secret—it happens in cleartext. The goal is preventing confusion with other protocols, not encrypting anything.
Let's trace a complete WebSocket connection from JavaScript code to established connection:
Step 1: Client JavaScript Code
1234567891011121314151617181920
// Browser JavaScriptconst socket = new WebSocket('wss://chat.example.com/room/42'); socket.onopen = function(event) { console.log('Connection established!'); socket.send(JSON.stringify({ type: 'join', user: 'Alice' }));}; socket.onmessage = function(event) { const message = JSON.parse(event.data); console.log('Received:', message);}; socket.onerror = function(error) { console.error('WebSocket error:', error);}; socket.onclose = function(event) { console.log('Connection closed:', event.code, event.reason);};Step 2: Client HTTP Request (sent by browser)
123456789
GET /room/42 HTTP/1.1Host: chat.example.comUpgrade: websocketConnection: UpgradeSec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==Sec-WebSocket-Version: 13Origin: https://myapp.example.comSec-WebSocket-Protocol: json, msgpackSec-WebSocket-Extensions: permessage-deflate; client_max_window_bitsStep 3: Server Validates Request
The server validates:
Step 4: Server HTTP Response
123456
HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=Sec-WebSocket-Protocol: jsonSec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=10Step 5: Connection Upgraded
Immediately after the server sends the final \r\n of its response:
onopen callback firessocket.send()Key Observations:
Sec-WebSocket-Accept value (HSmrc0sMlYUkAGmm5OPpG2HaGWk=) is computed from the client's key (x3JJHMbDL1EzLkh9GBhXDw==)json from the client's two offered protocolsNot all handshakes succeed. Understanding common failure modes is essential for debugging WebSocket applications.
| HTTP Status | Meaning | Common Cause | Resolution |
|---|---|---|---|
| 400 Bad Request | Malformed handshake | Missing required headers, invalid Key format | Check client library; verify headers |
| 403 Forbidden | Access denied | Origin not whitelisted, authentication failed | Check CORS config, include credentials |
| 404 Not Found | Endpoint doesn't exist | Wrong path, WebSocket not configured on path | Verify server routes, check path spelling |
| 426 Upgrade Required | Need different version | Server requires different WebSocket version | Check Sec-WebSocket-Version in response |
| 500 Internal Server Error | Server crashed during handshake | Bug in server handshake code | Check server logs; fix handler code |
| 502 Bad Gateway | Proxy can't connect | Backend server down or unreachable | Check backend availability; review proxy config |
| 503 Service Unavailable | Server overloaded | Too many connections, resource exhaustion | Scale servers; implement connection limits |
Chrome/Firefox DevTools → Network → WS filter shows WebSocket connections. Click a connection to see the handshake request/response headers and all subsequent messages. For failed handshakes, check the HTTP response in the 'Headers' tab—the response status and headers reveal exactly why it failed.
Real-world WebSocket deployments must traverse various intermediaries: reverse proxies, load balancers, CDNs, corporate firewalls. Each presents unique challenges.
The Fundamental Challenge:
Most HTTP intermediaries expect request-response pairs. After forwarding a request, they expect a single response, then the conversation is 'done' for that request. WebSocket's persistent, bidirectional nature violates these assumptions.
Nginx Configuration Example:
1234567891011121314151617181920212223242526272829303132
# WebSocket proxy configurationupstream websocket_servers { server backend1:8080; server backend2:8080; # Note: sticky sessions may be needed if WebSocket servers share state} server { listen 443 ssl; server_name example.com; location /ws/ { proxy_pass http://websocket_servers; proxy_http_version 1.1; # Required: Pass upgrade headers proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # Preserve original client info proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # Timeouts for long-lived connections proxy_read_timeout 86400s; # 24 hours proxy_send_timeout 86400s; # Buffering settings for real-time data proxy_buffering off; }}Key Configuration Points:
Upgrade Header Forwarding: The proxy must forward Upgrade and Connection headers. Without this, the backend never sees the WebSocket handshake.
HTTP/1.1 Required: proxy_http_version 1.1 is critical—HTTP/1.0 doesn't support Upgrade.
Extended Timeouts: Default HTTP timeouts (often 60 seconds) would kill WebSocket connections. Set timeouts appropriate for your application—often hours.
Buffering Disabled: HTTP proxies typically buffer responses. For real-time WebSocket, buffering adds unacceptable latency.
Sticky Sessions: If your WebSocket servers maintain per-connection state that isn't replicated, the load balancer must route reconnecting clients to the same server. IP-based or cookie-based affinity may be needed.
AWS ALB, Google Cloud Load Balancer, and Azure Load Balancer all support WebSocket, but often with limitations. Check idle timeout settings (often 60 seconds by default), which require client-side ping implementation. Some require specific listener configurations. Always test WebSocket functionality; don't assume it 'just works' behind cloud infrastructure.
The WebSocket handshake incorporates several security features, but applications must implement additional protections. Let's examine both the built-in security and required application-level measures.
Built-in Handshake Security:
Required Application-Level Security:
Cross-Site WebSocket Hijacking (CSWSH) is similar to CSRF but for WebSocket. An attacker's page can establish a WebSocket to your server if you don't validate the Origin header. The connection will carry the victim's cookies! Always validate Origin, and consider requiring explicit authentication tokens that aren't automatically sent by browsers.
The WebSocket handshake is a masterfully designed protocol transition mechanism. Let's consolidate the key technical details:
What's Next:
With handshake mechanics thoroughly understood, the next page explores where WebSocket truly shines: concrete use cases ranging from real-time chat and live sports feeds to multiplayer gaming and collaborative editing. You'll see how the full-duplex capability we've established translates into solving real engineering problems.
You now possess detailed understanding of the WebSocket handshake—from the initial HTTP request through protocol upgrade to the security implications. This knowledge enables you to implement WebSocket servers, debug connection failures, and configure infrastructure correctly. Next, we'll explore the diverse use cases that make WebSocket invaluable in modern web development.