Response header
< Sec-Websocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-WebSocket-Accept
Learn how the Sec-WebSocket-Accept header indicates server acceptance of a WebSocket connection upgrade. Understand the handshake process and key validation.
- Direction
- Response
- Category
- WebSocket
- Spec
- RFC 6455 §4.2.2
On this page
TL;DR:
Sec-WebSocket-Acceptis the server’s answer to the client’sSec-WebSocket-Key: base64(SHA-1(key +258EAFA5-E914-47DA-95CA-C5AB0DC85B11)). If it’s missing or wrong, the client drops the HTTP/1.1 upgrade. It proves the server understood the handshake, not who anyone is.
Why This Header Exists
A 101 status on its own isn’t enough to open a WebSocket. Some server or proxy that knows nothing about WebSockets could send one. So the client also checks that the response contains a value computed from the key it just sent. RFC 6455 §4.1 defines that check.
What The Exchange Looks Like
Using the RFC’s sample key dGhlIHNhbXBsZSBub25jZQ==, the server’s response looks like this:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
A blank line ends the HTTP headers. Everything after that on the connection is WebSocket frames.
Because the value depends on the request’s random key, a stale or unrelated HTTP response can’t pass the check by accident. But there’s no secret involved: anyone who sees the key can compute the answer. It isn’t a signature, and it doesn’t mean the server authenticated the client.
How The Server Computes It
Here it is in Node. Run it with node --input-type=module, or save it as an .mjs file:
import { createHash } from 'node:crypto'
const key = 'dGhlIHNhbXBsZSBub25jZQ=='
const guid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
const accept = createHash('sha1').update(key + guid, 'ascii').digest('base64')
console.log(accept)
// RFC 6455 example result: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Hash the base64 key string as sent, not the bytes it decodes to. That’s the most common bug. Also trim surrounding whitespace when you parse the header. RFC 6455 §4.2.2 defines the server side. This snippet only computes the header; a real server has a lot more to do.
What To Check When Handshakes Fail
Start by recomputing the accept value from the key the client actually sent. If your result differs from the server’s, compare the request headers on both sides of any proxy to see whether something changed the key along the way.
If the value matches but the connection still fails, check the rest of the response: status 101, Upgrade: websocket (case-insensitive), and Upgrade in the Connection header. The client also fails the handshake if the server picks a subprotocol or extension it never offered. Keep Content-Length and Transfer-Encoding off a 101; RFC 9110 forbids Content-Length on 1xx responses, and RFC 9112 §6.1 forbids Transfer-Encoding.
The key is just a nonce and the accept value is a fixed transformation of it. For security, you still need TLS (wss://) and your own authentication.
Behind nginx, send the same request straight to the backend and through the proxy. If the backend answers 101 and the edge answers 200 or 502, the hash is fine; look at whether Upgrade and Connection are being forwarded. They’re hop-by-hop headers, so nginx’s WebSocket instructions have you pass them on explicitly.
Related Headers
Frequently asked questions
What is Sec-WebSocket-Accept?
It is the response header a server sends to accept a WebSocket upgrade. Its value is computed from the client key and a public GUID, and the client checks it to confirm the server really speaks WebSocket. It says nothing about who the server or client is.
How is Sec-WebSocket-Accept calculated?
The server concatenates the client key with the fixed WebSocket GUID, hashes the result with SHA-1, and base64-encodes it.
What happens if Sec-WebSocket-Accept is wrong?
The client rejects the upgrade and the WebSocket connection never opens.
Is the magic GUID secret?
No. The GUID is public. The point is protocol validation, not secrecy.
Sources
Related
Content-Disposition Header
Learn how the Content-Disposition header controls whether content displays inline or downloads as an attachment. Set custom filenames for file downloads.
Content-Language Header
Learn how the Content-Language header specifies the natural language(s) of response content. Understand language tags and internationalization best practices.
Content-Length
Learn how Content-Length specifies the body size in bytes. Essential for progress indicators, connection management, and chunked transfer decisions.
Content-Location Header
Learn how Content-Location indicates an alternate URL for returned content. Useful for content negotiation and identifying canonical resource locations.