返回博客
HermesTailscaleTroubleshootingSelf-Hosting

Hermes Agent 无法通过 Tailscale 连接:排查方案

Hermes Desktop 通过 Tailscale 连不上远程 gateway?四类原因几乎覆盖所有场景,从 localhost 绑定到 CORS 正则匹配。

作者:Hermify Team||阅读约 3 分钟
深色场景中 Tailscale 字标位于一台笔记本上方,笔记本正尝试穿越网状网连接到远程 Hermes gateway,画面上有醒目文字 'Tailscale Not Connecting'

Tailnet 已上线,Hermes 依然没有回应

你在 VPS 上安装了 Tailscale,从笔记本加入了同一个 tailnet,并确认双方都能在 100.x.x.x 地址上互相 ping 通。hermes serve 在主机上运行,端口开放着,而笔记本上的 Hermes Desktop 应用却一直卡在 "Could not connect to Hermes gateway"。gateway 日志里没有任何愤怒的报错。Tailscale 面板上也看不到红色。

这种静默失败几乎总是四个原因之一,其中三个天生就是静默失败。本文逐一拆解每一类,教你如何确认是哪一类,以及对应的确切修复方式。请从最上面开始:第一类原因涵盖了大多数第一次搭远程访问的场景,后面每一类都建立在前面已经排除的基础上。

原因 1:hermes serve 绑定在 127.0.0.1

hermes serve 默认绑定在 127.0.0.1。对于只在笔记本上使用的场景,这是正确的默认值;对于任何希望通过 tailnet 访问的场景,却是错误的默认值。绑定在 loopback 上的进程只响应来自本机的请求,而 Tailscale 对端并不是本机。端口开了,防火墙没问题,隧道也在,但 socket 就是拒绝连接。

症状:在笔记本上执行 curl -v http://<hermes-vps>:8642/api/health 返回 Connection refused 或直接超时挂起。从 VPS 上的 SSH 会话内运行同一个 curl http://127.0.0.1:8642/api/health 却能立刻返回。如果 loopback 能回,tailnet 不能回,这就是你的原因。

修复方式是把 hermes serve 显式绑定到主机的 Tailscale IP:

TAILSCALE_IP=$(tailscale ip -4)
hermes serve --host "$TAILSCALE_IP" --port 8642

把 socket 绑到 tailnet 接口而不是 0.0.0.0 才是正确姿势。0.0.0.0 也能工作,很多教程也这么教,但它会把 socket 暴露在这台机器的所有接口上,包括任何不小心暴露到公网的接口,并把整个鉴权责任重新推回应用层。绑定到 Tailscale IP 是纵深防御:socket 从一开始就只在 tailnet 内部可达。

要让改动持久生效,把同样的 flag 写进 systemd unit 或 docker-compose.ymlcommand 里。如果你在 Docker 里跑,直接用 -p ${TAILSCALE_IP}:8642:8642 把端口发布到 Tailscale IP 上,而不是默认的 -p 8642:8642(默认会发布到主机所有接口)。

想看第一次搭 Tailscale 的完整流程,Hermes Agent + Tailscale 安全远程访问指南 会带你从头走到尾。

原因 2:Dashboard 的 CORS 正则拒绝了你的 Tailscale 来源

你把 gateway 绑到了 Tailscale IP,API 能响应 /api/health,web dashboard 也从 http://<hermes-vps>:8642/ 加载了 HTML。然后 dashboard 发出的每一个 API 请求都在浏览器控制台里因 CORS 报错失败:has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

发生了什么:早期 Hermes 版本在 dashboard 里硬编码了一个 allow_origin_regex,只匹配 ^https?://(localhost|127\.0\.0\.1)(:\d+)?$。这个正则在笔记本本地是安全的,在其他任何地方都是静默失效。像 http://hermes-vps:8642 这种 Tailscale 主机名或者 http://100.64.1.5:8642 这种 IP 永远匹配不上,预检请求失败,浏览器就把 fetch 丢掉了。跟踪该修复的 feature request 有完整的历史。

修复方式是一个环境变量:

export HERMES_DASHBOARD_CORS_ORIGINS="http://hermes-vps:8642,http://100.64.1.5:8642"
hermes serve --host "$TAILSCALE_IP" --port 8642

