63人参与 • 2026-09-23 • websocket
在微服务架构和前后端分离的项目中,使用 nginx 作为反向代理来处理 websocket 连接是标准操作。然而,websocket 并非传统的 http 请求,它依赖于 http 协议进行握手,随后升级为 tcp 长连接。这种特性导致许多开发者在配置 nginx 时频繁踩坑,最典型的表现就是:本地直连后端一切正常,经过 nginx 代理后要么报 404/502,要么连接建立后几分钟就自动断开。
很多开发者在配置 websocket 代理时,会想当然地在 proxy_pass 后面加上路径或斜杠,导致请求到达后端时路径被意外篡改。
以一个典型的排查案例为例:客户端发起的 websocket 连接地址为 ws://backend-server:8080/ws/chatroom/123,后端实际提供 websocket 服务的地址为 http://backend-server:8080,且后端路由(如 spring boot、node.js 或 go 框架)注册的就是完整的 /ws/chatroom/:id。
nginx 的 proxy_pass 指令在处理 uri 时,是否携带 uri 参数(哪怕只是一个 /),决定了请求路径是“原样透传”还是“替换重写”。
| nginx 配置写法 | 客户端请求 uri | 转发到后端的实际 uri | 行为定性 | 适用场景 |
|---|---|---|---|---|
proxy_pass http://backend-server:8080; | /ws/chatroom/123 | /ws/chatroom/123 | 原样透传 | 后端路由包含完整前缀(最常见) |
proxy_pass http://backend-server:8080/; | /ws/chatroom/123 | /chatroom/123 | 前缀替换 | 后端路由不需要 /ws 前缀 |
proxy_pass http://backend-server:8080/ws/; | /ws/chatroom/123 | /ws/ws/chatroom/123 | 路径叠加 | 几乎永远是错误配置 |
原理解析:
当 proxy_pass 后面只写主机名和端口(不带任何 uri)时,nginx 不会干预请求路径,客户端请求的原始 uri 会原封不动地传递给后端。
当 proxy_pass 后面带了 uri(例如 / 或 /api/)时,nginx 会将 location 匹配到的前缀部分从原始 uri 中剔除,然后拼接到 proxy_pass 指定的 uri 后面。
在上述案例中,如果写成 proxy_pass http://backend-server:8080/;,nginx 会把 /ws/ 替换为 /,后端收到的路径变成了 /chatroom/123,由于后端路由注册的是 /ws/...,自然会返回 404。去掉 proxy_pass 后的斜杠,让路径原样透传,问题即可迎刃而解。
需要特别注意的是,如果 location 使用了正则表达式(~ 或 ~*),或者使用了 rewrite 指令改变了 uri,proxy_pass 后面是严禁携带 uri 的。否则 nginx 在启动或 reload 时会直接报错:
nginx: [emerg] "proxy_pass" cannot have uri part in location given by regular expression...
在正则匹配下,nginx 只能原样透传 uri,或者通过 rewrite 结合捕获组来手动控制转发路径。
解决了路径问题,接下来是 websocket 握手失败(通常表现为 400 bad request 或 502 bad gateway)的重灾区。websocket 的连接建立依赖于 http/1.1 的协议升级(upgrade)机制。
客户端在发起连接时,会发送如下特殊的 http 请求头:
get /ws/chatroom/123 http/1.1 host: nginx-server upgrade: websocket connection: upgrade sec-websocket-key: dghlihnhbxbszsbub25jzq== sec-websocket-version: 13
如果 nginx 没有正确将这些 header 透传给后端,后端就无法识别这是一个 websocket 升级请求,从而拒绝连接。
proxy_http_version 1.1; proxy_set_header upgrade $http_upgrade; proxy_set_header connection "upgrade";
深度解析:
proxy_http_version 1.1;:nginx 反向代理默认使用 http/1.0 与后端通信。http/1.0 不支持长连接和协议升级,必须显式指定为 http/1.1。proxy_set_header upgrade $http_upgrade;:将客户端请求头中的 upgrade: websocket 透传给后端。$http_upgrade 是 nginx 内置变量,用于读取客户端的 upgrade 头。proxy_set_header connection "upgrade";:这里有一个极易混淆的坑。在普通的 http 代理中,我们通常不设置或保持默认。但在 websocket 代理中,必须明确告诉后端服务器保持连接并同意协议升级。在实际项目中,一个 server 块通常既代理普通 http 接口,又代理 websocket。如果硬编码 connection "upgrade",可能会影响普通 http 请求的 keep-alive 行为。更规范的做法是在 http 块中使用 map 指令进行动态判断:
# 在 http 块中定义 map
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# 在 location 中引用
proxy_set_header connection $connection_upgrade;
这样配置后,只有当客户端请求头包含 upgrade 时,connection 才会被设置为 upgrade,否则设置为 close(或 keep-alive),完美兼容 http 和 websocket。
websocket 连接建立后,会转化为 tcp 长连接。很多开发者配置完“三件套”后发现连接能建立,但静默几分钟后连接就会自动断开,这通常是 nginx 的超时机制或中间网络设备导致的。
nginx 默认的 proxy_read_timeout 是 60 秒。这意味着,如果 60 秒内 nginx 没有从后端读取到任何数据(即双方都没有发送消息),nginx 会认为后端无响应,主动切断 tcp 连接。
对于 websocket,必须大幅调长超时时间:
proxy_read_timeout 3600s; # 后端响应超时(两次读取之间的最大间隔) proxy_send_timeout 3600s; # 后端接收超时(两次写入之间的最大间隔)
仅仅调大 nginx 的超时时间是不够的。生产环境中,nginx 和客户端之间通常还存在 nat 网关、防火墙、slb(负载均衡器)等中间设备。这些设备为了节省资源,通常会清理长时间没有数据传输的“死连接”(一般静默超时时间在 5~15 分钟不等,远小于 nginx 设置的 3600s)。
标准解决方案:
在 websocket 应用层实现心跳机制。客户端每隔 30 秒发送一个 ping 帧,服务端回复 pong 帧。这不仅能保持连接活跃,防止中间设备切断连接,还能让客户端及时感知到网络异常(如拔网线、进电梯等导致的 tcp 半开连接),从而触发重连逻辑。
在实际项目中,除了 nginx 本身的配置,网络拓扑和中间件也会引发各种诡异问题。以下是生产环境中极易踩坑的几个场景。
如果你的 nginx 前面还有云厂商 slb、cdn 或其他 waf 设备,必须确保这些中间层开启了 websocket 支持。
upgrade 头的请求误判为异常扫描并拦截,需要配置白名单或放行规则。如果后端部署了多个 websocket 服务节点,且业务逻辑依赖内存中的 session(例如用户 a 连到了节点 1,但给 a 推送消息的 http 请求被负载均衡分发到了节点 2),会导致消息无法送达。
解决思路:
ip_hash;(简单,但在客户端出口 ip 变化或经过 cdn 时会失效)。后端服务通常需要记录客户端的真实 ip 或校验域名。如果 nginx 没有正确传递 header,后端拿到的将是 nginx 的 ip 和默认的 host,导致业务逻辑出错(如鉴权失败、日志记录错误)。
proxy_set_header host $host; proxy_set_header x-real-ip $remote_addr; proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for; proxy_set_header x-forwarded-proto $scheme;
如果客户端使用 wss://(websocket over tls)连接 nginx,而 nginx 到后端使用的是 ws://(明文),proxy_pass 必须写 http:// 而不是 https://。nginx 会在前端终结 ssl(ssl termination),然后以明文 http/1.1 与后端进行 websocket 握手。后端服务不需要配置证书,只需处理普通的 http 升级请求即可。
nginx 默认开启代理缓冲(proxy_buffering on),会将后端的响应数据先存入内存或磁盘,再发给客户端。对于普通 http 请求这能提高性能,但对于 websocket 这种要求实时性的双向通信,缓冲会导致消息延迟甚至丢失。
必须关闭缓冲:
proxy_buffering off;
以下是一份经过生产环境验证的、健壮的 websocket 代理配置模板。该模板综合了上述所有最佳实践,可直接参考使用。
# ==========================================
# 在 http 块中定义 map,用于智能处理 connection 头
# ==========================================
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# ==========================================
# server 块配置
# ==========================================
server {
listen 80;
server_name api.example.com;
# 生产环境建议强制 https
# if ($scheme = http) {
# return 301 https://$host$request_uri;
# }
# ------------------------------------------
# websocket 代理配置
# ------------------------------------------
location /ws/ {
# 1. 核心:原样透传 uri,末尾绝对不加斜杠
proxy_pass http://backend-server:8080;
# 2. 协议升级三件套
proxy_http_version 1.1;
proxy_set_header upgrade $http_upgrade;
proxy_set_header connection $connection_upgrade;
# 3. 基础 header 透传,确保后端获取真实 ip 和域名
proxy_set_header host $host;
proxy_set_header x-real-ip $remote_addr;
proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for;
proxy_set_header x-forwarded-proto $scheme;
# 4. 长连接超时控制(配合应用层心跳使用)
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
# 5. 禁用缓冲,确保消息实时推送
proxy_buffering off;
# 6. 禁用缓存
proxy_cache off;
# 7. 关闭访问日志(可选,ws 握手和心跳日志量大,减少磁盘 io)
# access_log off;
}
# ------------------------------------------
# 普通 http api 代理配置(对比参考)
# ------------------------------------------
location /api/ {
# 注意:这里加了斜杠,去除了 /api 前缀,转发到后端根路径
proxy_pass http://backend-server:8080/;
proxy_set_header host $host;
proxy_set_header x-real-ip $remote_addr;
proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for;
}
}
当配置完成后,不要仅依赖前端页面测试,应使用底层工具验证握手过程,以便精准定位问题。
通过 curl 发送带有 upgrade 头的请求,观察 nginx 和后端的响应状态码。
curl -i -n \ -h "connection: upgrade" \ -h "upgrade: websocket" \ -h "sec-websocket-version: 13" \ -h "sec-websocket-key: dghlihnhbxbszsbub25jzq==" \ http://api.example.com/ws/chatroom/123
结果研判:
http/1.1 101 switching protocols:握手成功,nginx 和后端配置完全正确。400 bad request:通常是后端没有收到 upgrade 头,检查 nginx 的“三件套”配置是否遗漏。404 not found:路径透传错误,检查 proxy_pass 是否多加了斜杠,或后端路由不匹配。502 bad gateway:nginx 无法连接后端,检查后端主机名、端口是否正确,以及服务器防火墙/安全组是否放行。在浏览器的“开发者工具 -> network -> ws”面板中:
proxy_read_timeout 设置过小。如果上述方法无法定位,直接查看 nginx 的 error.log。
upstream prematurely closed connection while reading response header from upstream:后端主动断开了连接,通常是后端代码抛出异常或握手校验失败。no live upstreams:后端服务宕机或健康检查失败。通过理清 proxy_pass 的路径替换逻辑,严格补齐协议升级与超时控制的相关指令,并关闭不必要的缓冲,nginx 代理 websocket 的稳定性将得到根本保障。在排查问题时,遵循“客户端 -> 中间网络 -> nginx -> 后端”的链路逐层验证,通常能快速定位并解决问题。
到此这篇关于nginx中反向代理websocket的常见问题与避坑指南的文章就介绍到这了,更多相关nginx反向代理websocket内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论