Nginx 已配 TLS,wss 仍失败:前端连到了明文后端端口
wss 地址必须连接实际提供 TLS 的入口。区分浏览器到 Nginx 与代理到应用两段连接,再核对端口、路径、Upgrade 和应用握手结果。
WebSocket 在本地用 ws:// 能连接,部署到 Nginx 后,前端却报 WebSocket is closed before the connection is established 或握手返回 400。如果前端地址写成 wss://example.com:8989/ws,而 TLS 只配置在 Nginx 的 443 端口,需要先检查是不是把浏览器指向了明文后端。
wss:// 表示客户端会先与目标端口进行 TLS 握手。域名相同不会让 8989 端口自动使用 443 端口的证书配置。
把浏览器入口和后端入口分清
假设部署关系如下,这是配置示例:
浏览器:wss://example.com/ws
Nginx:443 端口终止 TLS
后端:127.0.0.1:8989,明文 HTTP / WebSocket
浏览器应该连接公开的 Nginx 入口。端口 8989 属于 Nginx 到应用的内部连接,不应直接填到前端公开地址中。
先在浏览器 Network 面板检查失败请求的完整 URL,再对照 Nginx 的 listen 和后端实际监听协议。如果 Nginx 使用的是自定义 TLS 端口,例如 8443,前端就应填写那个入口端口。只有后端自己也提供 TLS、并准备允许客户端直接连接时,才可能使用指向后端端口的 wss:// 地址。
400 不是这个根因的唯一结果。向明文端口发送 TLS 数据可能表现为握手失败、断开或其他错误;即便已经连接正确入口,认证、路径或协议不匹配也可能返回 400。
给公开地址配置 WebSocket 代理
下面的站点配置示例将公开 /ws 映射到后端 /graphql。证书路径、域名、后端地址和路径需要替换为实际值;该例假设应用接受 /graphql 上的 WebSocket 升级:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/nginx/tls/fullchain.pem;
ssl_certificate_key /etc/nginx/tls/domain.key;
location = /ws {
proxy_pass http://127.0.0.1:8989/graphql;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
前端使用:
const socket = new WebSocket('wss://example.com/ws');
这里使用精确匹配,/ws/ 不会命中同一个入口。前后端应约定同一个公开路径,不要遇到失败就盲目添加或删除斜杠。实际应用若需要认证参数或特定 WebSocket 子协议,也应使用其相应客户端设置,不能只替换这段原生客户端代码。
Nginx 的反向代理需要显式传递 Upgrade 和 Connection,因为它们属于逐跳头字段。示例显式设置 HTTP/1.1,便于覆盖仍以其他默认值工作的版本。如果 Nginx 和应用在不同容器中,127.0.0.1 指向 Nginx 容器本身,必须改成可达的应用服务地址。
浏览器到 Nginx 的 TLS,与 Nginx 到应用的协议是两段独立连接。后端如果是明文服务,保留 proxy_pass http://...;后端真正启用了 TLS 时才使用 https://... 并按需要配置证书校验。不能为了匹配前端的 wss://,直接把明文后端改写成 HTTPS 地址。
地址正确后仍失败,按握手结果继续查
先保存原配置和前端入口设置,在对应 Nginx 实例检查配置,成功后再重载。前端地址改动也需要确认实际发布的资源使用新值;需要回退时恢复两处设置,不能只恢复代理而让客户端继续连接另一条路径。
重新连接后,Network 中应看到预期公开地址,传统 HTTP/1.1 WebSocket 升级成功时响应为 101 Switching Protocols,并能交换应用消息。如果返回 404,检查公开路径与后端路径;返回 401 或 403,检查认证和 Origin 等应用限制;返回 400,查看应用具体的握手错误,不要继续把所有问题归为 TLS。
连接建立后固定一段空闲时间断开,应再检查超时和心跳。Nginx 默认会在后端连续 60 秒没有发送数据时关闭这类代理连接;它与连接建立前失败是不同阶段的问题。


