NGINX WebSocket 代理配置实战:解决实时通信连接断开与握手失败
快速答案
- 核心结论:NGINX 作为 WebSocket 反向代理需要显式配置
proxy_http_version 1.1、proxy_set_header Upgrade $http_upgrade和proxy_set_header Connection "upgrade"三个关键指令,缺一不可。 - 第一排查点:WebSocket 连接 60 秒断开是
proxy_read_timeout默认值导致,需设置为86400s;握手返回 400 错误通常因 HTTP 版本或 Upgrade 头未正确转发。 - 最小配置:在
location块中添加proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 86400s; proxy_send_timeout 86400s;。 - 适用环境:NGINX 1.3+ 版本原生支持 WebSocket 代理,适用于任何需要 NGINX 前置代理 WebSocket 后端的场景(Node.js、Python FastAPI、Go 等)。
它解决什么问题
WebSocket 协议要求从 HTTP 升级到 WebSocket 连接,而 NGINX 默认的 HTTP 代理行为会丢弃 Upgrade 和 Connection 头,导致握手失败。本配置解决以下核心问题:
- 握手失败:客户端收到
400 Bad Request或426 Upgrade Required响应 - 连接意外断开:WebSocket 连接在空闲 60 秒后被 NGINX 强制关闭
- 负载均衡兼容:在多个后端实例间正确路由 WebSocket 流量并保持会话
典型应用场景包括在线聊天系统、实时协作编辑、股票行情推送、游戏服务器、物联网设备状态监控等需要双向实时通信的系统。
安装与快速上手
安装 NGINX
BASH# CentOS/RHEL/Fedora dnf install nginx && systemctl enable --now nginx # Ubuntu/Debian apt update && apt install nginx -y && systemctl enable --now nginx
最小可用配置
以下配置实现 WebSocket 代理到本地 3000 端口的后端服务:
NGINXhttp { # 定义后端服务器组 upstream websocket_backend { server 127.0.0.1:3000; } server { listen 80; server_name example.com; location /ws/ { proxy_pass http://websocket_backend; proxy_http_version 1.1; # 必须:使用 HTTP/1.1 proxy_set_header Upgrade $http_upgrade; # 必须:转发 Upgrade 头 proxy_set_header Connection "upgrade"; # 必须:设置 Connection 为 upgrade proxy_read_timeout 86400s; # 推荐:24小时超时 proxy_send_timeout 86400s; # 推荐:24小时超时 proxy_buffering off; # 推荐:关闭缓冲 } } }
核心配置参数详解
| 参数 | 必需 | 默认值 | 说明 |
|---|---|---|---|
proxy_http_version 1.1 | 是 | 1.0 | HTTP/1.1 是 WebSocket 升级的前提条件,HTTP/1.0 不支持 Upgrade 机制 |
proxy_set_header Upgrade $http_upgrade | 是 | 无 | 将客户端的 Upgrade: websocket 头原样转发给后端 |
proxy_set_header Connection "upgrade" | 是 | 无 | 对于 WebSocket 请求,将 Connection 头设置为 upgrade;非 WebSocket 请求保持原值 |
proxy_read_timeout | 否 | 60s | 两次读取操作之间的最大空闲时间,WebSocket 长连接建议设为 86400s |
proxy_send_timeout | 否 | 60s | 两次发送操作之间的最大空闲时间,与 proxy_read_timeout 同步设置 |
proxy_buffering off | 推荐 | on | 关闭缓冲确保 WebSocket 数据实时转发,避免数据延迟 |
智能处理非 WebSocket 请求
使用 map 指令避免破坏普通 HTTP 请求的 Connection 头:
NGINXhttp { map $http_upgrade $connection_upgrade { default "upgrade"; '' ""; } server { location /ws/ { proxy_pass http://websocket_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 86400s; proxy_send_timeout 86400s; proxy_buffering off; } } }
当客户端不发送 Upgrade 头时(普通 HTTP 请求),$connection_upgrade 变量为空字符串,Connection 头不会被错误设置为 upgrade。
与同类方案对比
| 对比维度 | NGINX | Caddy | HAProxy | 专用 WebSocket 代理 |
|---|---|---|---|---|
| 配置复杂度 | 中等,需手动处理 Upgrade 头 | 低,自动处理 WebSocket 升级 | 中等,需配置 TCP 模式 | 低,功能单一 |
| 高并发性能 | 优秀,事件驱动模型 | 良好 | 极佳,纯 TCP 代理延迟最低 | 一般 |
| 功能丰富度 | 高,支持 HTTP/2、gRPC、缓存、限流 | 中,自动 HTTPS 是亮点 | 中,侧重 TCP/HTTP 负载均衡 | 低,仅 WebSocket |
| 生态成熟度 | 极高,文档和社区支持最完善 | 中等,增长迅速 | 高,生产验证充分 | 低 |
| 安全特性 | 内置限流、WAF 集成、访问控制 | 自动 HTTPS、ACME 集成 | 基础 ACL、TLS 终止 | 有限 |
亮点:NGINX 通过 map 变量智能处理 Connection 头,这是其他方案常忽略的细节,能避免破坏非 WebSocket 请求。
生产环境实践与注意事项
性能调优
NGINXevents { worker_connections 10240; # 提高并发连接数 use epoll; # Linux 下使用 epoll multi_accept on; # 一次接受所有新连接 } http { # 调整文件描述符限制 worker_rlimit_nofile 65535; # 关闭缓冲 proxy_buffering off; proxy_request_buffering off; # 超时设置 proxy_read_timeout 86400s; proxy_send_timeout 86400s; }
负载均衡与会话保持
NGINXupstream websocket_backend { ip_hash; # 基于 IP 哈希实现会话保持 server 192.168.1.1:3000 max_fails=3 fail_timeout=30s; server 192.168.1.2:3000 max_fails=3 fail_timeout=30s; server 192.168.1.3:3000 backup; # 备用服务器 }
ip_hash 确保同一客户端始终路由到同一后端,避免 WebSocket 会话状态丢失。如果后端是无状态设计,可使用轮询配合 Redis 共享会话存储。
安全加固
NGINXlocation /ws/ { # 限制来源 valid_referers none blocked server_names *.example.com; if ($invalid_referer) { return 403; } # 限流防止洪水攻击 limit_req zone=websocket burst=20 nodelay; limit_conn websocket 10; # 仅允许 WebSocket 升级请求 if ($http_upgrade !~* "websocket") { return 400; } proxy_pass http://websocket_backend; # ... 其他配置 }
日志优化
WebSocket 连接关闭会产生大量无意义日志,建议调整日志级别:
NGINXhttp { # 仅记录错误级别日志 error_log /var/log/nginx/error.log error; # 或使用条件日志 map $status $loggable { ~^[23] 0; # 2xx 和 3xx 不记录 default 1; } access_log /var/log/nginx/access.log combined if=$loggable; }
常见报错与排查
错误 1:WebSocket 握手返回 400
报错信息:WebSocket connection to 'wss://example.com/ws/' failed: Error during WebSocket handshake: Unexpected response code: 400
排查步骤:
- 检查
proxy_http_version是否设置为1.1 - 确认
proxy_set_header Upgrade $http_upgrade和proxy_set_header Connection "upgrade"已正确配置 - 验证后端服务是否在监听正确的端口和路径
- 查看 NGINX 错误日志:
tail -f /var/log/nginx/error.log
错误 2:上游连接被提前关闭
报错信息:upstream prematurely closed connection while reading upstream
解决方案:
- 增加超时值:
proxy_read_timeout 86400s; proxy_send_timeout 86400s; - 检查后端 WebSocket 服务是否因空闲超时主动断开连接
- 在后端实现心跳机制(ping/pong 帧)
- 考虑添加
proxy_ignore_client_abort on;
错误 3:无可用上游服务器
报错信息:no live upstreams while connecting to upstream
排查步骤:
- 检查
upstream块中定义的后端服务器是否健康 - 确认后端服务正在运行且端口可访问:
curl http://127.0.0.1:3000/ws/ - 检查防火墙规则是否阻止了 NGINX 到后端的连接
- 使用
backup标记配置备用服务器
错误 4:SSL 握手失败
报错信息:SSL: error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure
解决方案:
- 确认 SSL 证书和密钥文件路径正确且权限为 600
- 检查
ssl_protocols是否包含后端支持的 TLS 版本 - 验证证书链是否完整(包括中间证书)
- 使用
openssl s_client -connect backend:port测试后端 SSL 配置
常见问题 FAQ
Q: 为什么我的 WebSocket 连接在 60 秒后自动断开?
A: 这是 NGINX 默认的 proxy_read_timeout 和 proxy_send_timeout(均为 60 秒)导致的。WebSocket 连接空闲时,NGINX 会关闭超过 60 秒无数据传输的连接。解决方案:在 location 块中设置 proxy_read_timeout 86400s; 和 proxy_send_timeout 86400s;(24 小时)。注意:这些超时是两次操作之间的间隔,如果 WebSocket 定期发送心跳消息,超时会被重置。建议同时在后端实现 WebSocket 心跳机制(ping/pong 帧),保持连接活跃。
Q: NGINX WebSocket 代理如何实现负载均衡和会话保持?
A: NGINX 支持多种负载均衡算法:1) 轮询(默认):upstream websocket_backend { server 192.168.1.1:3000; server 192.168.1.2:3000; };2) IP 哈希(会话保持):upstream websocket_backend { ip_hash; server ...; };3) 最少连接:upstream websocket_backend { least_conn; server ...; }。对于 WebSocket,推荐使用 ip_hash 确保同一客户端的请求始终路由到同一后端,避免会话状态丢失。注意:如果后端是无状态设计,可以使用轮询配合后端共享会话存储(如 Redis)。
Q: 如何调试 NGINX WebSocket 代理的连接问题?
A: 1) 启用调试日志:在 events 块中添加 debug_connection [客户端IP];在 http 块中添加 error_log /var/log/nginx/error.log debug;;2) 使用 curl 测试 WebSocket 握手:curl -v -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" -H "Sec-WebSocket-Version: 13" http://example.com/ws/;3) 检查后端响应:确保后端返回 101 Switching Protocols 状态码;4) 使用 tcpdump 抓包分析:tcpdump -i any port 80 or port 443 -w websocket.pcap;5) 验证 NGINX 配置:nginx -t 检查语法,nginx -T 输出完整配置。
相关深度解决方案
在配置当前服务时,如果您需要实现更复杂的架构或多源数据整合,建议配合参考我们整理的 Nginx 上游连接提前关闭错误排查与修复:proxy_read_timeout 与缓冲区配置。
在配置当前服务时,如果您需要实现更复杂的架构或多源数据整合,建议配合参考我们整理的 Nginx 413 Request Entity Too Large 错误:client_max_body_size 配置与排查。