400 Bad Request本质是WebSocket握手请求头缺失、非法或被截断,非后端逻辑错误;需检查Upgrade/Connection头透传、Sec-WebSocket-Key合规性、请求头大小超限及CDN/WAF开关状态。

WebSocket 握手阶段返回 400 Bad Request,基本可锁定为请求头缺失、非法或被中间环节截断——不是后端逻辑出错,而是“升级协议”的关键信息没递到。排查要分层聚焦,先确认是 Nginx 自身拦截,还是透传失败导致后端拒收。
确认错误来源:是 Nginx 拦截还是后端返回
打开 Nginx 错误日志(如 /var/log/nginx/error.log),搜索对应时间点的 400 请求记录:
- 若出现 client sent too large header、invalid host name、request line is too large 等提示 → 是 Nginx 主动拒绝,问题在代理层
- 若日志中无明显线索,但后端 access_log 或 error_log 明确记录了握手失败(如 invalid Sec-WebSocket-Key、upgrade token not found)→ 请求虽转发成功,但关键头丢失或不合规
- 用 curl 直连后端(绕过 Nginx)验证是否正常:如
curl -i -H "Upgrade: websocket" -H "Connection: Upgrade" http://backend:8080/ws,能返回 101 则确认问题出在代理配置
检查 WebSocket 必需请求头是否完整透传
Nginx 默认不转发 Upgrade 和 Connection 头,必须显式配置,且大小写敏感:
-
Upgrade值必须为小写 websocket(不是 Websocket、WS 或 ws) -
Connection值必须为 Upgrade(首字母大写,其余小写) - 确保
proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade";写在location块顶层,不要放在 if 块内(Nginx 的 if 是伪指令,易跳过) - 验证
$http_upgrade是否有值:在 log_format 中加入"$http_upgrade" "$http_connection",观察实际日志;若为空,说明客户端根本没发 Upgrade 头(如旧浏览器、测试工具误用 HTTP/1.0)
排查请求头大小波动引发的间歇性超限
Nginx 默认 large_client_header_buffers 4 8k,而 Cookie、Origin、自定义 Header 长度可能随登录态、设备 UA、A/B 参数动态变化,某次刚好超限就触发 400:
- 在 error_log 中搜索 client sent too large request header 或 header too large
- 临时调大缓冲验证:
large_client_header_buffers 8 16k,观察是否消失 - 长期解法:前端精简 WebSocket 路径下的 Cookie(如设
Path=/ws; SameSite=Strict),避免全站 Cookie 泄露;删除无用自定义 Header - 注意:若启用 HTTP/2,还需同步设置
http2_max_field_size 64k和http2_max_header_size 512k
验证后端服务握手响应一致性
偶发 400 很可能来自后端资源紧张时的行为异常:
- 检查后端 error_log(如 Swoole、Node.js 的 warning 及以上级别),看是否有 invalid Sec-WebSocket-Key、timeout during handshake 等提示
- Sec-WebSocket-Key 必须是 base64 编码的 16 字节随机值(编码后长度为 24 字符,末尾带 =),不能含空格、控制字符或非 base64 字符
- 确认后端未因高并发跳过校验,或因超时提前返回空响应;适当调大后端握手超时阈值(如 Swoole 的
websocket.handshake_timeout) - 若使用 CDN 或 WAF,确认其 WebSocket 开关已开启,且未静默过滤 Upgrade/Connection 头


















