LI YAO'S BLOG

有趣的人生, 一半家长里短, 一半山川胡海

Nginx 配置 WebSocket 重定向踩坑记 - OpenClaw 部署实战

问题背景

最近在服务器上部署了 OpenClaw,初始访问需要使用完整的 URL:

1
https://openclaw.iambeyouare.com/chat?session=main&token=7173f...

希望能简化访问方式,直接输入 openclaw.iambeyouare.com 就能自动跳转到聊天页面,让 nginx 帮忙拼接后面的参数。

初始配置

原始的 nginx 配置 (/etc/nginx/conf.d/openclaw.conf):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
server {
listen 443 ssl;
http2 on;
server_name openclaw.iambeyouare.com;

ssl_certificate /etc/letsencrypt/live/iambeyouare.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/iambeyouare.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

add_header X-Content-Type-Options nosniff always;

location / {
proxy_pass http://127.0.0.1:18789;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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 https;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}

我的”解决方案”

为了让根路径自动跳转,我添加了 location = / 块:

1
2
3
4
5
6
7
8
9
10
# 根路径自动跳转到聊天页面
location = / {
return 301 https://$host/chat?session=main&token=7173f...;
}

# WebSocket / HTTP 反代到 OpenClaw Gateway
location / {
proxy_pass http://127.0.0.1:18789;
# ... 其他配置
}

看起来完美:精确匹配根路径做重定向,其他路径正常代理。

测试配置通过,重载 nginx:

1
sudo nginx -t && sudo systemctl reload nginx

问题爆发

配置生效后,访问 https://openclaw.iambeyouare.com/ 出现错误:

1
Disconnected from gateway. disconnected (1006): no reason

页面无法正常连接,WebSocket 断开。

问题分析

错误原因

OpenClaw 是一个实时聊天应用,依赖 WebSocket 长连接。工作流程如下:

  1. 浏览器访问根路径 /,加载 HTML 页面
  2. 页面中的 JavaScript 代码立即建立 WebSocket 连接到 /
  3. 我的配置对所有访问 / 的请求都做了 301 重定向
  4. WebSocket 握手请求也被重定向到了 /chat?...
  5. WebSocket 连接失败 → 错误码 1006(异常断开)

核心问题

没有区分普通 HTTP 请求和 WebSocket 请求,一刀切的重定向导致 WebSocket 无法建立连接。

正确的修复方案

第一步:在 nginx.conf 中添加 WebSocket 检测

在 http 块中添加以下 map 指令:

1
2
3
4
5
6
7
8
9
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

map $http_upgrade $is_ws {
default 0;
websocket 1;
}
  • $http_upgrade:HTTP 请求头中的 Upgrade 字段
  • WebSocket 握手时,该字段值为 websocket
  • 普通请求时,该字段为空或其他值

第二步:修改 openclaw.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
server {
listen 443 ssl;
http2 on;
server_name openclaw.iambeyouare.com;

ssl_certificate /etc/letsencrypt/live/iambeyouare.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/iambeyouare.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

add_header X-Content-Type-Options nosniff always;

# 根路径:如果是 WebSocket,走反代;否则跳转到 /chat?...token=...
location = / {
# 通过 if 只做"跳转/不跳转"的控制(允许 return / rewrite),不写 proxy 指令
if ($is_ws = 0) {
return 302 /chat?session=main&token=7173f...;
}

# 是 WebSocket:把 / 交给上游
proxy_pass http://127.0.0.1:18789;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

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_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}

# 其它路径全部反代
location / {
proxy_pass http://127.0.0.1:18789;

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

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_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}

关键点

  1. 使用 if ($is_ws = 0) 判断是否为普通 HTTP 请求
  2. 只有非 WebSocket 请求才执行重定向
  3. WebSocket 请求正常代理到后端网关
  4. 使用 302 而非 301,避免浏览器缓存重定向规则

我的反思

错误原因总结

问题 说明
没考虑 WebSocket OpenClaw 是实时聊天应用,依赖 WebSocket 长连接
一刀切重定向 对所有 / 请求无差别重定向,包括 API 和 WebSocket
缺乏场景分析 应该区分”人类访问页面”和”程序建立连接”

教训

配置涉及 WebSocket 的服务时,必须:

  1. 识别 WebSocket 请求(通过 $http_upgrade 头)
  2. 只对普通 HTTP 请求做重定向
  3. WebSocket 连接必须原样代理到后端

正确的思考方式

在修改 nginx 配置前,应该问自己:

  • 这个服务使用什么协议?(HTTP/HTTPS/WebSocket)
  • 有哪些类型的请求会访问这个路径?
  • 重定向会影响哪些请求?
  • 是否有特殊请求需要例外处理?

最终效果

修复后:

  • ✅ 访问 https://openclaw.iambeyouare.com/ → 浏览器自动跳转到聊天页面
  • ✅ WebSocket 连接正常建立 → 实时消息功能正常
  • ✅ 其他路径正常代理 → API、静态资源等均不受影响

参考资源


总结:这次踩坑让我深刻理解了 WebSocket 的工作原理和 nginx 配置的细节。在涉及实时通信的应用部署时,一定要仔细分析请求类型,避免”想当然”的配置。

赞赏一杯咖啡