☰
openclaw 使用 nginx 反代部署过程 与 disconnected (1008): pairing required 解决
2026/10/7 20:04:56 网站建设 项目流程

1. openclaw 反代后 WebSocket 报 disconnected (1008): pairing required 到底卡在哪

openclaw 是一个把本地网关能力暴露成 Web 控制台的工具,默认监听127.0.0.1:65530,浏览器打开 Control UI 就能对话、跑 Agent、看日志。它适合想在自己服务器上跑一套私有 AI 控制台的人,尤其是已经用 nginx 管着一堆站点的运维同学。问题也正出在这里:你把它放到 nginx 后面,域名访问页面能开,但控制台一直转圈,控制台里刷出disconnected (1008): pairing required,聊天发不出去,设备配对也过不去。

这个报错的关键词拆开看就明白了。1008是 WebSocket 关闭码里的 policy violation,pairing required是 openclaw 自己抛的业务语义——它认为当前连接没有完成设备配对,所以拒绝建立可信会话。为什么直连127.0.0.1:65530没事,一套 nginx 就炸?因为 openclaw 的 Control UI 需要 secure context(HTTPS 或 localhost)来生成设备身份,而 nginx 反代默认只转发了普通 HTTP 请求,WebSocket 的Upgrade/Connection头没透传,握手在 nginx 层就被降级成了普通请求,openclaw 侧拿不到完整的握手上下文,设备身份生成失败,于是回你一个 1008。

我试过最典型的翻车现场:宝塔面板里加反向代理,目标 URL 填http://127.0.0.1:65530,发送域名填127.0.0.1,保存后页面能开,但控制台 WebSocket 那一路直接 400 或秒断。原因就是宝塔默认生成的反代配置里没有 WebSocket 升级段,proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade";这两行缺失,proxy_read_timeout也还是默认 60s,长连接撑不住。

所以这篇要解决的是完整链路:nginx 的 server/location 片段怎么写、Upgrade 头怎么转、超时怎么调、路径怎么重写,以及 openclaw 侧openclaw.json里配对相关参数怎么配。最后给你 curl 和浏览器控制台两个验证动作,确认握手真的成功,而不是页面能开就以为好了。

需要先明确一点:allowInsecureAuth和dangerouslyDisableDeviceAuth这两个开关是 break-glass 场景用的,官方明确说allowInsecureAuth并不会绕过 secure-context、设备身份或设备配对检查,dangerouslyDisableDeviceAuth更是严重的安全降级,openclaw security audit会直接告警。所以正确姿势是优先把 HTTPS 配好,让 secure context 成立,而不是一上来就关校验。下面所有配置都围绕这个原则展开。

2. TaoToken 前置准备:把模型侧和网关侧先理顺

在动 nginx 之前,先把 openclaw 依赖的模型调用链路准备好,否则你反代配通了,控制台里发消息还是报模型错误,排查会互相干扰。openclaw 的网关负责设备配对和 UI,真正的模型推理走的是外部 API,这里用 TaoToken 作为统一入口最省事:一个 Key 覆盖多家模型,Base URL 固定,不用在 openclaw 里为每个模型改配置。

你需要准备三样东西,我把它叫「三件套」,后面所有配置都围绕它:

项目值说明
Base URLhttps://taotoken.net/apiOpenAI 兼容协议入口,不加任何 UTM 后缀
API Key在控制台生成形如sk-...,只显示一次,复制保存
Model ID例如claude-sonnet-4-5等以控制台模型列表为准,别照抄

获取路径很直接:打开 API Keys 页面生成密钥,地址是https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite。生成后先别关页面,Key 只展示一次。想先确认模型能不能通,用模型对话页发一条测试消息即可:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite。如果你后面要长期跑编码类 Agent,可以看 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite。

拿到三件套后,先在服务器上用 curl 验证模型侧是通的,这一步和 nginx 无关,但能帮你把变量隔离出来:

curl -sS https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-5", "messages": [{"role": "user", "content": "ping"}], "max_tokens": 16 }'

返回里出现choices数组就说明模型侧没问题。如果这里就报 401,那是 Key 的问题,跟 nginx 一点关系都没有,先解决它。这一步做完,你后面遇到disconnected (1008)就能确定是网关/反代层的事,而不是模型层。

openclaw 侧的配置文件是openclaw.json,网关部分至少要包含端口、绑定和 controlUi 段。基础骨架长这样,先别急着加危险开关:

{ "gateway": { "port": 65530, "mode": "local", "bind": "loopback", "controlUi": { "allowInsecureAuth": false, "dangerouslyDisableDeviceAuth": false } } }

bind: loopback意味着只监听127.0.0.1,外部访问必须经过 nginx,这正是我们要的架构:nginx 负责 TLS 和转发,openclaw 只信任本机。mode: local表示本地模式。这两个值别乱改,改成0.0.0.0会把网关直接暴露到公网,配对校验反而更容易出问题。

