WebSocket握手失败主因是Nginx未正确配置proxy_set_header Upgrade $http_upgrade和Connection "upgrade",且必须配合proxy_http_version 1.1、足够长的proxy_read_timeout(如86400)及proxy_send_timeout,路径映射、SSL证书链和worker进程残留亦需排查。

WebSocket连接在Nginx后握手失败,90%不是代码问题,而是proxy_set_header Upgrade和proxy_set_header Connection这两行没配对、写错或被覆盖。
为什么Upgrade头总是变成null
Nginx默认不透传Upgrade和Connection这两个非标准HTTP头,哪怕你写了proxy_set_header,如果proxy_http_version不是1.1,或者Connection值写成close或"Upgrade"(首字母大写),后端收到的$http_upgrade就是空字符串。
-
proxy_http_version 1.1必须显式声明——HTTP/1.0不支持协议升级 -
proxy_set_header Upgrade $http_upgrade中$http_upgrade是Nginx内置变量,不能手写websocket -
proxy_set_header Connection "upgrade"的值必须小写upgrade,且带英文双引号(否则Nginx会当作变量解析) - 若location里有多个
proxy_set_header块,后面会覆盖前面,确保这两行在proxy_pass之前且未被其他配置覆盖
proxy_read_timeout设太短导致60秒准时断连
WebSocket长连接空闲时,Nginx默认60秒就主动断开(proxy_read_timeout默认值),浏览器报WebSocket is already in CLOSING or CLOSED state,看起来像握手失败,其实是连接维持不住。
- 生产环境建议设为
86400(24小时),足够覆盖绝大多数用户静默期 -
proxy_send_timeout也要同步调大,否则服务端发心跳帧超时也会断连 - 不要只改
http块全局值——必须在具体location里重写,否则无效 - 如果后端本身有心跳机制(如
pingInterval),Nginx超时值应略大于它,避免抢断
路径映射错误让请求根本没走到WebSocket handler
前端连wss://a.com/ws/chat,Nginx配置location /ws { proxy_pass http://backend:3000; },结果后端收不到请求,日志里全是404——因为proxy_pass末尾没加/,Nginx会把/ws前缀原样拼到后端地址上,实际请求变成了http://backend:3000/ws/chat,而你的WebSocket路由可能是/chat。
- 正确写法一(推荐):
location /ws { proxy_pass http://backend:3000/; }(注意末尾/)→ 请求变为http://backend:3000/chat - 正确写法二(精确匹配):
location ^~ /ws/chat { proxy_pass http://backend:3000/chat; } - 别用
rewrite做路径替换——容易漏掉Upgrade头,且增加调试复杂度 - Spring Boot等框架对路径敏感,Controller上的
@MessageMapping("/chat")必须和最终到达的路径完全一致
SSL证书链不全引发静默断连
浏览器控制台没报错,但wss://连接反复建立又关闭,Wireshark抓包看到TLS握手完成后立刻RST,大概率是证书链缺失。Let’s Encrypt的fullchain.pem必须包含根证书+中间证书,不能只用cert.pem。
- 验证命令:
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null | openssl x509 -noout -text | grep "CA Issuers",输出应含http://开头的中间证书URL - Nginx配置里
ssl_certificate必须指向fullchain.pem,不是cert.pem - 某些CDN或企业防火墙会剥离
Upgrade头,若确认Nginx配置无误但依然失败,先绕过CDN直连测试 -
ssl_protocols TLSv1.2 TLSv1.3必须显式声明,旧版本协议可能干扰WebSocket协商
最常被忽略的是:Nginx reload后,旧worker进程可能还在处理老连接,Upgrade头转发逻辑只在新worker生效。遇到“改了配置还不行”,先nginx -s stop && nginx彻底重启,再用curl -i -H"Connection: Upgrade" -H"Upgrade: websocket" https://yoursite.com/ws验证响应头是否含101 Switching Protocols。


















