WebSocket握手失败主因是Nginx未透传Upgrade头、TLS验证失败或Swoole监听地址为127.0.0.1;需检查proxy_set_header Upgrade $http_upgrade与Connection "upgrade"、wss证书配置及监听IP是否为0.0.0.0。

WebSocket 握手失败时 Connection: Upgrade 被丢弃或改写
PHP 本身不原生支持 WebSocket 客户端(fsockopen 或 curl 都不能直接完成 WebSocket 协议握手),多数人用 PHP 做的是服务端(如 ReactPHP、Swoole)或通过 HTTP 客户端“模拟”发起握手请求。此时常见问题是:你发出了正确的 Upgrade: websocket 和 Connection: Upgrade 头,但服务端收不到——因为中间代理、Nginx、甚至某些 PHP HTTP 客户端库(如旧版 curl)会过滤或重写这些头。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 用
telnet或nc手动发原始 HTTP 请求,确认服务端是否响应101 Switching Protocols,排除服务端配置问题 - Nginx 反向代理 WebSocket 必须显式透传升级头:
proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade";,漏掉任一都会导致握手失败 - 若用
curl模拟握手,确保使用CURLOPT_HTTPHEADER显式设置Connection: Upgrade,且不要启用CURLOPT_FOLLOWLOCATION(302 重定向会丢失升级语义)
PHP 客户端发的 Sec-WebSocket-Key 格式不合法或未 Base64 编码
WebSocket 握手要求客户端生成一个 16 字节随机值,再 Base64 编码后放入 Sec-WebSocket-Key。很多人直接用 md5(time()) 或 rand() 拼字符串,结果长度不对、含非法字符,或忘记 Base64 编码——服务端校验失败就直接返回 400。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 生成 key 必须用
random_bytes(16)+base64_encode(),例如:base64_encode(random_bytes(16)) - 检查请求头中该字段是否含换行、空格或中文字符(比如误用了
mb_convert_encoding或拼接了调试字符串) - 服务端计算
Sec-WebSocket-Accept是对key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"做 SHA1 再 Base64,可用此逻辑反推验证你发的 key 是否能被正确解析
SSL/TLS 层未对齐:用 ws:// 连 wss:// 服务,或证书验证失败
PHP 的 stream_socket_client() 或 curl 默认不跳过 TLS 验证。如果你连的是 wss://example.com,但没配 CURLOPT_SSL_VERIFYPEER => false(开发环境)或没提供正确 CA bundle(生产环境),连接会在 TLS 握手阶段就断开,根本到不了 HTTP 升级环节——表现为超时或 SSL certificate problem 错误。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 先用
openssl s_client -connect example.com:443 -servername example.com测试 TLS 是否通;不通就别急着查 WebSocket 头 -
curl必须设置:CURLOPT_PROTOCOLS => CURLPROTO_WSS(新版 cURL)或至少CURLOPT_URL => "wss://...",否则可能降级为 http - 用
stream_context_create()时,ssl选项里要明确加"verify_peer" => false(仅测试)或"cafile" => "/path/to/cacert.pem"
Swoole/ReactPHP 启动后监听地址不可达或端口被占
很多 PHP WebSocket 服务跑在 Swoole 上,启动无报错,但客户端始终连接拒绝(Connection refused)。这往往不是握手问题,而是服务压根没起来,或监听在 127.0.0.1 却从外网连,或防火墙/SELinux 拦截了端口。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 用
netstat -tuln | grep :端口号确认进程是否真在监听,注意看Local Address是*:端口还是127.0.0.1:端口 - Swoole
WebSocket\Server构造时第二个参数默认是127.0.0.1,要对外服务必须显式写'0.0.0.0' - Linux 上检查
sudo ss -tulnp | grep php,确认是不是被其他 php-fpm 或残留进程占了端口
真正卡住排查的,往往是 Nginx 透传头缺失、TLS 证书链不全、或 Swoole 监听地址写死了 127.0.0.1 却以为自己在公网可访问——这些问题不会报“WebSocket 握手失败”,只会让连接在更底层就断掉。










