# 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.

Source: https://howhttpworks.com/headers/sec-websocket-accept
Last reviewed: 2026-10-05

> **TL;DR:** `Sec-WebSocket-Accept` is the server's answer to the client's `Sec-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](https://www.rfc-editor.org/rfc/rfc6455#section-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
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:

```javascript
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](https://www.rfc-editor.org/rfc/rfc6455#section-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](https://www.rfc-editor.org/rfc/rfc9110#section-8.6) forbids Content-Length on 1xx responses, and [RFC 9112 §6.1](https://www.rfc-editor.org/rfc/rfc9112#section-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](https://nginx.org/en/docs/http/websocket.html) have you pass them on explicitly.

## Related Headers

- [Sec-WebSocket-Key](https://howhttpworks.com/headers/sec-websocket-key)
- [Upgrade](https://howhttpworks.com/headers/upgrade)
- [101 Switching Protocols](https://howhttpworks.com/status-codes/101)
