[知识是人生的灯塔,只有不断学习,才能照亮前行的道路]
本文将探讨如何在 Nginx 中实现 WebSocket 的反向代理。在开始配置之前,我们先来简单了解一下 WebSocket 协议,知其然亦知其所以然。

Websocket 介绍
WebSocket 提供了一种在单个 TCP 连接上进行全双工通信的方法,这对于实时应用(如聊天室、在线游戏等)非常有用,是目前广泛使用的一种 Web 通讯方式。与 HTTP 协议相比,它最大的优势在于服务端可以主动推送数据,无需客户端轮询,从而能及时地向客户端反映服务端的变化。
WebSocket 具有以下核心特点:
- 持久连接:连接建立后无需重复握手,数据以帧的形式传输。
- 低延迟:相比 HTTP 长轮询,WebSocket 减少了数据传输延迟。
- 高效性:传输数据量更小,适合高频实时通信。
目前主流的开发语言如 Java、Python、Node.js、Go 等,都提供了对 WebSocket 的原生支持,开发成本低,易于上手。
WebSocket 协议帧的基本结构如下图所示:

让我们来解析一下这个帧结构中的关键字段:
FIN 位:用于表示这是当前帧的最后一部分。当 FIN 设置为 1 时,表示这是一个完整的消息;如果为 0,则表示还有更多的数据片段需要接收。
RSV1、RSV2 和 RSV3:保留位,用于自定义扩展,值为 0 时表示不使用这些扩展。
Opcode:用于表示帧的类型,例如文本、二进制数据或连接关闭等。若接收到未知的 opcode,则会关闭连接。常见值有:
- 0x0:表示一个继续帧(Continuation Frame)。
- 0x1:表示一个文本帧(Text Frame)。
- 0x2:表示一个二进制帧(Binary Frame)。
- 0x8:表示一个连接关闭帧(Connection Close Frame)。
- 0x9:表示一个 Ping 帧。
- 0xA:表示一个 Pong 帧。
MASK 掩码位:表示帧中数据是否经过掩码处理。客户端发出的数据帧必须经过掩码处理(值为 1),服务端发出的帧则无需掩码(值为 0)。当值为 1 时,会附带一个 4 字节的掩码密钥用于解码数据。
Payload length:表示负载数据的字节数。它可能是 7 位、7+16 位或 7+64 位:
- 如果值为 0 到 125,则直接表示负载长度。
- 如果值为 126,则后续两个字节表示实际的负载长度。
- 如果值为 127,则后续八个字节表示实际的负载长度。
Extended Payload Length:扩展负载长度字段,当 Payload length 为 126 或 127 时使用,用于存放实际的长度值。
Masking-key:掩码密钥,当 MASK 位为 1 时存在,占用 4 个字节。
Payload Data:实际传输的数据内容。
知识扩展:WebSocket 也有一些局限性,例如数据分片是有序的,不支持原生的多路复用(但可通过扩展实现),且协议原生不支持压缩(同样可通过 RFC 7692 扩展实现)。
Websocket 反向代理指令
在 Nginx 中配置 WebSocket 反向代理,主要依赖于 ngx_http_proxy_module 模块。其配置思路与 HTTP 反向代理相似,但有几个关键指令必须设置,以确保协议能够正确升级:
# 设置 HTTP 版本为 1.1,这是 WebSocket 握手所必需的
proxy_http_version 1.1;
# 设置 WebSocket 协议升级头
proxy_set_header Upgrade $http_upgrade;
# 将 connection 头设置为 "upgrade",以完成 WebSocket 的升级握手
proxy_set_header Connection "upgrade";

