使用Nginx代理WebSocket(尤其是WSS加密连接),最核心的注意事项是:必须显式配置协议升级(Upgrade)和超时时间,否则连接会因Nginx的默认HTTP短连接行为而失败或频繁断开。
以下是需要特别注意的几个方面:
一、升级协议头:必须透传,缺一不可
WebSocket握手依赖HTTP升级机制,Nginx默认不会透传相关头部。因此,必须在`location`块中强制配置以下三项:
```nginx
location /ws/ {
proxy_pass http://backend_ws; # 后端地址
proxy_http_version 1.1; # 1. 必须使用HTTP/1.1
proxy_set_header Upgrade $http_upgrade; # 2. 透传Upgrade头
proxy_set_header Connection "upgrade"; # 3. 设置为upgrade
}
```
`proxy_http_version 1.1;`:WebSocket的升级机制仅在HTTP/1.1及以上版本中定义,HTTP/1.0不支持。
`proxy_set_header Upgrade $http_upgrade;`:将客户端发来的`Upgrade: websocket`头原样传递给后端。此处应使用`$http_upgrade`变量,而非硬编码"websocket",以兼容其他升级协议。
`proxy_set_header Connection "upgrade";`:这是最关键的一步。此处的值必须是带引号的固定字符串`"upgrade"`,而非变量。如果漏掉或配错,后端无法识别升级意图,连接会失败。
二、超时时间:大幅延长,避免“准时断开”
Nginx默认的`proxy_read_timeout`为60秒,这对于需要长期保持空闲的WebSocket连接来说太短了,会导致连接在无数据传输时被强制断开。必须调大相关超时参数:
```nginx
location /ws/ {
# ... 升级协议头配置 ...
proxy_read_timeout 86400s; # 建议设为24小时或更长
proxy_send_timeout 86400s; # 与读超时保持一致
proxy_buffering off; # 禁用缓冲,保证实时性
}
```
`proxy_read_timeout`:定义Nginx等待后端发送数据的超时时间。对于WebSocket,应设置为一个足够大的值(如`86400s`),或至少大于应用层的心跳间隔。
`proxy_send_timeout`:定义Nginx向后端发送数据的超时时间。建议与`proxy_read_timeout`设置为相同值,防止大消息分片传输时被中断。
`proxy_buffering off;`:必须关闭代理缓冲。WebSocket是流式通信,开启缓冲会导致数据帧被缓存,破坏实时性,甚至造成粘包。
SSL/WSS 特定注意事项
当代理WSS(WebSocket Secure)时,除了上述通用配置,还需注意以下几点:
SSL 终端(SSL Termination):最常见的模式是Nginx处理SSL加密,然后将解密后的普通WebSocket(WS)流量转发给后端。这样后端服务无需处理证书,简化了架构。
HTTPS 基础配置:确保`server`块正确监听了443端口并配置了SSL证书。
```nginx
server {
listen 443 ssl;
server_name ws.yourdomain.com;
ssl_certificate /path/to/your/certificate.pem; # 证书文件
ssl_certificate_key /path/to/your/private.key; # 私钥文件
# 推荐显式指定TLS版本
ssl_protocols TLSv1.2 TLSv1.3;
# ... WebSocket location配置 ...
}
```
转发原始协议:为了让后端知道原始请求是HTTPS,可以添加 `proxy_set_header X-Forwarded-Proto $scheme;`。这有助于后端正确生成重定向链接等。
HTTP/2 的兼容性:虽然可以在`listen`指令中启用`http2`,但必须保留 `proxy_http_version 1.1;`,因为WebSocket升级依赖于HTTP/1.1。
完整配置示例
以下是一个集成了上述所有要点的完整Nginx配置示例:
```nginx
# 映射 $connection_upgrade 变量,更优雅地处理升级头
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream websocket_backend {
# 使用 ip_hash 保证同一客户端始终连接到同一后端
ip_hash;
server 127.0.0.1:8080;
server 127.0.0.1:8081;
keepalive 32; # 保持与后端的空闲连接池
}
server {
listen 443 ssl http2;
server_name ws.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/ws.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ws.yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location /ws/ {
proxy_pass http://websocket_backend;
# WebSocket 核心配置
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade; # 使用map变量
# 传递真实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;
# 超时与缓冲配置
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_buffering off;
}
}
```
验证与调试
配置完成后,可以通过以下方法验证是否生效:
检查响应状态码:在浏览器开发者工具的“网络”面板中,WebSocket握手请求的状态码必须是 `101 Switching Protocols`。如果是200或其他错误码,则说明升级失败。
使用curl模拟:用以下命令模拟WebSocket握手,观察响应头中是否包含`Upgrade: websocket`和`Connection: upgrade`。
```bash
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" https://your-domain/ws/
```
检查Nginx日志:如果连接异常断开,查看Nginx的错误日志(通常位于`/var/log/nginx/error.log`),搜索 `upstream prematurely closed connection` 等关键词,这通常指向超时或后端问题