Nginx代理WebSocket握手返回400错误,根本原因是默认配置未透传Upgrade关键请求头,需添加三行配置:proxy_http_version 1.1、proxy_set_header Upgrade $http_upgrade、proxy_set_header Connection "upgrade"。

WebSocket 握手阶段返回 400 Bad Request,通常不是前端代码问题,而是服务端在 HTTP 升级(Upgrade)过程中校验失败或响应异常。由于握手是 HTTP 请求,error_log 是定位这类问题最直接的线索——它能暴露 Nginx、PHP-FPM、Swoole 或其它中间件在解析请求头、处理协议升级时的具体报错。
确认 error_log 的实际输出位置和级别
很多 400 报错不进日志,是因为日志级别太高或路径配置错误:
- Nginx 的
error_log需设为info或更低(如debug),才能记录握手阶段的协议细节(例如 “client sent invalid upgrade header”); - PHP-FPM 的
error_log和log_level = notice才会记录 FastCGI 转发异常,比如超大请求头被截断; - Swoole 的
swoole.error_log配置需开启,且log_level≥ 2(WARNING)才可能捕获handshake failed类错误; - 检查是否启用了
log_not_found off(Nginx),避免把 400 当作 404 忽略。
重点排查握手请求头中的常见违规项
WebSocket 握手本质是带特定 Header 的 HTTP GET 请求,error_log 常会提示类似 “invalid Sec-WebSocket-Key” 或 “missing Upgrade header”:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
-
Sec-WebSocket-Key缺失、长度不对(必须是 base64 编码的 16 字节随机值)或含非法字符; -
Upgrade: websocket和Connection: Upgrade大小写错误或拼写错误(如写成Websocket或upgarde); - 请求头总大小超限(Nginx 默认
large_client_header_buffers 4 8k),含多余 Cookie 或自定义 Header 时易触发 400 且 error_log 提示 “header too large”; - 使用了不支持的
Sec-WebSocket-Version(如传了 7 或 14,而服务端只认 13)。
检查反向代理层对 Upgrade 请求的透传配置
如果 WebSocket 经过 Nginx / Apache / CDN,400 很可能发生在代理环节,error_log 会明确指出:
- Nginx 缺少
proxy_http_version 1.1和proxy_set_header Upgrade $http_upgrade,导致 Upgrade 头丢失; -
proxy_buffering on与 WebSocket 冲突,某些版本会在 error_log 中报 “upstream sent no valid HTTP/1.0 header”; - Apache 的
mod_proxy_wstunnel未启用,或ProxyRequests Off缺失,error_log 可能显示 “connection refused” 或 “no protocol handler”; - CDN(如 Cloudflare)默认不支持 WebSocket,若未开启“WebSockets”开关,会直接返回 400 且 origin error_log 无记录——此时需看边缘节点日志或关闭 CDN 测试。
用 curl 模拟握手 + 实时观察 error_log
绕过浏览器,用最小化请求验证并触发日志:
- 运行:
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" -H "Sec-WebSocket-Version: 13" http://localhost:9501/ws; - 同时在另一终端执行:
tail -f /var/log/nginx/error.log /var/log/php-fpm/www-error.log; - 观察哪一层最先报错:是 Nginx 拒绝升级?PHP-FPM 无法连接后端?还是 Swoole 解析 key 失败?错误行时间戳和进程 ID(如
[error] 12345#0:)可帮你快速定位模块; - 注意:若日志中出现
recv() failed (104: Connection reset by peer),大概率是后端进程崩溃或未监听,而非协议问题。
不复杂但容易忽略:400 Bad Request 在 WebSocket 握手阶段,几乎总是服务端某层主动拒绝,而不是网络中断。紧盯 error_log 中第一个非通用错误(比如具体指出哪个 header 不合法),比反复改前端代码更高效。

















