1. 为什么Nginx代理WebSocket会报400错误?
第一次遇到Nginx代理WebSocket报400错误时,我盯着浏览器控制台那个刺眼的红色错误提示,完全摸不着头脑。明明直接访问后端服务是正常的,怎么经过Nginx就出问题了呢?后来才发现,这其实是WebSocket协议握手过程中的一个典型配置问题。
WebSocket协议在建立连接时,会先发起一个HTTP升级请求。这个请求需要包含特定的头部信息,而Nginx默认配置并不包含这些必要字段。当客户端通过Nginx代理连接WebSocket时,Nginx如果没有正确转发这些头部,就会导致握手失败,最终返回400 Bad Request错误。
这个问题的核心在于协议升级机制。WebSocket连接建立时,客户端会发送类似这样的请求头:
Upgrade: websocket Connection: Upgrade而Nginx默认配置会把这些关键头部过滤掉,导致后端服务收不到协议升级请求,自然就无法建立WebSocket连接。
2. 完整解决方案:Nginx配置调整
2.1 基础配置模板
经过多次实践,我总结出了一个可靠的Nginx WebSocket代理配置模板。这个配置已经在我负责的多个生产环境中稳定运行:
location /websocket/ { proxy_pass http://backend_server; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; # 超时设置 proxy_connect_timeout 7d; proxy_read_timeout 7d; proxy_send_timeout 7d; # 其他优化参数 proxy_buffering off; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }这个配置中有几个关键点:
proxy_http_version 1.1- 强制使用HTTP/1.1协议,这是WebSocket必需的Upgrade和Connection头部 - 允许协议升级到WebSocket- 超时设置 - WebSocket是长连接,需要设置较长的超时时间
2.2 配置参数详解
让我详细解释下这些参数的作用:
proxy_http_version 1.1WebSocket协议基于HTTP/1.1的升级机制,必须明确指定。如果使用HTTP/1.0,WebSocket握手会失败。
Upgrade和Connection头部这两个头部是WebSocket握手的核心。Upgrade: websocket告诉服务器客户端希望升级协议,Connection: Upgrade表示这是升级连接。
超时设置WebSocket连接通常会保持很长时间,所以需要调整默认的超时设置:
proxy_connect_timeout- 连接后端服务器的超时proxy_read_timeout- 等待后端响应的超时proxy_send_timeout- 发送请求到后端的超时
我一般设置为7天(604800秒),这样可以避免不必要的连接中断。
3. 高级配置与优化
3.1 SSL/TLS配置
如果你的WebSocket服务运行在HTTPS下,还需要注意SSL配置:
server { listen 443 ssl; server_name yourdomain.com; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; location /ws/ { proxy_pass http://backend_ws; # 上述WebSocket配置... } }SSL配置要点:
- 证书路径要正确
- 建议启用HTTP/2(虽然WebSocket over HTTP/2有特殊要求)
- 考虑启用SSL会话复用减少握手开销
3.2 负载均衡配置
对于高并发场景,你可能需要配置WebSocket的负载均衡:
upstream websocket_servers { server 10.0.0.1:8080; server 10.0.0.2:8080; server 10.0.0.3:8080; } server { location /ws/ { proxy_pass http://websocket_servers; # 其他WebSocket配置... } }负载均衡注意事项:
- WebSocket是长连接,普通的轮询负载均衡可能不够均衡
- 考虑使用
ip_hash保持客户端与固定后端服务器的连接 - 监控后端服务器的连接数,避免单台过载
4. 常见问题排查技巧
4.1 诊断工具推荐
当WebSocket连接出现问题时,我通常会使用以下工具进行诊断:
浏览器开发者工具
- 查看Network标签中的WebSocket连接状态
- 检查握手请求和响应头
curl测试
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Host: example.com" -H "Origin: http://example.com" http://localhost/ws这个命令可以模拟WebSocket握手,查看原始响应。
Nginx日志在Nginx配置中增加调试日志:
error_log /var/log/nginx/error.log debug;然后检查日志中的详细错误信息。
4.2 典型错误场景
场景一:跨域问题错误信息:WebSocket connection to 'wss://...' failed: Error during WebSocket handshake: Unexpected response code: 403
解决方案:
location /ws/ { # ...其他配置 proxy_set_header Origin ""; add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; }场景二:代理缓冲区不足错误现象:连接随机断开,Nginx日志中出现upstream sent too big header错误。
解决方案:
location /ws/ { # ...其他配置 proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k; }场景三:心跳断开错误现象:长时间空闲后连接断开。
解决方案:
- 客户端实现心跳机制
- 调整Nginx超时设置(如前文所述)
- 考虑使用TCP keepalive
location /ws/ { # ...其他配置 proxy_socket_keepalive on; }5. 性能优化实践
5.1 连接数优化
WebSocket服务通常需要维持大量并发连接,这对Nginx和操作系统都有一定压力。以下是我总结的优化经验:
调整系统文件描述符限制
# 临时设置 ulimit -n 100000 # 永久设置 echo "* soft nofile 100000" >> /etc/security/limits.conf echo "* hard nofile 100000" >> /etc/security/limits.conf优化Nginx worker连接数
worker_processes auto; worker_rlimit_nofile 100000; events { worker_connections 50000; use epoll; multi_accept on; }内核参数调优
echo "net.ipv4.tcp_max_tw_buckets = 200000" >> /etc/sysctl.conf echo "net.core.somaxconn = 65535" >> /etc/sysctl.conf sysctl -p
5.2 内存优化
WebSocket连接会占用内存,特别是在高并发场景下:
调整Nginx缓冲区
location /ws/ { proxy_buffering off; proxy_buffer_size 4k; proxy_buffers 4 4k; }禁用不必要的模块编译Nginx时,只包含必需的模块以减少内存占用。
监控内存使用使用工具如
htop或nginx-status模块监控Nginx内存使用情况。
6. 真实案例分享
去年我们项目上线了一个实时协作功能,使用了WebSocket技术。在压力测试时,当并发连接达到约3000时,开始出现400错误。经过排查,发现几个问题:
- Nginx默认的
worker_connections是512,完全不够用 - 操作系统文件描述符限制太低
- 缺少正确的心跳机制,导致连接堆积
解决方案:
- 调整Nginx配置,增加worker连接数
- 优化系统级参数
- 实现客户端和服务端的双向心跳
- 增加Nginx的负载均衡节点
最终系统稳定支持了超过2万并发WebSocket连接。这个案例让我深刻理解到,WebSocket服务的稳定性不仅取决于正确的代理配置,还需要考虑系统级的优化。