WebSocket 建立连接时 426 Upgrade Required 问题

FreeGuideOnline 最新 2026-07-05

http GET /chat HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13


如果服务器同意切换,它必须返回状态码 `101 Switching Protocols`,并带有对应的响应头。而 **426 Upgrade Required** 出自 HTTP/1.1 协议,最初的定义是:客户端应当切换到协议清单中列出的某个协议,才能访问所请求的资源。**在 WebSocket 场景中,426 出现意味着服务器或某个中间节点虽然收到了你的升级请求,但它自身没有能力完成协议升级,而是把这个状态码当作一种拒绝方式返回给了客户端。**

---

### 426 错误的主要触发场景

根据实际部署经验,问题极少出在客户端代码,绝大多数都是服务器端或中间代理配置不当造成的。以下是最常见的三个场景。

#### 1. 反向代理或负载均衡器未转发 WebSocket 升级头

这是最高频的原因。如果后端是一个真正的 WebSocket 服务(比如 Node.js、Spring、Go 等),而前端请求是通过 Nginx、Apache 或云负载均衡器(如 AWS ALB、ELB)转发的,这些中间层默认可能不会处理 `Upgrade` 和 `Connection` 头,导致后端收到的只是一个普通 HTTP 请求,从而返回 426 或者直接 400/404。

#### 2. 服务器框架或库没有正确挂载 WebSocket 处理

比如你在 Flask、Express、ASP.NET 等应用里只定义了普通 HTTP 路由,但没有将 `/ws` 这样的路径委托给 WebSocket 处理器。服务器会将它当作一个普通 HTTP 请求,并发现客户端指明了 `Upgrade: websocket`,于是返回 426 表示“请升级,但我这里不支持”。

#### 3. HTTP/2 与 WebSocket 的兼容性问题

HTTP/2 不支持逐跳头中的 `Connection` 字段。如果客户端通过 HTTP/2 发出升级请求,而代理没有正确处理(例如将 HTTP/2 转换成 HTTP/1.1 再传给后端),就可能产生 426。部分浏览器在 HTTP/2 下甚至会直接禁用 WebSocket 握手。

---

### 解决方案一:修复 Nginx 反向代理配置

绝大多数生产环境使用 Nginx 作为 WebSocket 网关。要使升级头正确传递,必须在 `location` 块中显式设置以下指令:

```nginx
location /ws/ {
    proxy_pass http://backend_server;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 86400s;  # 防止长连接被意外切断
}

配置要点解释:

  • proxy_http_version 1.1:HTTP/1.1 才支持分块传输和协议升级。
  • proxy_set_header Upgrade $http_upgrade:将客户端发来的 Upgrade 头原样传给后端。
  • proxy_set_header Connection "upgrade":硬编码为 "upgrade",而不是使用变量 $connection_upgrade,因为某些版本 Nginx 中该变量可能为空。
  • 防止超时断开proxy_read_timeout 设长一些(单位秒),因为 WebSocket 连接可能长时间没有数据。

配置完成后务必执行 nginx -t && nginx -s reload


解决方案二:Apache httpd 的 mod_proxy_wstunnel

如果负载均衡是 Apache,需要使用 mod_proxy_wstunnel 模块(确保已启用)。在虚拟主机或位置配置中添加类似规则:

ProxyPass "/ws/"  "ws://backend:8080/"
ProxyPassReverse "/ws/"  "ws://backend:8080/"

对于 WSS(加密连接),后端地址改为 wss://,且确保 SSL 终结后的请求头正确传递。同时注意 Apache 版本需要支持此模块,一般 2.4.5 以上版本内置。


解决方案三:IIS / ARR (Application Request Routing) 配置

微软的 ARR 3.0 和 URL 重写模块可用于 WebSocket 反向代理。需要在 ARR 层面关闭“响应缓冲”(Response Buffering),否则 WebSocket 帧会被阻塞。

关键步骤:

  1. 在 IIS 管理器中,选择服务器级或站点级的“Application Request Routing Cache”。
  2. 点击右侧操作面板的“Server Proxy Settings”。
  3. 将“Response buffer threshold (KB)”设置为 0,以禁用缓冲。
  4. 同时确保 URL 重写规则将流量转发给后端 WebSocket 服务。

此外,后端应用程序池必须启用 WebSocket 支持(在 IIS 的“WebSocket”功能中打开)。


解决方案四:检查后端应用代码的挂载方式

不同语言和框架开启 WebSocket 的方式也需要一一排查。

Node.js (Express + ws 库)
最常见的错误是忘记将 HTTP 服务器的 upgrade 事件交给 ws 处理。

const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

server.on('upgrade', (request, socket, head) => {
    wss.handleUpgrade(request, socket, head, (ws) => {
        wss.emit('connection', ws, request);
    });
});

Spring Boot (Java)
必须使用 @EnableWebSocket 并实现 WebSocketConfigurer,或者使用 STOMP 子协议。如果只是添加了普通 @Controller,没有注册 WebSocket 端点,请求就会以 426 结束。

Go (gorilla/websocket)
在 HTTP 处理器中必须调用 Upgrader.Upgrade(w, r, nil) 并检查返回的错误,而不是仅在路由里返回普通响应。

var upgrader = websocket.Upgrader{}
func wsHandler(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println(err)
        return
    }
    defer conn.Close()
    // ... 读写消息
}