nginx 默认不透传 websocket 升级头,必须配置 proxy_http_version 1.1、proxy_set_header upgrade $http_upgrade、proxy_set_header connection "upgrade"、proxy_set_header host $host 和 proxy_pass 五项,缺一则握手失败导致 400 或连接秒断。

Nginx 默认不转发 WebSocket 升级请求,直接配 proxy_pass 会导致 400 或连接立即关闭 —— 必须显式透传 Upgrade 和 Connection 头,并禁用缓冲。
为什么 WebSocket 在 Nginx 后会断开
浏览器发起 WebSocket 连接时,会发送带 Upgrade: websocket 和 Connection: Upgrade 的 HTTP 请求。Nginx 默认把这类请求当普通 HTTP 处理,不透传升级头,也不保持长连接,后端(如 Python 的 websockets 或 FastAPI + uvicorn)收不到合法的升级请求,直接返回 400 或静默关闭。
常见现象:WebSocket opening handshake was canceled、Error during WebSocket handshake: Unexpected response code: 400、连接建立后几秒内自动 close。
- 确保后端服务本身支持 WebSocket(例如
uvicorn启动时未禁用--ws,websockets服务监听正确路径) - Nginx 版本需 ≥ 1.3.13(旧版不支持
Upgrade透传) - 不要在 location 块里加
proxy_buffering on—— WebSocket 是流式协议,缓冲会破坏帧边界
必需的 Nginx 配置项
在对应 location 块中添加以下 5 行,缺一不可:
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_pass http://your_backend;
说明:
-
proxy_http_version 1.1:WebSocket 依赖 HTTP/1.1 的持久连接和升级机制 -
$http_upgrade是 Nginx 内置变量,只在请求含Upgrade头时非空;若直接写死"websocket",普通 HTTP 请求也会被错误升级 -
Connection "upgrade"必须加英文双引号,否则 Nginx 会忽略该值(这是易错点) -
proxy_set_header Host $host看似冗余,但某些 Python 框架(如Starlette)校验Host头是否匹配,缺失会导致 400
Python 后端注意事项(以 uvicorn + FastAPI 为例)
即使 Nginx 配对了,Python 侧仍可能因配置或中间件中断连接:
- 启动命令必须含
--ws(uvicorn ≥ 0.20.0 默认开启,但低版本需显式指定) - 避免使用
gzip中间件或任何修改响应 body 的中间件 —— WebSocket 帧不是 HTTP body,压缩会损坏二进制帧 - 检查路由路径是否一致:Nginx 的
location /ws/必须和 FastAPI 的@app.websocket("/ws/")完全匹配(注意结尾斜杠) - 如果用
websockets库自建服务,确保监听的是0.0.0.0:port而非127.0.0.1:port,否则 Nginx 无法反向代理到它
验证与调试技巧
别只看浏览器控制台,要分层确认:
- 用
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" http://your-domain.com/ws/直连 Nginx,看响应头是否含Upgrade: websocket和Connection: upgrade - 在 Python 后端日志中打印
scope["type"](ASGI)或request.headers.get("upgrade")(WSGI 兼容层),确认升级头是否抵达 - Nginx 错误日志(
error_log /var/log/nginx/error.log debug;)里搜upstream prematurely closed或websocket,能快速定位是 Nginx 断开还是后端拒绝 - WebSocket 连接建立后,用
ss -tuln | grep :your_port观察连接状态,ESTAB持续存在才说明长连接生效
最常被跳过的一步:Nginx reload 后,浏览器没硬刷新(Ctrl+Shift+R),旧的连接池还在用 HTTP/1.0 缓存,导致看似配置生效实则没走新规则。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











