Hermes Agent Tailscale Not Connecting: Fixes
Hermes Desktop cannot reach your remote gateway over Tailscale? Four causes cover almost every case, from the localhost bind to the CORS regex.
The Tailnet Is Up and Hermes Still Will Not Answer
You installed Tailscale on the VPS, joined the tailnet from your laptop, and confirmed both sides can ping each other on their 100.x.x.x addresses. hermes serve is running on the host with an open port, and the Hermes Desktop app on the laptop just spins on "Could not connect to Hermes gateway." Nothing in the gateway log looks angry. Nothing in Tailscale looks red.
That silent failure is almost always one of four causes, and three of them are silent by design. This post walks through each, how to confirm it is yours, and the exact fix. Start from the top: the first cause catches the majority of first-time remote setups, and every cause below it assumes the ones above have been ruled out.
Cause 1: hermes serve Is Bound to 127.0.0.1
hermes serve binds to 127.0.0.1 by default. That is the correct default for a laptop-only setup and the wrong default for anything you want to reach across the tailnet. A process bound to loopback answers only requests that originate on the same machine, and a Tailscale peer is not the same machine. The port is open, the firewall is fine, the tunnel is up, and the socket refuses the connection.
Symptom: from the laptop, curl -v http://<hermes-vps>:8642/api/health returns Connection refused or hangs and times out. From an SSH session on the VPS, the same curl http://127.0.0.1:8642/api/health works instantly. If loopback answers and the tailnet does not, this is your cause.
The fix is to bind hermes serve to the host's Tailscale IP explicitly:
TAILSCALE_IP=$(tailscale ip -4)
hermes serve --host "$TAILSCALE_IP" --port 8642
Binding to the tailnet interface rather than 0.0.0.0 is the shape you want. 0.0.0.0 also works and is what a lot of guides reach for, but it exposes the socket on every interface the machine has, including any accidentally-public one, and puts the whole authentication story back on the app layer. Binding to the Tailscale IP is defense in depth: the socket is only reachable from inside the tailnet in the first place.
Make the change permanent by putting the same flag into the systemd unit or the docker-compose.yml command. If you run in Docker, publish the port to the Tailscale IP directly with -p ${TAILSCALE_IP}:8642:8642 rather than the default -p 8642:8642 (which publishes on every host interface).
For the full first-time Tailscale install, the Hermes Agent + Tailscale secure remote access guide walks the recipe end to end.
Cause 2: The Dashboard CORS Regex Rejects Your Tailscale Origin
You bind the gateway to the Tailscale IP, the API answers /api/health, and the web dashboard loads its HTML from http://<hermes-vps>:8642/. Then every API call the dashboard makes fails with a CORS error in the browser console: has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
What is happening: older Hermes builds shipped a hardcoded allow_origin_regex on the dashboard that matched only ^https?://(localhost|127\.0\.0\.1)(:\d+)?$. The regex was safe on a laptop and silently useless anywhere else. A Tailscale hostname like http://hermes-vps:8642 or an IP like http://100.64.1.5:8642 never matches, so the preflight fails and the browser drops the fetch. The feature request tracking the fix has the full history.
The fix is one environment variable:
export HERMES_DASHBOARD_CORS_ORIGINS="http://hermes-vps:8642,http://100.64.1.5:8642"
hermes serve --host "$TAILSCALE_IP" --port 8642
List every origin you actually load the dashboard from: the MagicDNS name, the raw Tailscale IP, and any Funnel or serve alias you might have added. Wildcards are supported (http://*.tail1a2b3.ts.net:8642) if you would rather match the whole tailnet name than list each device.
Two related knobs that trip people up:
HERMES_DASHBOARD_HOSToverrides the address the dashboard advertises to the browser. If you left it atlocalhost, the dashboard renders links back tohttp://localhost:8642/api/...and the browser tries to hit its own loopback instead of the tailnet. Set it to your Tailscale hostname or IP.- The Hermes Desktop app also carries an Origin. If you use the packaged desktop rather than the browser dashboard, its renderer sends
Origin: null(Electron loads overfile://). Older builds accepted that only when the server was bound to loopback, which is the mutual exclusivity described in issue #38412. Recent builds acceptnullwhen it is present inHERMES_DASHBOARD_CORS_ORIGINSalongside your real origins: add the literal stringnullto the list to allow the desktop client.
Restart hermes serve after any change to these env vars. The values are read at startup, not per request.
Cause 3: The Tailscale Tunnel Is Falling Back to DERP or Not Coming Up
If the dashboard eventually loads but every message takes several seconds to send and voice notes stutter, the tunnel is up but slow. Tailscale is relaying every packet through a DERP server on the way to your VPS, and the round-trip is dominated by that extra hop rather than the model itself. If nothing gets through at all, the tunnel probably never came up in the first place.
Confirm which one you have with tailscale status. A healthy peer shows direct <ip>:<port> on its line. A DERP-relayed peer shows relay "<region>". If the peer is missing entirely or marked offline, the tunnel never established.
The fix is different for each case:
- Stuck on DERP. Open UDP
41641outbound on both the VPS host firewall and the client's network firewall. That is the port Tailscale uses for direct WireGuard tunnels; if either side blocks outbound UDP, both peers fall back to DERP even though the pair is authenticated. Confirm withsudo ufw allow 41641/udpon the VPS and by pinging the peer again aftertailscale down && tailscale up. Corporate networks and hotel Wi-Fi are the usual UDP-egress offenders. If a direct connection remains impossible, DERP works fine for text but you will feel it on voice. - Peer marked offline or the tunnel never came up. The node key expired. Tailscale rotates keys every 180 days by default, and a device that has been offline through the rotation window comes back "offline" in the admin console until you re-authenticate. Fix it with
tailscale up --force-reauthon the affected side and re-log in through the browser. To avoid the rotation entirely on server-side VPS installs, tag the node (tailscale up --advertise-tags=tag:server) and disable key expiry on that tag in the Tailscale admin console: tagged nodes skip the 180-day check by default. - Battery-saver killed the client on the laptop. macOS and Windows both let the OS pause background services under aggressive power modes, and the Tailscale menubar app can silently sign itself out. If the tailnet went dark right after you unplugged, check the tray icon before diagnosing anything else.
Cause 4: You Are Reaching for a Localhost URL from a Remote Client
The last silent case is the one where every layer works and the client is asking the wrong question. If you configured Hermes Desktop's Remote Gateway URL as http://localhost:8642 or http://127.0.0.1:8642, the app tries to reach its own loopback interface instead of crossing the tailnet, and no amount of server-side fixing will help.
Symptom: on the laptop, the desktop app shows "Could not connect." From the same laptop, curl http://<hermes-vps>:8642/api/health returns a healthy response.
The fix is one setting. In Hermes Desktop, open Settings then Connection and set the Remote Gateway URL to one of:
http://<magic-dns-name>:8642- preferred, survives Tailscale IP changes.http://<tailscale-ip>:8642- the raw100.x.x.xaddress. Stable enough for a static setup.
The MagicDNS name is what tailscale status shows in the first column for the VPS row. If you have never enabled MagicDNS, do it in the admin console under DNS: it is one toggle and it saves you every IP-change debug session for the life of the tailnet.
While you are in Settings, sanity-check the credentials field. If the gateway is behind a token (HERMES_AUTH_TOKEN), the client needs the same token, and a stale one produces a 4403 on the WebSocket that looks a lot like a connection failure. The WebSocket 4403 issue has more detail on that specific failure mode.
Diagnostic Order That Saves Time
When the tailnet is up and Hermes still will not answer, work through the causes in this order rather than rebuilding your Tailscale setup:
- Is hermes serve bound to loopback?
curl http://<tailscale-ip>:8642/api/healthfrom the client answers this in one second. Highest hit rate on first-time remote setups. - Is the dashboard CORS regex rejecting your origin? Open the browser dev tools on the dashboard and look for a red CORS entry in the network tab. If present, set
HERMES_DASHBOARD_CORS_ORIGINSand restart. - Is the tunnel direct or relayed?
tailscale statusshowsdirectorrelayper peer.Offlinemeans the node key expired and you need--force-reauth. - Is the client asking for localhost? Open the desktop app's connection settings and confirm the Remote Gateway URL points at the tailnet hostname, not
localhost.
For the underlying Docker recipe on the VPS, see the Hermes Agent Docker guide. If you would rather skip the mesh entirely, self-hosting vs managed Hermes Agent covers the tradeoffs.
When You Would Rather Not Run a Mesh
Tailscale is the right shape for a self-hosted Hermes when you want to keep the box on your own VPS and reach it from anywhere. It is also one more system to keep alive: a key rotation window, a CORS env var, a firewall rule for UDP 41641, and a client setting that has to match the tailnet name of the day. If your read is that a personal AI agent should not require a mesh VPN and a browser-console debug session to say hello back, get started with Hermify. Hermify runs a managed Hermes Agent on Telegram with the same memory and skills, live in about a minute, with no ports to open and no tailnet to run.
Sources
- Hermes Agent Issue #38061: Can't connect to Remote Gateway via Tailscale for Hermes Desktop
- Hermes Agent Issue #10567: Add --host and CORS config for hermes dashboard to enable Tailscale/VPN access
- Hermes Agent Issue #34390: dashboard --allowed-hosts flag for reverse-proxy and Tailscale access
- Hermes Agent Issue #38412: Desktop Remote gateway can't connect over WebSocket
- Tailscale: firewall ports and UDP 41641
- Tailscale: key expiry and re-authentication
- Securely Connecting a Remote Hermes Agent to Your Local Desktop App via Tailscale (Medium)
Run Your Own Hermes Agent
Bring your API key, connect Telegram, and get a self-improving AI agent live in 60 seconds.
Get Started