openclaw 使用 nginx 反代部署过程 与 disconnected (1008): pairing required 解决
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 violationpairing required是 openclaw 自己抛的业务语义——它认为当前连接没有完成设备配对所以拒绝建立可信会话。为什么直连127.0.0.1:65530没事一套 nginx 就炸因为 openclaw 的 Control UI 需要 secure contextHTTPS 或 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_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite。生成后先别关页面Key 只展示一次。想先确认模型能不能通用模型对话页发一条测试消息即可https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite。如果你后面要长期跑编码类 Agent可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite。拿到三件套后先在服务器上用 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.comopenclaw 监听127.0.0.1:65530证书用 Lets 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 连接。状态应该是101Messages 里能看到双向帧在流动。如果状态是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/apiKey 填生成的密钥Model ID 填控制台里的准确名称。只填 Key 不填 Base URL或者 Base URL 带了多余路径都会导致reading choices类报错。1008 反复出现但握手是 101。回到第 2 节的openclaw.json确认bind是loopback、mode是local。如果bind被改成了0.0.0.0openclaw 可能认为自己在非可信网络下配对策略变严。另外确认allowInsecureAuth保持false配合 HTTPS 使用只有在 HTTPS 实在上不了的 break-glass 场景才临时开dangerouslyDisableDeviceAuth调完立刻关掉并跑openclaw security audit确认。6. 把链路固定下来从握手到模型调用的完整闭环整套链路跑通后你的访问路径是这样的浏览器 →https://openclaw.example.comnginx TLS 终止→127.0.0.1:65530openclaw 网关WebSocket 升级→ 设备配对通过 → 控制台发消息 → openclaw 调用https://taotoken.net/api的模型接口 → 流式返回。每一段都有独立的验证手段nginx 层看 101openclaw 层看security audit模型层看 curl 的choices。日常维护记住三个动作。改完 nginx 先nginx -t再 reload别直接 restart。openclaw 升级后重新跑一次openclaw security audit确认配对相关开关没被默认打开。模型侧如果换 Key只改环境变量或配置文件里的 KeyBase URL 和 Model ID 不动避免引入新变量。如果你还要接 Claude Code 或做长期编码 Agent接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite里面有各工具的完整配置示例。控制台管理 Key 在https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite。需要新 Key 或轮换旧 Key走 API Keys 页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite。Claude Code 的 Anthropic 兼容接入参考https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_code_anthropicutm_campaignrewrite。最后留一个我踩过的坑nginx 的map块如果写在server里面reload 会报错但旧配置还在跑你会以为新配置生效了其实没有。改完一定看nginx -t的输出确认syntax is ok和test is successful两行都在。握手 101 拿到手isSecureContext为 truedisconnected (1008): pairing required就不会再出现了。