3. nginx 反代可复制配置:Upgrade 头、超时与路径重写

这一节是核心,直接给你能粘贴的片段。假设你的域名是openclaw.example.com,openclaw 监听127.0.0.1:65530,证书用 Let's Encrypt 或你自己的证书。先看完整的 server 块:

map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { listen 443 ssl http2; server_name openclaw.example.com; ssl_certificate /etc/nginx/ssl/openclaw.example.com.pem; ssl_certificate_key /etc/nginx/ssl/openclaw.example.com.key; # 控制台静态资源与 API location / { proxy_pass http://127.0.0.1:65530; proxy_http_version 1.1; 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; # WebSocket 升级关键三行 proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; # 长连接超时,别用默认 60s proxy_read_timeout 3600s; proxy_send_timeout 3600s; proxy_connect_timeout 30s; # 关闭缓冲,避免流式响应被攒着 proxy_buffering off; proxy_cache off; } }

逐行解释几个容易踩的点。map $http_upgrade $connection_upgrade必须放在http块里,不能塞进server,否则 nginx 启动直接报unknown variable。它的作用是:当请求带Upgrade头时,Connection设为upgrade;否则设为close。很多人图省事写死proxy_set_header Connection "upgrade";,普通 HTTP 请求也会带上 upgrade,某些后端会因此行为异常,用 map 更稳。

proxy_http_version 1.1是 WebSocket 的前提,HTTP/1.0 不支持升级。proxy_read_timeout 3600s决定 nginx 等后端响应的最长时间,openclaw 的 WebSocket 是长连接,默认 60s 会被 nginx 主动掐断,表现就是控制台每隔一分钟断一次,然后刷disconnected (1008)。proxy_buffering off对 SSE/流式输出很重要,开着缓冲你会看到消息一次性蹦出来而不是逐字输出。

如果你的 openclaw 挂在子路径下,比如https://openclaw.example.com/ui/,需要做路径重写。注意 WebSocket 的路径也要一起重写,否则握手地址对不上:

location /ui/ { proxy_pass http://127.0.0.1:65530/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 3600s; proxy_buffering off; }

proxy_pass末尾带/表示把/ui/前缀剥掉再转发,openclaw 侧收到的还是根路径。这里有个坑:如果 openclaw 前端代码里写死了 WebSocket 的绝对路径(比如/ws),子路径部署时握手会打到https://openclaw.example.com/ws而不是/ui/ws,照样 1008。遇到这种情况,要么把 openclaw 部署在根路径,要么在前端配置里指定 ws 基址。实测下来,根路径部署最省心,子路径适合你确实有多个服务要共用域名。

如果你用宝塔面板,别直接用它生成的反代配置,它默认不带 WebSocket 段。正确做法是在宝塔的「网站 → 设置 → 配置文件」里手动把上面的 location 段贴进去,或者用「反向代理 → 自定义配置文件」覆盖。目标 URL 填http://127.0.0.1:65530,发送域名填$host或127.0.0.1都行,关键是那三行 Upgrade 头必须手动补上。

HTTPS 这块再强调一次:openclaw 的 Control UI 需要 secure context 才能生成设备身份。你用 HTTPS 访问,secure context 成立,配对流程正常走;你用纯 HTTP 访问公网域名,secure context 不成立,设备身份生成不了,就会一直pairing required。所以证书不是可选项,是必需项。本地调试可以用http://127.0.0.1:65530直连,因为 localhost 被视为 secure context。

4. 验证握手成功:curl 与浏览器控制台两个动作

配置改完,nginx -t检查语法,然后nginx -s reload。别急着开浏览器,先用 curl 验证 WebSocket 握手,这一步能直接看到 101 状态码,比看页面靠谱。

curl -i -N \ -H "Connection: Upgrade" \ -H "Upgrade: websocket" \ -H "Sec-WebSocket-Version: 13" \ -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \ -H "Host: openclaw.example.com" \ https://openclaw.example.com/ws

期望看到HTTP/1.1 101 Switching Protocols,以及响应头里的Upgrade: websocket和Connection: upgrade。如果返回 400 或 426,说明 Upgrade 头没透传,回去检查map和proxy_set_header。如果返回 502,说明 nginx 连不上127.0.0.1:65530,先确认 openclaw 进程在跑、端口在听:ss -lntp | grep 65530。

握手通了之后,打开浏览器控制台(F12 → Network → WS 标签),刷新页面,找到那条 WebSocket 连接。状态应该是101,Messages 里能看到双向帧在流动。如果状态是101但很快变成 closed,看 Close 帧的 code 是不是 1008。是 1008 就说明 nginx 层通了,但 openclaw 侧配对没过,往下看第 5 节的排查。

再补一个验证 secure context 的动作,在浏览器控制台执行:

console.log(window.isSecureContext);

HTTPS 访问时应该是true。如果是false,设备身份生成会失败,配对必然过不去。这时候要么修证书,要么临时用http://127.0.0.1:65530本地访问。

openclaw 侧还可以跑一次安全审计,确认当前配置状态:

openclaw security audit

如果你开了dangerouslyDisableDeviceAuth,这里会明确告警。审计通过、握手 101、isSecureContext为 true,三个条件齐了,控制台就不会再刷disconnected (1008): pairing required。

5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth

这一节按真实报错对照,都是我在部署过程中实际撞到的。

401 Unauthorized。两种来源要分清。如果 curl 模型接口报 401,是 TaoToken 的 Key 错了或没带Authorization头,检查Bearer后面有没有多余空格。如果 openclaw 控制台里报 401,是网关侧的 token 不对,检查openclaw.json里 controlUi 的 token 配置和浏览器里填的是否一致。别把这两个 401 混为一谈。

local proxy failed。这个通常出现在 openclaw 尝试通过本地代理访问外部模型时。检查openclaw.json里有没有配置代理地址,以及那个地址是否可达。如果你在服务器上设了HTTP_PROXY环境变量但代理没跑,openclaw 会报这个。用env | grep -i proxy看一眼,不需要就 unset 掉。

reading choices 报错。形如cannot read property 'choices' of undefined,说明模型返回体不是预期的 OpenAI 格式。常见原因是 Base URL 写错了,比如写成了https://taotoken.net而漏了/api,或者路径拼成了/v1/chat/completions但 Base 里已经带了/v1,导致最终 URL 变成/v1/v1/...。正确写法是 Base URL 用https://taotoken.net/api,请求路径用/v1/chat/completions。Model ID 写错也会导致返回错误结构,以控制台模型列表为准。

OAuth 相关报错。如果你用 Claude Code 或 Codex 这类需要 OAuth 的工具接 openclaw,报 OAuth 失败通常是回调地址和 nginx 反代后的地址不一致。OAuth 回调必须走你配置的公网 HTTPS 域名,不能是127.0.0.1。检查 nginx 有没有把X-Forwarded-Proto透传,后端要靠它判断原始协议来拼回调 URL。缺了这个头,后端以为是 HTTP,回调地址就错了。

CC Switch / Cline MCP / Codex auth.json 三件套。如果你在 openclaw 里挂这些工具,配置必须写全 Base URL、Key、Model ID 三项,缺一不可。以 Codex 的auth.json为例:

{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的Key", "model": "claude-sonnet-4-5" }

Cline 的 MCP 配置同理,Base URL 指向https://taotoken.net/api,Key 填生成的密钥,Model ID 填控制台里的准确名称。只填 Key 不填 Base URL,或者 Base URL 带了多余路径,都会导致reading choices类报错。

1008 反复出现但握手是 101。回到第 2 节的openclaw.json,确认bind是loopback、mode是local。如果bind被改成了0.0.0.0,openclaw 可能认为自己在非可信网络下,配对策略变严。另外确认allowInsecureAuth保持false,配合 HTTPS 使用;只有在 HTTPS 实在上不了的 break-glass 场景,才临时开dangerouslyDisableDeviceAuth,调完立刻关掉并跑openclaw security audit确认。

6. 把链路固定下来:从握手到模型调用的完整闭环

整套链路跑通后,你的访问路径是这样的:浏览器 →https://openclaw.example.com(nginx TLS 终止)→127.0.0.1:65530(openclaw 网关,WebSocket 升级)→ 设备配对通过 → 控制台发消息 → openclaw 调用https://taotoken.net/api的模型接口 → 流式返回。每一段都有独立的验证手段:nginx 层看 101,openclaw 层看security audit,模型层看 curl 的choices。

日常维护记住三个动作。改完 nginx 先nginx -t再 reload,别直接 restart。openclaw 升级后重新跑一次openclaw security audit,确认配对相关开关没被默认打开。模型侧如果换 Key,只改环境变量或配置文件里的 Key,Base URL 和 Model ID 不动,避免引入新变量。

如果你还要接 Claude Code 或做长期编码 Agent,接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite,里面有各工具的完整配置示例。控制台管理 Key 在https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite。需要新 Key 或轮换旧 Key,走 API Keys 页面:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite。Claude Code 的 Anthropic 兼容接入参考https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claude_code_anthropic&utm_campaign=rewrite。

最后留一个我踩过的坑:nginx 的map块如果写在server里面,reload 会报错但旧配置还在跑,你会以为新配置生效了其实没有。改完一定看nginx -t的输出,确认syntax is ok和test is successful两行都在。握手 101 拿到手,isSecureContext为 true,disconnected (1008): pairing required就不会再出现了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询