HSTS本身不干扰WebSocket,真正原因是其强制HTTPS后触发HTTP/2协商,而HTTP/2静默丢弃Upgrade和Connection: upgrade头,导致wss握手失败;需通过子域名隔离、禁用http2或map动态设置Connection头来解决。

HSTS 本身不干扰 WebSocket,真正出问题的是它强制 HTTPS 后引发的 HTTP/2 协商,而 HTTP/2 会静默丢弃 WebSocket 握手必需的 Upgrade 和 Connection: upgrade 头——这才是 wss 连接失败的根源。
确认 HSTS 是否已生效并影响 ALPN 协商
HSTS(Strict-Transport-Security)只是告诉浏览器“只用 HTTPS 访问”,它不修改协议逻辑。但一旦启用,浏览器访问 wss:// 时会走 TLS 握手,并在 ALPN 阶段优先协商 h2(HTTP/2),而非 http/1.1。Nginx 在 h2 连接上无法触发 Upgrade 流程,导致后端收不到升级请求。
- 用
curl -v --http2 wss://your-domain.com/ws检查是否真的走 h2(看输出中是否有ALPN, offering h2和Using HTTP2) - 用浏览器开发者工具 → Network → WS 标签页,查看连接的
Protocol列:若显示h2,说明已被 HTTP/2 拦截 - 临时注释掉
add_header Strict-Transport-Security ...;并 reload Nginx,再测试 wss 是否恢复——可快速验证是否为 HSTS 间接引发
隔离 WebSocket 流量,绕过 HTTP/2 路径
不能靠“禁用 HSTS”来保 wss,而应让 wss 流量不经过 h2 通道。最稳妥的方式是域名或路径级分离:
-
子域名隔离(推荐):单独配
ws.your-domain.com,server 块中listen 443 ssl;不加http2,并复用同一套证书;HSTS 可设includeSubDomains,不影响该子域协议选择 -
路径级分流(轻量适用):用
map判断升级意图,动态设置 Connection 头,并确保该 location 下不继承全局http2行为:
map $http_upgrade $connection_upgrade { default upgrade; '' close; }
location /ws/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } - 避免在启用了
http2的 server 块内写if ($scheme = 'wss') { ... }或重定向——WebSocket 握手是原子性请求,任何跳转都会中断
检查 Nginx 配置中是否隐式破坏升级链路
即使 HSTS 和 http2 分开,以下配置仍可能让 wss 掉链:
-
proxy_set_header Connection "";—— 这是普通 HTTP 长连接用的,会覆盖"upgrade",必须改为proxy_set_header Connection "upgrade"; -
gzip on;或proxy_buffering on;开启——WebSocket 帧是二进制流,压缩或缓存会导致帧错乱、延迟甚至连接卡死 -
proxy_read_timeout小于后端心跳周期(如后端每 30 秒 ping,Nginx 却设了 45 秒超时)——连接会在无数据时被主动关闭,表现就是“闪断”或反复重连 - SSL 配置中
ssl_protocols若禁用了 TLSv1.2(仅留 TLSv1.3),而某些旧客户端或嵌入式设备不兼容,也会表现为握手失败,需结合日志error_log /var/log/nginx/error.log debug;查SSL_do_handshake() failed类报错
验证与日志辅助定位
光看返回码(如 400、502、空白连接)不够,要抓真实行为:
- 开启 Nginx debug 日志:
error_log /var/log/nginx/error.log debug;,重点搜http2、upgrade、websocket、ssl handshake - 用
tcpdump -i any port 443 -w ws.pcap抓包,Wireshark 中过滤tls.handshake.type == 1(ClientHello)和http2,确认 ALPN 协商结果 - 后端加日志:打印收到的请求头,确认
Upgrade: websocket和Connection: upgrade是否完整抵达


