把你实际打开 dashboard 的每一个来源都列出来:MagicDNS 名称、原始的 Tailscale IP,以及任何你加过的 Funnel 或 serve 别名。也支持通配符(http://*.tail1a2b3.ts.net:8642),如果你更愿意匹配整个 tailnet 名而不是一个个列设备,可以这样写。

有两个相关设置容易踩坑:

  • HERMES_DASHBOARD_HOST 会覆盖 dashboard 向浏览器公告的地址。如果保留了 localhost,dashboard 生成的链接会指回 http://localhost:8642/api/...,浏览器就去访问自己的 loopback,而不是穿越 tailnet。把它设成你的 Tailscale 主机名或 IP。
  • Hermes Desktop 应用同样带着 Origin。 如果你用的是打包版桌面应用而不是浏览器 dashboard,它的 renderer 会发送 Origin: null(Electron 通过 file:// 加载页面)。旧版本只有在服务端绑定 loopback 时才接受这个 null,这就是 issue #38412 描述的互斥情况。新版本在 HERMES_DASHBOARD_CORS_ORIGINS 中同时列出真实来源和字面量 null 时才允许桌面客户端,请把字面字符串 null 加到列表里。

修改任何这些环境变量之后,记得重启 hermes serve。它们只在启动时读取,不会随请求变化。

原因 3:Tailscale 隧道退回 DERP 或者根本没建立

如果 dashboard 最终能加载,但每条消息要好几秒才发出,语音消息断断续续,那说明隧道是通的但很慢。Tailscale 正在通过 DERP 服务器中转每一个包再送到你的 VPS,往返时间被这一跳而不是模型本身主导。如果什么都过不去,那大概率隧道根本没建立起来。

tailscale status 确认你处在哪种情况。健康的对端在自己的一行里会显示 direct <ip>:<port>。走 DERP 中转的对端会显示 relay "<region>"。如果对端完全不见了或被标为 offline,说明隧道从未建立。

两种情况的修复方式不同:

  • 卡在 DERP。 在 VPS 主机防火墙和客户端网络防火墙上都开放出站 UDP 41641。这是 Tailscale 用于建立 WireGuard 直连的端口;只要有一侧封锁了出站 UDP,即使这一对已完成认证,双方也会退回 DERP。在 VPS 上执行 sudo ufw allow 41641/udp 确认,并在 tailscale down && tailscale up 之后再 ping 一次对端。企业内网和酒店 Wi-Fi 是封锁出站 UDP 的常见嫌疑。如果直连始终不可能,DERP 处理文字够用,但语音会明显有感。
  • 对端被标 offline 或者隧道从未上线。 节点 key 过期了。Tailscale 默认每 180 天轮换 key,一台设备如果在轮换窗口里一直离线,再上线时管理控制台里就会显示 "offline",直到你重新认证。在受影响的一侧执行 tailscale up --force-reauth,通过浏览器重新登录即可。要在服务端 VPS 上彻底避免轮换,给节点打上 tag(tailscale up --advertise-tags=tag:server),然后在 Tailscale 管理控制台 里为该 tag 关闭 key 过期:被打 tag 的节点默认不再受 180 天检查影响。
  • 省电模式在笔记本上把客户端杀掉了。 macOS 和 Windows 都允许系统在激进省电模式下暂停后台服务,Tailscale 的菜单栏应用可能会自己悄悄退出登录。如果 tailnet 是在你拔掉电源之后不久就黑了,先看看托盘图标再去诊断别的问题。

原因 4:你在远程客户端里指向了 localhost URL

最后一种静默情况是每一层都在正常工作,而客户端问错了问题。如果你把 Hermes Desktop 的 Remote Gateway URL 配成了 http://localhost:8642http://127.0.0.1:8642,应用只会尝试访问自己的 loopback 接口,而不会穿越 tailnet,再多的服务端修复都救不了。

症状:在笔记本上,桌面应用显示 "Could not connect"。从同一台笔记本执行 curl http://<hermes-vps>:8642/api/health 却能得到健康响应。

修复方式只有一处设置。 在 Hermes Desktop 里打开 Settings 然后 Connection,把 Remote Gateway URL 设置成下列之一:

  • http://<magic-dns-name>:8642 - 推荐,Tailscale IP 变化时依然可用。
  • http://<tailscale-ip>:8642 - 原始的 100.x.x.x 地址。对于静态配置足够稳定。

MagicDNS 名称就是 tailscale status 在 VPS 那一行第一列展示的名字。如果你从没启用过 MagicDNS,在管理控制台的 DNS 里打开它:一个开关而已,却能帮你省下这个 tailnet 未来所有 IP 变更的调试时间。

顺便看一下凭据字段。如果 gateway 后面有一个 token(HERMES_AUTH_TOKEN),客户端得用同一个 token,过期的 token 会在 WebSocket 上返回一个 4403,看起来非常像连接失败。WebSocket 4403 issue 有关于该特定失败模式的更多细节。

一份省时间的诊断顺序

当 tailnet 上线而 Hermes 不回应时,按这个顺序逐条排查,而不是把 Tailscale 从头再搭一遍:

  1. hermes serve 是不是绑在 loopback 上? 从客户端执行 curl http://<tailscale-ip>:8642/api/health,一秒之内就有答案。这是新搭远程访问时命中率最高的一条。
  2. Dashboard 的 CORS 正则是不是拒绝了你的来源? 在 dashboard 页面打开浏览器 devtools,查看 Network 面板里有没有红色的 CORS 条目。有的话就设置 HERMES_DASHBOARD_CORS_ORIGINS 并重启。
  3. 隧道走的是直连还是中转? tailscale status 会按对端显示 directrelayOffline 意味着节点 key 过期,需要 --force-reauth
  4. 客户端指向的是 localhost 吗? 打开桌面应用的连接设置,确认 Remote Gateway URL 指向的是 tailnet 主机名,而不是 localhost

想看 VPS 底层的 Docker 部署配方,请阅读 Hermes Agent Docker 指南。如果你更希望彻底跳过网状 VPN,自托管 vs 托管 Hermes Agent 会讨论其中的取舍。

当你实在不想再运维一张网状网

如果你想把 Hermes 留在自己的 VPS 上并从任何地方访问,Tailscale 是正确的形态。但它也是又一个要维护的系统:一个 key 轮换窗口、一个 CORS 环境变量、一条 UDP 41641 防火墙规则,以及一个必须和当天 tailnet 名称对上的客户端设置。如果你的想法是"个人 AI 助手不应该需要一张网状 VPN 加一次浏览器控制台调试才肯回你一声你好",那就 从 Hermify 开始吧。Hermify 在 Telegram 上运行托管的 Hermes Agent,同样的记忆与技能,约一分钟即可上线,无需开放端口,也无需维护任何 tailnet。

参考资料

运行你自己的 Hermes Agent

自带 API 密钥,连接 Telegram,60 秒内即可上线一个自我改进的 AI 智能体。

立即开始