OpenClaw 远程访问三条路:SSH 隧道、Tailscale Serve 与直连绑定该怎么选
把 OpenClaw 网关跑在一台常开的机器上,然后从笔记本、手机上连过去,是个很自然的需求。但真去连的时候,第一反应往往是「怎么连不上」——网关明明起来了,本机 openclaw health 也正常,换台设备就是没反应。
原因在官方文档第一段就写着:网关的 WebSocket 默认绑定 loopback,端口是 18789(配置项 gateway.port)。也就是说它只接受来自本机 127.0.0.1 的连接,局域网里的其它机器根本够不着。这不是 bug,是默认的安全姿态。
所以远程访问这件事,本质上是「怎么在不把这个端口暴露出去的前提下,让远端客户端够到它」。官方文档给的路径有三条:SSH 端口转发、Tailscale Serve(顺带一个公网的 Funnel 变体)、以及在可信内网或 Tailnet 上直接绑定。三条路不是并列的备选项,适用场景差得挺远。
先搞清楚谁是服务端、谁是客户端
这一层不理顺,后面的配置会一直配错地方。
按 docs/gateway/remote.md 的说法:OpenClaw 在一台主机上跑一个网关(master),其余所有东西都是客户端连到它。网关自己持有会话、鉴权档案、渠道和状态。
- 操作端(你本人,或 macOS 应用):网关可达时,直接走局域网 / Tailnet 的 WebSocket 最省事;SSH 隧道是万能兜底。
- 节点(iOS / Android 及其它设备):一律连网关的 WebSocket,走局域网、Tailnet 或 SSH 隧道。
文档里有个例子说明「命令在哪儿执行」:一条 Telegram 消息进来,先到网关;网关跑 agent,agent 决定要不要调某个节点上的工具;网关通过网关 WebSocket 的 node.invoke RPC 调用节点;节点返回结果,网关回复 Telegram。
要点是:节点不跑网关服务。同一台主机上原则上只应该跑一个网关,除非你是有意用隔离的 profile 跑多个网关。macOS 应用的所谓「节点模式」,就是一个通过网关 WebSocket 接入的节点客户端而已。整体的四层关系可以先看架构综览。
三种拓扑,文档自己给了对照
| 场景 | 网关跑在哪 | 适合谁 |
|---|---|---|
| 常开网关在你的 Tailnet 里 | 持久主机(VPS 或家庭服务器),通过 Tailscale 或 SSH 到达 | 笔记本经常合盖休眠,但要求 agent 一直在线 |
| 家里的台式机 | 台式机;笔记本通过 macOS 应用的远程模式连过来 | 想把 agent 放在一直通电的硬件上 |
| 笔记本本机 | 笔记本,通过 SSH 隧道或 Tailscale Serve 安全暴露(保持 gateway.bind: "loopback") | 单机使用 |
文档给的倾向很明确:常开和笔记本这两种场景,优先保持 gateway.bind: "loopback",Control UI 走 Tailscale Serve;或者在可信的局域网 / Tailnet 上用 gateway.remote.transport: "direct" 绑定。SSH 隧道则是「任何机器上都能用」的兜底。
路线一:SSH 隧道,最土但最通用
一条命令的事:
ssh -N -L 18789:127.0.0.1:18789 user@gateway-host
隧道起来之后,openclaw health 和 openclaw status --deep 就会通过 ws://127.0.0.1:18789 打到远端网关上。另外 openclaw gateway status、openclaw gateway health、openclaw gateway probe、openclaw gateway call 这几条也可以用 --url 直接指向转发后的地址。健康检查这块的两套机制差异,见health 与 heartbeat。
两个必须记住的坑:
第一,18789 要换成你实际配置的 gateway.port(也可能来自 --port 或 OPENCLAW_GATEWAY_PORT)。
第二,官方专门用 Warning 强调:--url 永远不会回退去取配置或环境里的凭据。你必须显式传 --token 或 --password;不传的话客户端就是不带凭据连过去,对端要求鉴权时必然失败。这条是很多「命令行连不上但网页能开」的直接原因。
想让 CLI 默认走远端,可以把目标持久化到配置里:
{
gateway: {
mode: "remote",
remote: {
url: "ws://127.0.0.1:18789",
token: "your-token",
},
},
}
网关只监听 loopback 时,这个 URL 就保持写 ws://127.0.0.1:18789,然后先把 SSH 隧道拉起来。在 macOS 应用的 SSH 隧道传输模式下,探测到的网关主机名写在 gateway.remote.sshTarget(user@host 或 user@host:port),而 gateway.remote.url 仍然保持本地隧道地址;远端端口和本地不一致时,另外设 gateway.remote.remotePort。
主机密钥校验默认是严格模式(gateway.remote.sshHostKeyPolicy: "strict")。可以改成 "openssh" 把校验交给你自己的 OpenSSH 配置,但文档提醒:开之前先把用户级和系统级的 SSH 配置检查一遍。
macOS 上把隧道守住
文档给了一套 LaunchAgent 方案,让隧道在重启和崩溃后自动恢复。先在 ~/.ssh/config 里加一段:
Host remote-gateway
HostName <REMOTE_IP>
User <REMOTE_USER>
LocalForward 18789 127.0.0.1:18789
IdentityFile ~/.ssh/id_rsa
一次性把公钥推过去(ssh-copy-id -i ~/.ssh/id_rsa <REMOTE_USER>@<REMOTE_IP>),再配上网关令牌:
openclaw config set gateway.remote.token "<your-token>"
对端用密码鉴权就换成 gateway.remote.password。OPENCLAW_GATEWAY_TOKEN 作为 shell 级覆盖依然有效,但文档说得很清楚:持久的远程客户端配置应该落在 gateway.remote.token / gateway.remote.password 上。凭据本身怎么存,见密钥与凭据管理。
最后把 LaunchAgent 存成 ~/Library/LaunchAgents/ai.openclaw.ssh-tunnel.plist(ProgramArguments 是 /usr/bin/ssh -N remote-gateway,KeepAlive 和 RunAtLoad 都置 true),然后加载:
launchctl bootstrap gui/$UID ~/Library/LaunchAgents/ai.openclaw.ssh-tunnel.plist
排查用这几条:
ps aux | grep "ssh -N remote-gateway" | grep -v grep
lsof -i :18789
launchctl kickstart -k gui/$UID/ai.openclaw.ssh-tunnel
launchctl bootout gui/$UID/ai.openclaw.ssh-tunnel
如果你从旧版本升上来,机器上可能还留着一个 com.openclaw.ssh-tunnel 的旧 LaunchAgent,文档要求卸载并删掉它。
路线二:Tailscale Serve,把 N 条隧道换成一个地址
SSH 隧道的问题是「每个客户端都得自己拉一条」。手机上没法这么干——iOS 和 Android 的伴侣应用是直连网关 WebSocket 的,不管理 SSH 隧道传输。
Tailscale Serve 的思路是:网关继续只听 loopback,由 Tailscale 终结 HTTPS 并反代进来。结果是一个 https://<host>.<tailnet>.ts.net 地址,对应的 WebSocket 是 wss://<host>.<tailnet>.ts.net,Tailnet 内被允许的设备可达,公网不可达。
开之前需要满足这几条:Tailnet 开了 MagicDNS;Tailscale 管理后台 DNS > HTTPS Certificates 里开了 HTTPS 证书;网关主机装了 Tailscale 并已登录;网关已经配好 token、password 或 trusted-proxy 鉴权——Serve 不能和 gateway.auth.mode: "none" 组合使用。
OpenClaw 会自动找 Tailscale CLI:先看 PATH,再看 macOS 应用包路径 /Applications/Tailscale.app/Contents/MacOS/Tailscale,再看 /Applications 下其它匹配安装,最后查系统 locate 数据库。macOS 用户不需要手动把应用包里的二进制加进 PATH。
在网关主机上执行:
openclaw config set gateway.bind loopback
openclaw config set gateway.tailscale.mode serve
openclaw gateway restart
gateway.tailscale.mode 三个取值:serve 是仅 Tailnet 内可达,网关留在 127.0.0.1;funnel 是公网 HTTPS,要求必须配共享密码;off 是默认值,表示 OpenClaw 不托管 Serve/Funnel(注意这不等于本机 Tailscale 守护进程停了或登出了)。
Serve 开启后,OpenClaw 让 Tailscale 在 443 上提供 HTTPS,反代到一个由网关持有的私有临时 loopback 监听器;而普通的网关监听器默认仍然在 127.0.0.1:18789 上,供本机直连客户端使用。
最容易踩的那个坑:Tailnet 策略没放行 443
文档专门用一节讲这个:Tailscale 的访问控制对 Serve 同样生效。策略严格的 Tailnet 里,你得允许客户端设备访问网关主机的 TCP 443。
没这条授权时,Serve 地址在网关主机上能打开,但从其它每一台设备访问都会静默超时——这个症状看起来像网关坏了,实际上是 Tailnet 策略在挡。
新式 grants 策略里加一条:
{
"src": ["autogroup:member"],
"dst": ["<gateway-host-or-ip>"],
"ip": ["tcp:443"]
}
老式 ACL 策略则是往 acls 数组里加:
{
"action": "accept",
"src": ["autogroup:member"],
"dst": ["<gateway-host-or-ip>:443"]
}
autogroup:member 是放行所有已认证的 Tailnet 成员。想收紧就换成更窄的用户、组、tag 或设备选择器,只覆盖真正需要访问网关的客户端。
验证三步
先在网关主机上确认路由:
tailscale serve status
输出应该显示一条指向 https://<host>.<tailnet>.ts.net 的 HTTPS 路由,反代到网关持有的私有临时 loopback 端口。
再从同 Tailnet 的另一台设备上测:
curl -sS -o /dev/null -w '%{http_code}\n' https://<host>.<tailnet>.ts.net/
Control UI 根路径应该返回 200。如果这里超时、而同样的命令在网关主机上返回 200,先回去查上一步的 TCP 443 授权。
最后证明网关进程没有自己把端口开到网络上:
lsof -nP -iTCP:<port> -sTCP:LISTEN
默认端口就填 18789。网关监听器应该在 127.0.0.1:<port>,而不是 0.0.0.0:<port> 或者某个局域网 / Tailnet 地址。HTTPS 监听和反代路径归 Tailscale 管。
Serve 的身份头鉴权,边界要看清
tailscale.mode: "serve" 且 gateway.auth.allowTailscale 为 true 时,Control UI 的 WebSocket 鉴权可以用 Tailscale 身份头(tailscale-user-login)替代 token/password。OpenClaw 的校验方式是:用本机 Tailscale 守护进程解析请求的 x-forwarded-for 地址(tailscale whois),和身份头里的登录名比对上才接受。而且请求必须落到 OpenClaw 专用的托管 Tailscale 监听器上、并带着 Tailscale 的 x-forwarded-for、x-forwarded-proto、x-forwarded-host 头;往普通网关监听器上手动塞这些头会被拒绝。
这个免密流程假定网关主机是可信的。如果同一台主机上可能跑不受信任的本地代码,就把 gateway.auth.allowTailscale 设为 false,改用 token/password。
绕过的范围也要看准,它只作用于 Control UI 的 WebSocket 鉴权面,以及 Control UI 头像的只读 GET/HEAD 请求。其它 HTTP API 端点(/v1/*、/tools/invoke、/api/channels/* 等)从不使用 Tailscale 身份头鉴权,一律走网关正常的 HTTP 鉴权模式。它也不绕过浏览器设备身份:没有设备身份的客户端照样被拒,节点角色的连接照样走正常配对和鉴权。
顺便说一句 Serve 的前置条件与限制:Serve 要求 Tailnet 启用 HTTPS,CLI 会在缺失时提示;Serve 会注入 Tailscale 身份头,Funnel 不会;Funnel 要求 Tailscale v1.38.3+、MagicDNS、启用 HTTPS 和 funnel 节点属性,且只支持 443、8443、10000 三个 TLS 端口,macOS 上还要求用开源版 Tailscale 应用。另外,托管入口不支持具名 Tailscale Service,老配置里带 gateway.tailscale.serviceName 的需要跑 openclaw doctor --fix;从旧版默认 HTTPS Serve 路由迁移过来也是走 doctor,openclaw doctor 先预览、--fix 应用、再重启网关。doctor 的输出怎么读,见doctor 体检报告。
路线三:直连绑定(LAN / Tailnet IP)
网关已经在可信局域网或 Tailnet 上可达时,可以用 direct 模式:
{
gateway: {
mode: "remote",
remote: {
transport: "direct",
url: "ws://192.168.0.202:18789",
token: "your-token",
},
},
}
也可以让网关直接监听 Tailnet IP,完全不走 Serve/Funnel:
{
gateway: {
bind: "tailnet",
auth: { mode: "token", token: "your-token" },
},
}
另一台 Tailnet 设备上的原生或 CLI 客户端用 ws://<tailscale-ip>:18789 连接。
但这里有条硬限制:不要用这个明文 HTTP 地址开浏览器 Control UI。文档的原话意思是,远程明文 HTTP 无法建立浏览器设备身份,而 token/password 鉴权并不能替代设备身份——浏览器端要用 Tailscale Serve。
还有个行为细节:存在可绑定的 Tailnet IPv4 时,网关同时会要求 http://127.0.0.1:18789 供已鉴权的同主机客户端使用;启动时如果拿不到 Tailnet 地址,就回退成仅 loopback,等 Tailscale 可用后重启网关才会加上直连 Tailnet 访问。这两条路径都不会额外增加局域网或公网暴露。
安全规则清单
这一节基本就是照抄文档的红线,逐条对照检查比事后排查省事:
| 规则 | 说明 |
|---|---|
| 默认保持 loopback | 除非确定需要绑定,否则别改 |
| loopback + SSH / Tailscale Serve | 官方称为最安全的默认组合,无公网暴露 |
明文 ws:// 的适用范围 | 仅 loopback、私有/局域网(RFC 1918)、链路本地、CGNAT、.local、.ts.net 主机;公网远端必须 wss:// |
| 非 loopback 绑定 | lan/tailnet/custom,或 loopback 不可用时的 auto,必须启用网关鉴权:token、password,或带 gateway.auth.mode: "trusted-proxy" 的身份感知反代 |
gateway.remote.token / .password | 是客户端凭据来源,本身不配置服务端鉴权 |
| 本地调用路径的回退 | 仅当 gateway.auth.* 未设置时,才可拿 gateway.remote.* 兜底 |
| SecretRef 解析失败 | gateway.auth.token / .password 若显式配了 SecretRef 却解析不出来,直接 fail closed,不做远程回退掩盖 |
gateway.remote.tlsFingerprint | 为 wss:// 固定远端 TLS 证书;没有存过 pin 时,macOS 只在系统信任通过后首次使用时 pin。自签名或私有 CA 的网关需要显式指纹,或者改走 SSH |
| trusted-proxy | 默认期望非 loopback 的身份感知代理;同主机 loopback 反代需显式设 gateway.auth.trustedProxy.allowLoopback = true |
| 浏览器控制 | 按操作端权限对待:仅 Tailnet + 有意识的节点配对 |
关于鉴权模式本身,gateway.auth.mode 有四个取值:none(仅私有入口)、token(设置了 OPENCLAW_GATEWAY_TOKEN 时的默认值)、password(走 OPENCLAW_GATEWAY_PASSWORD 或配置)、trusted-proxy。
凭据到底从哪儿取:优先级要背下来
远程连不上而报鉴权失败时,八成是取到了你以为没在用的那份凭据。文档把这套规则统一成一份契约,call / probe / status 各条路径共用(node-host 也用同一份,只有一个本地模式例外):
- 显式凭据(
--token、--password,或工具的gatewayToken)在接受显式鉴权的调用路径上永远优先。 - CLI
--url从不复用隐式的配置/环境凭据;环境变量OPENCLAW_GATEWAY_URL只能配合环境凭据(OPENCLAW_GATEWAY_TOKEN/OPENCLAW_GATEWAY_PASSWORD)。 - 本地模式默认:token 取
gateway.auth.token→OPENCLAW_GATEWAY_TOKEN→gateway.remote.token(最后这项只在本地 token 未设时兜底);password 同理,gateway.auth.password→OPENCLAW_GATEWAY_PASSWORD→gateway.remote.password。 - 远程模式默认:token 取
gateway.remote.token→OPENCLAW_GATEWAY_TOKEN→gateway.auth.token;password 取OPENCLAW_GATEWAY_PASSWORD→gateway.remote.password→gateway.auth.password。注意两者顺序不一样。 - node-host 的本地模式例外:环境凭据排第一,且忽略
gateway.remote.token/.password,因为节点命令的目标主机和端口是显式给定的。 - 带 SecretRef 的远程启动 / 状态 / 向导探测,把配置里的
gateway.remote.token和.password当作该目标的权威来源;只有两者都没配时才考虑环境里的凭据。配了但解析不出来时,探测会告警且不会回退到环境凭据。 - 网关的环境覆盖只认
OPENCLAW_GATEWAY_*前缀。
聊天界面和浏览器控制
WebChat 没有独立的 HTTP 端口,SwiftUI 聊天界面是直连网关 WebSocket 的。所以远程用法就三种:SSH 转发 18789 后连 ws://127.0.0.1:18789;局域网 / Tailnet 直连模式下连配置好的私有 ws:// 或安全 wss://;macOS 上由应用的远程模式自动管理所选传输。
浏览器控制是个单独的场景:网关在一台机器、浏览器在另一台时,做法是在浏览器所在机器上跑 node host,两台都在同一 Tailnet 里,网关把浏览器动作代理给节点,不需要额外的控制服务器或 Serve URL。文档明确建议浏览器控制避免用 Funnel,节点配对要按操作端权限对待。
什么时候这几条路都不合适
- 要给公网上的人访问:只有 Funnel 这一条,而且它强制 password 鉴权(
tailscale.mode: "funnel"在鉴权模式不是password时会拒绝启动),端口只能是 443/8443/10000。如果这些限制不满足你的场景,文档没有提供别的公网方案。 - 不想装 Tailscale:那 Serve/Funnel 整条线都用不了(要求
tailscaleCLI 已安装并登录),剩下的就是 SSH 隧道,或者在可信内网里直连绑定 + 网关鉴权。 - 手机端 + 不想用 Tailscale:iOS/Android 应用不管理 SSH 隧道传输,实际上就只剩「网关在移动设备够得着的可信网络上」这一种。
- 自签名或私有 CA 的
wss://:需要显式配gateway.remote.tlsFingerprint,否则 macOS 端只在系统信任通过后才首次 pin,路走不通就退回 SSH。 - Serve 用的是旧版 Tailscale:Serve CLI 在 Tailscale 1.52 变过,命令不可用时先升级客户端再说。
还有两件事这篇没覆盖:一是网关本身起不来(锁文件、端口占用、后台进程)的排查,那是另一条线;二是证书首次签发慢——文档只说初次 HTTPS 请求可能耗时较长,让它跑完再重试,没有给具体时长。真遇到「本机 200、别处超时」,还是先回到 TCP 443 那条 Tailnet 策略上查。
延伸阅读
- 从头读起:OpenClaw 架构总览:网关、Agent、渠道、节点、插件五层各管什么,出问题该往哪查
- 本专题共 40 篇,完整分组目录见专题页
- OpenClaw 的密钥与凭据怎么存:SecretRef 四种来源、审计迁移与哨兵机制拆解
- OpenClaw 一台机器跑多个网关:profile 隔离、端口派生与多租户 cell
本文依据 OpenClaw 官方仓库(github.com/openclaw/openclaw)docs/ 下的官方文档整理,核对日 2026-08-17。
我们没有安装或运行过 OpenClaw,因此不涉及界面外观、操作手感与实测耗时的任何描述;
文中的默认值、命令与配置项均为文档口径,不构成对实际运行结果的保证。
该项目迭代很快,请以仓库最新内容为准。接入即时通讯平台前,请自行确认所在平台的规则与合规要求。