Guide
Hermes Agent Gateway Token Setup: Securing Dashboard Access
The Hermes gateway does not use one long-lived API token the way some agent runtimes do. It mints a fresh session token per process, binds to loopback by default, and demands a real auth provider the moment you expose it. Here is the whole model, and the one environment variable people copy that does nothing.
Start Here: What the Gateway Is
hermes serve runs the Hermes backend — the JSON-RPC and WebSocket gateway that the desktop app and remote clients connect to. It is headless and never opens a browser UI itself. By default it listens on 127.0.0.1, port 9119.
hermes serve # backend only — 127.0.0.1:9119
hermes serve --status # list running server processes
hermes serve --stop # stop them
# The browser dashboard is a SEPARATE subcommand on the same port
hermes dashboard # launches the web UI and opens a browser
hermes dashboard --no-open # serve the UI without opening a browserKeep the two straight or the rest of this will not work as expected: hermes serve runs the backend and never presents a browser UI, while hermes dashboard is what actually serves the web dashboard for managing config, API keys and sessions. Both default to 127.0.0.1:9119 and share the same auth flags.
That loopback default is the most important security property in this guide. A gateway bound to 127.0.0.1 is not reachable from the network at all, which is why the default setup needs no password from you.
The Session Token
Sensitive dashboard endpoints — the ones that reveal stored secrets, for example — are protected by a session token passed in the X-Hermes-Session-Token header.
You normally never see it. On every server start Hermes generates one with secrets.token_urlsafe(32) and injects it into the single-page-app HTML, so the legitimate web UI has it and nothing else does. It lives in memory and dies when the process exits — there is no token file to leak and no rotation to schedule.
You only set it yourself when another process must make authenticated API calls on your behalf:
# Pin the session token for this server process
export HERMES_DASHBOARD_SESSION_TOKEN="$(openssl rand -base64 32)"
hermes serveThis is exactly how the desktop shell works: it mints a token, injects it through this variable, and uses it for the API calls it makes for you. For Desktop-over-SSH there are two dedicated flags instead, --ssh-session-token-file and --ssh-owner-nonce.
The Variable That Does Nothing
If you are migrating from OpenClaw you will meet HERMES_GATEWAY_TOKEN, mapped from an OpenClaw gateway.auth.token. Setting it does not secure anything: the official OpenClaw-to-Hermes migration script is the only thing in the Hermes tree that writes it, and no part of the running gateway reads it back.
It is a translation artifact, not a Hermes feature. The variable that actually gates requests is HERMES_DASHBOARD_SESSION_TOKEN. If you came here from an OpenClaw setup expecting a single shared gateway token, that concept does not carry over — use the model described below instead.
Exposing the Gateway Safely
The June 2026 hardening changed the rules here, and old blog posts have not caught up. --insecure is now a deprecated no-op: it formerly bypassed auth on a non-loopback bind, and it no longer does. A public bind always requires an auth provider.
There are two, configured in ~/.hermes/config.yaml — and they are not interchangeable. Hermes positions basic_auth as protection for a trusted network or VPN, and recommends OAuth/OIDC for a genuinely public deployment. A password on the open internet is the weakest of the options here, so prefer OAuth if the dashboard is reachable from anywhere.
dashboard:
# Preferred for a public deployment: OAuth / OIDC
oauth:
...
# Password protection — intended for a trusted network or VPN,
# not as the sole gate on a public origin
basic_auth:
username: you
password: "a-long-random-string"
# Tells Hermes it is serving a public origin
public_url: https://hermes.example.com
# Required if you terminate TLS at a reverse proxy
trusted_proxies:
- 10.0.0.0/8Setting a non-loopback dashboard.public_url engages a ticket-only auth gate broadly on that machine rather than only for the public process, so treat it as closer to a machine-wide switch than a per-process one. Desktop-owned loopback backends are exempt, but only when all of three conditions hold — a loopback bind, HERMES_DESKTOP=1, and an operator-minted credential.
The Option Most People Should Pick
Keep the bind on loopback and tunnel to it. The gateway's own help text recommends this, and it sidesteps auth configuration entirely because nothing is exposed in the first place.
# On the server — serve the UI without trying to open a browser there
hermes dashboard --no-open
# From your laptop — forward the remote port to a local one
ssh -N -L 9119:127.0.0.1:9119 you@your-server
# Now open http://127.0.0.1:9119 locallyA Tailscale or WireGuard network achieves the same thing with less friction if you connect often.
Troubleshooting
- 401 after restarting the server. Expected. A new process mints a new session token; reload the dashboard page so the UI picks it up.
- Public bind refuses to start unauthenticated. Also expected since the hardening. Configure
basic_authoroauth, or go back to loopback and tunnel. --insecurechanged nothing. It is a no-op that is kept only so old scripts do not crash.- Behind nginx or Caddy and every request looks external. Set
dashboard.trusted_proxies, or the gateway cannot see the real client address. - Port already in use. Run
hermes serve --status— a previous instance is probably still alive.--stopclears them.
On OpenClaw
OpenClaw genuinely does use a single gateway auth token, which is why its setup reads so differently — see OpenClaw Gateway Token Setup. The two models do not translate field-for-field, and that mismatch is the whole reason the migration script invents a variable Hermes then ignores.
What's Next?
- Hermes Agent Profiles — isolated configs on one machine
- Hermes Agent + Tailscale — private remote access
- Hermes Desktop App — the client that mints its own token
- Deploy Hermes Agent — production deployment