The steps
Bind, register, talk — both ways
Eight steps, roughly eighteen minutes if the bot is already on the tailnet and Hermes is already installed on the Mac and the VPS. Do the bind + peer add once per Hermes host.
What you need#
- The Grok Bot computer already on your tailnet. If it isn't, stop and do the exit-node guide first.
- Hermes on the Mac and/or the VPS — binary usually at
~/.local/bin/hermes, which is often missing from a default SSHPATH. - An SSH key the box can use, plus a
ProxyCommandthroughts nc(next step). Regular kernel TCP to100.xwill fail on a userspace Tailscale box. - The Tailscale IP of each Hermes host — write them down as
YOUR_TS_IP. You will bind to those, not to0.0.0.0.
The box does not run HermesI never install Hermes on the Grok Bot computer. The bot is a speaker, not a peer. Installing it there just gives you a third brain that nothing else can SSH into.
Who talks to whom#
Three roles. Only two of them run Hermes. Only two of them accept SSH.
| Role | What it does |
|---|---|
| Grok Bot computer | Userspace Tailscale. No inbound SSH. No Hermes. It SSHes out to the hosts, then runs hermes peer dm there. |
| Mac | Runs Hermes. api_server listens on this machine's Tailscale IP, port 8642. Talks to the VPS with hermes peer dm PEER_NAME. |
| VPS | Runs Hermes, often under a non-root user. Root's home is the wrong tree — a stale ~/.hermes that looks installed and is not the one you think. |
Vice versa is the same command on the other host. The thing that never works: SSH into the Grok Bot computer. Don't try Tailscale SSH at it either.
Userspace Tailscale and SSH via ts nc#
On the Grok Bot computer, Tailscale is userspace. Kernel TCP to a 100.x address fails. ts ping succeeding is not the same as TCP working. I always SSH through ts nc:
Host MAC_HOST
HostName YOUR_TS_IP
User <mac-user>
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
ProxyCommand ts nc %h %p
Host VPS_HOST
HostName YOUR_TS_IP
User root
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
ProxyCommand ts nc %h %p
Then ssh MAC_HOST and ssh VPS_HOST just work. Point ProxyCommand at the full path to ts if it isn't on the login PATH.
Four things I keep hitting
- Mac App Store Tailscale cannot do Tailscale SSH or
tailscale file cp. Don't runtailscale set --sshon that build. Regularsshd+ a key is the path. - After a Grok Bot Update, Tailscale is often down. Bring it back up the same way you did in the prerequisite. Never Reset the computer — that drops Tailscale and the newer chats with it.
- Do not set a global
ALL_PROXYon the box. It breaks Cursor internals. Userspace networking ists nc, not an HTTP proxy. echo >/dev/tcp/YOUR_TS_IP/22failing whilets pingworks is the expected userspace picture, not a broken tailnet.
Bind api_server to that machine's Tailscale IP#
On each Hermes host, api_server listens on that machine's Tailscale IP only, port 8642. Not 0.0.0.0. A public bind plus a local terminal is a host-user RCE surface. Names only — generate the key on the host, chmod 600 the env file, never print the value:
API_SERVER_ENABLED=true
API_SERVER_HOST=YOUR_TS_IP
API_SERVER_PORT=8642
API_SERVER_KEY=<generate-on-this-host-do-not-print>
Restart the gateway from SSH (step 8) so it picks up the bind. Then confirm it is listening on the Tailscale IP, not on every interface:
# Mac
$ lsof -nP -iTCP:8642 -sTCP:LISTEN
… YOUR_TS_IP:8642 …
# VPS
$ ss -lntp | grep 8642
… YOUR_TS_IP:8642 …
One key per listenerEach host generates its own API_SERVER_KEY. The Mac's key is what the VPS will send as a bearer, and the other way around. I never paste a key into chat, a ticket, or a screenshot.
Cross-register peers#
On the Mac, add the VPS. On the VPS, add the Mac. PEER_NAME is the name you will type later. The URL is the other host's Tailscale IP. The key is the other host's listener key — pass it on the command line, don't echo it first:
$ hermes peer add PEER_NAME --url http://YOUR_TS_IP:8642 --key <PEER_API_SERVER_KEY>
$ hermes peer list
PEER_NAME http://YOUR_TS_IP:8642 [key set]
Peer config stores url and note only. The keys stay in each host's Hermes .env. If peer list does not say [key set], the add did not take — do not debug by printing the env file.
Health check (uses the stored peer key, no model, no conversation):
$ curl -sS -H "Authorization: Bearer $HERMES_PEER_PEER_NAME_KEY" \
http://YOUR_TS_IP:8642/v1/models
… 200 … hermes-agent …
Load the env, don't cat itAPI_SERVER_KEY is this host's listener. Peer keys are HERMES_PEER_<NAME>_KEY. Hitting the other host with $API_SERVER_KEY 401s — use $HERMES_PEER_PEER_NAME_KEY, or curl on that host with its own $API_SERVER_KEY. Source the env. I do not cat it, and I do not put the raw key in a log.
Talk from Grok Bot#
From the Grok Bot computer I SSH to the host, then I run hermes peer dm there. SSH PATH almost never includes ~/.local/bin — export it or the command is "not found":
$ ssh MAC_HOST 'export PATH="$HOME/.local/bin:$PATH"; \
hermes peer dm PEER_NAME "Reply with PONG only."'
On the VPS, Hermes is often a different user than the one SSH lands on. Root's ~/.hermes is the stale tree. Run it as VPS_USER:
$ ssh VPS_HOST 'sudo -u VPS_USER -H \
env PATH=/home/VPS_USER/.local/bin:/usr/bin \
hermes peer dm PEER_NAME "Reply with PONG only."'
One-shot CLI — a new session, not the live gateway brain — is hermes -z "…" the same way. It spends model quota. So does peer dm.
hermes send is not a conversationhermes send is an outbound notify. No LLM, no reply, no peer. I use peer dm when I want two agents to talk, and I do not fire -z in a loop — every call spends quota.
The other way: Mac ↔ VPS#
Once both peers are registered, the two Hermes hosts talk without the bot in the middle. Same command, other direction. Still no SSH into the Grok Bot computer — if I need a file on the box, I pull it from the Mac side with whatever connector I already use, not inbound SSH.
$ export PATH="$HOME/.local/bin:$PATH"
$ hermes peer dm PEER_NAME "Reply with PONG only."
On the VPS, prefix with sudo -u VPS_USER -H and the PATH to that user's ~/.local/bin if you are not already that user.
Restarts, pitfalls, cheat sheet#
Restart gateways from SSH. Never from inside a Hermes turn — the gateway would kill itself mid-reply.
# Mac (or as VPS_USER on the VPS)
$ export PATH="$HOME/.local/bin:$PATH"
$ hermes gateway restart
$ hermes gateway status
# VPS as root, targeting the Hermes user
$ sudo -u VPS_USER -H env XDG_RUNTIME_DIR=/run/user/<uid> \
systemctl --user restart hermes-gateway.service
Caveats
| Caveat | What to remember |
|---|---|
| No inbound SSH on the box | Port 22 is closed. Tailscale SSH into the Grok Bot computer is not a workaround. Talk by SSHing out, then peer dm. |
| Userspace ≠ kernel TCP | ts ping can succeed while ssh YOUR_TS_IP hangs. Always ts nc / ProxyCommand. |
| Mac App Store Tailscale | Cannot do Tailscale SSH or file cp. Use regular sshd and a key. |
| After Grok Bot Update | Re-bring Tailscale up. Never Reset the computer. |
| No global ALL_PROXY | It breaks Cursor internals on the box. Userspace is ts nc, not an HTTP proxy. |
| Bind stays on 100.x | API_SERVER_HOST=YOUR_TS_IP, never 0.0.0.0. Confirm with lsof / ss. |
| SSH PATH | ~/.local/bin is missing on a default SSH login. Export it or call the binary by path. |
| VPS user | Hermes is often a non-root user. Root home is the wrong, stale tree. |
| Keys stay in .env | Peer config is url + note. Never print keys, never paste them into a chat. |
| send vs peer dm | hermes send is outbound notify. peer dm and -z spend model quota. |
| Restart from SSH | Never restart the gateway from inside a Hermes turn. |
Cheat sheet
$ ssh MAC_HOST
$ ssh VPS_HOST
# on each Hermes host — bind YOUR_TS_IP:8642, not 0.0.0.0
$ hermes peer add PEER_NAME --url http://YOUR_TS_IP:8642 --key <PEER_API_SERVER_KEY>
$ hermes peer list
$ ssh MAC_HOST 'export PATH="$HOME/.local/bin:$PATH"; \
hermes peer dm PEER_NAME "…"'
$ ssh VPS_HOST 'sudo -u VPS_USER -H \
env PATH=/home/VPS_USER/.local/bin:/usr/bin \
hermes peer dm PEER_NAME "…"'
Who wrote this
I build, grow and automate products.
I'm a one-person product studio: custom apps, growth for apps that already shipped, and business automation with AI. Same person designs it, writes it and ships it. Pick the service that fits — an assistant will walk you through it in a few minutes and I'll read every word.
Pick your service →