Understanding HTTP 101 Switching Protocols: How the WebSocket Handshake Actually Works
101 Switching Protocols is a status code you'll almost never write by hand, yet it's responsible for practically every real-time feature on the modern web — chat, live dashboards, multiplayer games, streaming updates. Any time a connection starts as plain HTTP and ends up as a WebSocket, a 101 happened in between. This post covers what the code actually promises, walks through the WebSocket handshake it powers, and covers a wrinkle that trips people up: HTTP/2 and HTTP/3 don't use this mechanism at all, even for WebSockets.
What Is a 101?
A 101 tells the client the server agrees to change what protocol the current connection speaks — right now, on this same TCP connection, without closing and reopening it.
The 101 (Switching Protocols) status code indicates that the server understands and is willing to comply with the client's request, via the Upgrade header field (Section 7.8), for a change in the application protocol being used on this connection. The server MUST generate an Upgrade header field in the response that indicates which protocol(s) will be in effect after this response. — RFC 9110, Section 15.2.2
In plain English: the client asked (via an Upgrade header on its request) to switch this connection to a different protocol, and the server is saying yes. The response itself must name the protocol being switched to — a bare 101 with no Upgrade header would leave the client not knowing what just happened to its own connection.
Request, Then Response: The Upgrade Dance
A protocol switch is a negotiation, not a unilateral move by either side. The client proposes; the server accepts, declines, or ignores:
A client MAY send a list of protocol names in the Upgrade header field of a request to invite the server to switch to one or more of the named protocols, in order of descending preference, before sending the final response. A server MAY ignore a received Upgrade header field if it wishes to continue using the current protocol on that connection. Upgrade cannot be used to insist on a protocol change. — RFC 9110, Section 7.8
That last line matters: Upgrade is always a request, never a demand, from the client's side. A server has no obligation to honor it — it can just answer the request normally on the existing protocol. (The server-side mirror of "I insist on a different protocol" is 426, not 101 — see below.)
A hypothetical exchange, straight from the RFC:
GET /hello HTTP/1.1
Host: www.example.com
Connection: upgrade
Upgrade: websocket, IRC/6.9, RTA/x11HTTP/1.1 101 Switching Protocols
Connection: upgrade
Upgrade: websocket
[... data stream switches to websocket with an appropriate response
(as defined by new protocol) to the "GET /hello" request ...]Note that the original request doesn't just vanish once the protocol switches — per the RFC, the server is still expected to answer it, "as if it had received its equivalent within the new protocol," without making the client resend anything.
The WebSocket Handshake, Header by Header
The single most common real-world use of 101 is upgrading an HTTP connection to WebSocket, as defined by RFC 6455. The client's request and the server's response look almost identical to the generic example above, but with WebSocket-specific headers added:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-WebSocket-Key and Sec-WebSocket-Accept exist purely to prove both sides are speaking WebSocket on purpose, not to encrypt or authenticate anything. The server takes the client's key, appends a fixed GUID (258EAFA5-E914-47DA-95CA-C5AB0DC85B11), SHA-1 hashes the result, and base64-encodes it into Sec-WebSocket-Accept. If the value doesn't match what the client independently computes, the client is required to abort the connection — this defends against caches and misconfigured proxies that might otherwise reply to a WebSocket handshake as if it were an ordinary HTTP request.
Handling it in Node.js
You essentially never construct this 101 response yourself. Libraries like ws do the handshake validation and header math for you — you hook into Node's upgrade event and hand the request off:
import { createServer } from "node:http";
import { WebSocketServer } from "ws";
const server = createServer();
const wss = new WebSocketServer({ noServer: true });
wss.on("connection", (ws) => {
ws.on("message", (data) => {
console.log("received:", data.toString());
});
});
server.on("upgrade", (request, socket, head) => {
wss.handleUpgrade(request, socket, head, (ws) => {
wss.emit("connection", ws, request);
});
});
server.listen(8080);handleUpgrade() is what actually writes the 101 Switching Protocols response with the correctly computed Sec-WebSocket-Accept — the library owns the RFC 6455 handshake logic so your application code never touches raw status lines.
Deploying it
WebSocket connections are long-lived by nature, which matters for where you run them. Vercel Functions natively support WebSocket connections, but an established connection stays pinned to a single Function instance for its maximum duration — if your app needs state shared across multiple connections or instances, you'd pair it with an external store (Redis, for example) or use a dedicated realtime platform rather than in-memory state on the function.
HTTP/2 and HTTP/3 Don't Use This At All
Here's the wrinkle: everything above — the Upgrade header, the Connection: Upgrade handshake, the 101 status line — is an HTTP/1.1 mechanism. It doesn't exist in HTTP/2 or HTTP/3. Those protocols already multiplex many logical streams over one connection, so "upgrade this one connection to a different protocol" doesn't map cleanly onto their framing.
Instead, WebSocket over HTTP/2 uses Extended CONNECT, defined in RFC 8441. The client sends a CONNECT request carrying a :protocol pseudo-header set to websocket, rather than an Upgrade header — and per that RFC, the Connection and Upgrade header fields from RFC 6455 "MUST NOT be included" in that request, because the :protocol pseudo-header supersedes them. There's no 101 in this flow at all; the tunnel is established as a CONNECT response instead. RFC 9220 defines the equivalent mechanism for HTTP/3.
Practically, this is invisible to you if you're using a library like ws or a browser's WebSocket API — they negotiate the right mechanism for whatever HTTP version the connection ends up using. But it's worth knowing that "101" and "WebSocket handshake" aren't actually synonymous — 101 is specifically the HTTP/1.1 answer to that question.
101 vs 426
| Code | Meaning | Who initiates | Standard? |
|---|---|---|---|
| 101 Switching Protocols | Server agrees to a protocol change the client requested | Client proposes via Upgrade; server accepts | Yes (RFC 9110) |
| 426 Upgrade Required | Server refuses to proceed on the current protocol and demands a change | Server insists via Upgrade on an error response | Yes (RFC 9110) |
They're two ends of the same negotiation: 101 is the server saying yes to an offer, 426 is the server making its own demand. See our 426 Upgrade Required post for the other half of that story.
Common Pitfalls
- Hand-rolling the
Sec-WebSocket-Acceptcomputation. It's a fixed algorithm (SHA-1 of the client's key plus a magic GUID, base64-encoded), but getting it subtly wrong produces a connection that looks fine in casual testing and then fails against strict clients. Use a maintained library instead of reimplementing RFC 6455's handshake math. - Forgetting the response needs an
Upgradeheader. The RFC makes it mandatory — a101without one is malformed, and a mismatched or missingUpgradeheader field is exactly the kind of thing well-behaved clients are required to check for. - Assuming 101 always means WebSocket. It's a generic protocol-switch mechanism; WebSocket is just its most common tenant. HTTP/1.1-to-HTTP/2 upgrades (the
h2ctoken) also used it historically, but RFC 9113 formally deprecated that path — theh2cUpgrade token "MUST NOT be sent by a client or selected by a server." Cleartext HTTP/2 today is negotiated "with prior knowledge" instead, skipping 101 entirely. - Expecting a 101 handshake path on an HTTP/2 or HTTP/3 connection. If you're debugging why a WebSocket library isn't sending an
Upgraderequest over an HTTP/2 connection, that's expected — it's using Extended CONNECT instead, per RFC 8441.
Wrapping Up
101 is the server's "yes" to a protocol change the client proposed — most visibly, the handshake that turns an ordinary HTTP request into a long-lived WebSocket connection. The rules of thumb:
- 101 means the server agreed to switch protocols on this connection; the client always proposes first via
Upgrade - A 101 response is required to include its own
Upgradeheader naming the new protocol - Application code essentially never sends this by hand — WebSocket libraries own the handshake
- It's an HTTP/1.1-only mechanism; HTTP/2 and HTTP/3 use Extended CONNECT (RFC 8441 / RFC 9220) instead
For more, see our pages on 101 Switching Protocols and 426 Upgrade Required, our 100 Continue post for the other 1xx code most developers meet, and the rest of the 1xx informational codes.