HTTP 101 Switching Protocols

HTTP 101 Switching Protocols is sent by the server in response to an Upgrade request header from the client, indicating that the server is switching to the protocol specified in the Upgrade header. This is the mechanism that enables WebSocket connections — the client sends an HTTP/1.1 GET with 'Upgrade: websocket' and 'Connection: Upgrade', and the server responds with 101 to confirm the protocol switch. After the 101 response, the connection is no longer HTTP; it speaks whatever protocol was negotiated.

Debug HTTP 101 live
Analyze real 101 behavior — headers, caching, CORS, redirects
Open Inspector →

Try it (live endpoint)

Response includes the status code, standard headers (including Content-Type), and a small diagnostic JSON body describing the request and returned status.

Simulator URL (copy in the app after load — not a normal link):

https://httpstatus.com/api/status/101

Example request:

curl -i "https://httpstatus.com/api/status/101"
Try in playground

Meaning

The server is switching protocols as requested by the client via the Upgrade header (e.g., upgrading to WebSocket).

What it guarantees
  • An interim response was provided before the final response.
What it does NOT guarantee
  • The request has succeeded or failed.
  • Clients should treat this as a final outcome.

When to use this status

  • Use only when it accurately describes the outcome for clients and tooling.

When NOT to use this status (common misuses)

Critical headers that matter

Tool interpretation

Browsers
Treats as success; caches/revalidates based on headers and validators.
API clients
Deserializes per Content-Type; conditional requests use validators when implemented.
Crawlers / SEO tools
Indexes depending on headers and canonical stability; caches behavior via validators and cache directives.
Uptime monitors
Typically marks success; advanced checks may flag header anomalies or latency.
CDNs / reverse proxies
Caches/revalidates based on Cache-Control, ETag, and Vary; compression and content-type affect behavior.

Inspector preview (read-only)

On this code, Inspector focuses on semantics, headers, and correctness warnings that commonly affect clients and caches.

Signals it will highlight
  • Status semantics vs method and body expectations
  • Header sanity (Content-Type, Cache-Control, Vary) and evidence completeness
Correctness warnings
No common correctness warnings are specific to this code.

Guided Lab outcome

  • Reproduce HTTP 101 Switching Protocols using a controlled endpoint and capture the full exchange.
  • Practice distinguishing status semantics from transport issues (redirects, caching, proxies).

Technical deep dive

Defined in RFC 7231 Section 6.2.2, the 101 response MUST include an Upgrade header field indicating the protocol(s) being switched to. The most common use is the WebSocket handshake (RFC 6455): the client sends 'Sec-WebSocket-Key' (a base64-encoded random value), and the server responds with 'Sec-WebSocket-Accept' (the key concatenated with a magic GUID and SHA-1 hashed). This proves the server understands WebSocket, not just any Upgrade request. HTTP/2 connections can also be initiated via 101 using 'Upgrade: h2c' for cleartext HTTP/2, though this is rare since most HTTP/2 uses ALPN during TLS negotiation. Important: 101 is a hop-by-hop status — proxies must handle the Upgrade themselves or tunnel the connection.

Real-world examples

WebSocket chat application
A real-time chat app initiates a WebSocket connection. The browser sends GET /chat with Upgrade: websocket headers. The server validates the origin, checks authentication via cookies or tokens in the query string, and responds with 101. The TCP connection then carries bidirectional WebSocket frames for real-time messaging.
Server-Sent Events fallback to WebSocket
A monitoring dashboard first attempts a WebSocket connection for bidirectional communication. If the server responds 101, it uses WebSocket. If a proxy blocks the upgrade and returns 200, it falls back to SSE (EventSource) for server-to-client streaming.
HTTP/2 cleartext upgrade
An internal microservice initiates an h2c (HTTP/2 cleartext) connection by sending GET / with 'Upgrade: h2c' and an HTTP2-Settings header. The server responds 101 and switches to HTTP/2 framing on the same TCP connection without TLS.

Framework behavior

Express.js (Node)
Express uses the 'ws' or 'socket.io' library for WebSocket support. The upgrade happens at the http.Server level: server.on('upgrade', (req, socket, head) => { wss.handleUpgrade(req, socket, head, ws => wss.emit('connection', ws, req)); }). Express middleware doesn't run for upgrade requests unless explicitly wired.
Django / DRF (Python)
Django Channels handles WebSocket upgrades via ASGI. The Daphne or Uvicorn server processes the 101 handshake, then routes to a WebSocket consumer: class ChatConsumer(AsyncWebsocketConsumer): async def connect(self): await self.accept(). Django's WSGI mode cannot handle WebSockets.
Spring Boot (Java)
Spring WebSocket uses @EnableWebSocket or @EnableWebSocketMessageBroker. The DefaultHandshakeHandler processes the 101 negotiation. Spring Security can intercept the handshake via a HandshakeInterceptor to validate auth tokens before the upgrade completes.
FastAPI (Python)
FastAPI handles WebSocket natively: @app.websocket('/ws') async def ws_endpoint(websocket: WebSocket): await websocket.accept(). Uvicorn handles the 101 upgrade at the ASGI level. Starlette's WebSocket class manages the handshake and frame parsing.