上图展示了 WebSocket 握手过程中关键的请求与响应头部。除了上述 Nginx 需要设置的头部,WebSocket 协议本身还定义了一些特定的头部:
Sec-WebSocket-Version:客户端声明其使用的 WebSocket 协议版本(如 13)。
Sec-WebSocket-Extensions:用于协商本次连接要使用的扩展,如压缩。
Sec-WebSocket-Key:客户端发送的一个随机生成的密钥,用于服务端验证。
Sec-WebSocket-Protocol:用于协商应用子协议(如 chat)。
Sec-WebSocket-Accept:服务端对客户端 Key 计算后的响应密钥,用于完成握手验证。
实践演示
接下来,我们通过一个完整的例子来演示如何配置 Nginx 反向代理 WebSocket。
步骤 01. 准备 WebSocket 服务端环境
我们使用 Python 的 websockets 库快速创建一个简单的回声服务端,它会将客户端发送的消息原样返回。
# 安装必要的依赖
pip install websockets -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
# 创建 WebSocket 服务器脚本
tee websocket_server.py <<EOF
import asyncio
import logging
from datetime import datetime
import websockets
from websockets.server import serve
# 设置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
async def echo_handler(websocket):
"""处理 WebSocket 连接,将客户端发送的消息原样返回"""
client_address = websocket.remote_address
logger.info(f"客户端连接成功: {client_address}")
try:
async for message in websocket:
# 记录接收到的消息
logger.info(f"收到来自 {client_address} 的消息: {message}")
# 构建响应消息(原样返回客户端发送的内容)
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
response = f"[{timestamp}] 服务端消息: {message}"
# 发送响应
await websocket.send(response)
logger.info(f"发送响应到 {client_address}: {response}")
except websockets.exceptions.ConnectionClosed as e:
logger.info(f"客户端断开连接: {client_address}, 原因: {e}")
except Exception as e:
logger.error(f"处理客户端 {client_address} 时发生错误: {e}")
await websocket.close(code=1011, reason=str(e))
async def main():
"""启动 WebSocket 服务器"""
host = "0.0.0.0"
port = 8765
logger.info(f"启动 WebSocket 服务器在 ws://{host}:{port}")
logger.info("按 Ctrl+C 停止服务器")
# 启动服务器
async with serve(
echo_handler,
host,
port,
ping_interval=20,
ping_timeout=20,
close_timeout=10,
) as server:
await server.wait_closed()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
logger.info("服务器已停止")
EOF
# 启动 WebSocket 服务器
python3 websocket_server.py
步骤 02. 测试 WebSocket 服务
在另一台 Linux 服务器上,可以使用 websocat 工具测试刚启动的服务是否正常。
# 下载并安装 websocat 工具
wget https://github.com/vi/websocat/releases/download/v1.14.0/websocat.x86_64-unknown-linux-musl
mv websocat.x86_64-unknown-linux-musl /usr/local/bin/websocat
chmod +x /usr/local/bin/websocat
# 连接测试
websocat ws://127.0.0.1:8765

步骤 03. 配置 Nginx 反向代理
现在,我们来配置 Nginx 作为反向代理,并使其同时支持 ws (WebSocket) 和 wss (WebSocket Secure) 协议。这里我们复用之前学过的 SSL/TLS 配置。
tee /usr/local/nginx/conf.d/server.conf <<'EOF'
# 定义后端 WebSocket 服务地址
upstream websocket_backend {
server 10.20.172.214:8765;
}
server {
listen 80;
# 监听 443 端口,启用 SSL
listen 443 ssl;
server_name server.weiyigeek.top;
default_type text/html;
# 开启 HTTP/2 支持
http2 on;
# 日志文件
access_log /var/log/nginx/server.log main;
error_log /var/log/nginx/server.err.log debug;
# SSL 证书配置
ssl_certificate /usr/local/nginx/certs/server.crt;
# 使用加密的私钥文件(需提供密码文件)
ssl_certificate_key /usr/local/nginx/certs/server_encrypted.key;
ssl_password_file /usr/local/nginx/certs/ssl_password.txt;
# 支持的 SSL/TLS 协议版本
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
# 加密套件
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE:ECDH:AES:HIGH:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:!NULL:!aNULL:!eNULL:!EXPORT:!PSK:!ADH:!DH:!DES:!MD5:!RC4;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_prefer_server_ciphers on;
# 强制使用 HTTPS 访问
add_header Strict-Transport-Security "max-age=31536000;includeSubDomains;preload" always;
# WebSocket 反向代理核心配置
location ^~ /ws {
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 60s; # 设置长连接超时时间
proxy_set_header X-Real-IP $remote_addr;
# 缓冲区配置,应对可能的消息突增
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
}
}
EOF
步骤 04. 验证配置并测试
应用 Nginx 配置,并使用 websocat 分别通过 HTTP 和 HTTPS 测试反向代理。
# 检查配置并重载 Nginx
nginx -t && nginx -s reload
# 测试非 SSL 的 WebSocket 反向代理
websocat ws://server.weiyigeek.top/ws
# 测试 SSL 加密的 WebSocket (WSS) 反向代理
websocat -k wss://server.weiyigeek.top/ws

知识扩展:除了命令行工具,我们也可以在 Web 前端使用 JavaScript 连接到这个 WebSocket 服务。
// Connect to the WebSocket echo server
const socket = new WebSocket('wss://server.weiyigeek.top/ws');
// Connection opened
socket.addEventListener('open', (event) => {
console.log('Connected to echo server');
socket.send('Hello, WebSocket Echo Server!');
});
// Listen for echoed messages
socket.addEventListener('message', (event) => {
console.log('Echoed back:', event.data);
});
// Handle errors and close events
socket.addEventListener('error', (event) => {
console.error('WebSocket error:', event);
});
socket.addEventListener('close', (event) => {
console.log('Disconnected from echo server');
});
总结
本文介绍了 WebSocket 协议的基础原理,并通过一个完整的实践演示了如何使用 Nginx 配置 WebSocket 反向代理,涵盖了从搭建简易 Python 服务端到配置支持 ws/wss 协议的全过程。掌握这一配置,对于构建实时聊天、在线协作、游戏等需要双向通信的 Web 应用至关重要。希望这篇实践指南能帮助你顺利部署自己的 WebSocket 服务。如果你想与更多开发者交流此类网络与系统配置经验,欢迎到 云栈社区 参与讨论。