Debugging guide

  1. Use browser DevTools → Network tab → filter 'WS' to see the 101 handshake and WebSocket frames
  2. Check for proxy interference — nginx needs 'proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";'
  3. Verify the Sec-WebSocket-Accept value matches: base64(SHA-1(Sec-WebSocket-Key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))
  4. If connections drop immediately after 101, check that the server isn't closing the socket — enable WebSocket ping/pong frames
  5. Test with: curl -i -N -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGVzdA==' http://localhost:8080/ws

Code snippets

Node.js
const WebSocket = require('ws');
const http = require('http');

const server = http.createServer();
const wss = new WebSocket.Server({ noServer: true });

server.on('upgrade', (request, socket, head) => {
  // Authenticate before completing the 101 handshake
  const token = new URL(request.url, 'http://localhost')
    .searchParams.get('token');
  if (!isValid(token)) {
    socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
    socket.destroy();
    return;
  }
  wss.handleUpgrade(request, socket, head, (ws) => {
    wss.emit('connection', ws, request);
  });
});
Python
import asyncio
import websockets

async def handler(websocket, path):
    async for message in websocket:
        response = f'Echo: {message}'
        await websocket.send(response)

async def main():
    # Server sends 101 Switching Protocols automatically
    async with websockets.serve(
        handler, 'localhost', 8765,
        extra_headers={'X-Server': 'MyApp'}
    ):
        await asyncio.Future()  # run forever

asyncio.run(main())
Java (Spring)
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(
            WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyHandler(), "/ws")
            .setAllowedOrigins("https://myapp.com")
            .addInterceptors(new AuthHandshakeInterceptor());
    }
}

public class MyHandler extends TextWebSocketHandler {
    @Override
    protected void handleTextMessage(
            WebSocketSession session, TextMessage msg) {
        session.sendMessage(
            new TextMessage("Echo: " + msg.getPayload()));
    }
}
Go
package main

import (
	"log"
	"net/http"
	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool {
		return r.Header.Get("Origin") == "https://myapp.com"
	},
}

func wsHandler(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil) // sends 101
	if err != nil {
		log.Println("Upgrade failed:", err)
		return
	}
	defer conn.Close()
	for {
		_, msg, err := conn.ReadMessage()
		if err != nil { break }
		conn.WriteMessage(websocket.TextMessage, msg)
	}
}

FAQ

Can HTTP 101 be used for protocols other than WebSocket?
Yes. The Upgrade mechanism is generic — it can switch to any protocol. Besides WebSocket (Upgrade: websocket), it's used for HTTP/2 cleartext (Upgrade: h2c) and was historically proposed for TLS upgrades (Upgrade: TLS/1.0). However, WebSocket is by far the most common use case in practice.
What happens if a proxy doesn't support the Upgrade mechanism?
The proxy will either strip the Upgrade/Connection headers (breaking the handshake) or return a regular HTTP response instead of 101. The client sees a non-101 response and the WebSocket connection fails. This is why WebSocket over TLS (wss://) is preferred — it tunnels through proxies that inspect HTTP traffic.
Is HTTP 101 used in HTTP/2 or HTTP/3?
HTTP/2 has its own mechanism for WebSocket via RFC 8441 (Extended CONNECT). Instead of 101, the server responds with 200 to a CONNECT request with :protocol pseudo-header set to 'websocket'. HTTP/3 uses a similar CONNECT approach. The 101 mechanism is HTTP/1.1 only.
How do you authenticate WebSocket connections before the 101 upgrade?
Three common approaches: (1) Pass a token in the query string: ws://host/path?token=xxx — validated during the upgrade handshake. (2) Use cookies — the browser automatically sends cookies with the upgrade request. (3) Use a two-step flow — authenticate via REST first, get a short-lived ticket, then pass the ticket in the WebSocket URL.

Client expectation contract

Client can assume
    Client must NOT assume
    No common correctness warnings are specific to this code.
    Retry behavior
    Retries are generally unnecessary; treat as final unless domain rules require revalidation.
    Monitoring classification
    Server error
    Use payload and header checks to avoid false positives; cacheability depends on Cache-Control/ETag/Vary.

    Related status codes

    100 Continue
    The server has received the request headers, and the client should proceed to send the request body (used with Expect: 100-continue header).
    102 Processing
    The server has received and is processing the request, but no response is available yet. Used in WebDAV to prevent client timeouts on long operations.

    Explore more

    Related guides
    Related tools
    Related utilities