Remote access

Put Hermes Agent behind a reverse proxy

Herald works with Hermes Agent behind a TLS reverse proxy such as Caddy or nginx. Serve Hermes at the root of an HTTPS hostname, pass WebSockets and the Host header through to http://127.0.0.1:9119, and run the proxy on the same computer. Then run npx herald-setup --url https://your-hostname to set the password and dashboard.public_url. Keep the proxy off the open internet if you can.

Requirements at a glance

Proxy location
The same computer as Hermes
Address
HTTPS at the root of a hostname, with no path
Certificate
From a public certificate authority, such as Let’s Encrypt
Traffic
HTTP and WebSockets (/api/ws) to 127.0.0.1:9119
Hermes settings
dashboard.public_url and a dashboard password, written by herald-setup

Caddy

hermes.example.com {
	reverse_proxy 127.0.0.1:9119
}
Caddy serves HTTPS and redirects HTTP on its own. Its reverse proxy passes the Host header through unchanged, sets X-Forwarded-For and X-Forwarded-Proto, and proxies WebSockets without extra settings.

nginx

server {
  listen 443 ssl;
  server_name hermes.example.com;
  # Add your ssl_certificate and ssl_certificate_key lines here.

  location / {
    proxy_pass http://127.0.0.1:9119;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}
The Upgrade and Connection headers let WebSockets through, and Host $host keeps your hostname. Herald pings its connection every 15 seconds, which keeps it inside nginx’s default 60-second read timeout.

Connect Herald

  1. Point a hostname at the proxy

    Create a DNS record for a name such as hermes.example.com, and set up one of the configs above on the computer that runs Hermes.

  2. Run herald-setup with that address

    It writes the dashboard password and dashboard.public_url, keeps Hermes on 127.0.0.1 and starts it if nothing is answering.

    npx herald-setup --url https://hermes.example.com
  3. Scan the QR code in Herald

    Tap Scan the QR code. Herald signs in with the username and password from the code, then opens its WebSocket with a single-use ticket.

Rules that Herald and Hermes set

  • No path prefix. Hermes supports prefixes such as /hermes for its desktop app, but Herald keeps only the scheme, host and port of an address. Give Hermes its own hostname.
  • Keep the Host header. Hermes rejects requests whose Host isn’t loopback or the exact hostname in public_url, as a guard against DNS rebinding.
  • Pass WebSockets. Herald’s chat is a WebSocket to /api/ws, opened with a single-use ticket after sign-in.
  • No extra login at the proxy. Herald can’t answer HTTP basic auth or a single sign-on page in front of Hermes. It only speaks Hermes’ own sign-in.
  • Proxy on another machine? herald-setup keeps Hermes on 127.0.0.1, so a proxy elsewhere can’t reach it. That setup needs Hermes listening on another address and the proxy’s IP in dashboard.trusted_proxies, which herald-setup doesn’t configure.
  • Public certificates only. Herald’s Android app trusts only system certificate authorities, so a self-signed certificate won’t work there.

A private proxy is the safer shape

A proxy that only answers on your home network or VPN gives you HTTPS without putting the sign-in page on the internet. Hermes’ docs describe exactly this deployment shape: a TLS proxy in front of a dashboard bound to 127.0.0.1, with the proxy’s origin in dashboard.public_url.

Tailscale Serve is the same shape with nothing to maintain. It terminates HTTPS on your machine’s ts.net name, forwards to the loopback Hermes and is reachable only from your tailnet. Unless you need a public hostname, it is the simpler choice.

Troubleshooting

  • 502 Bad Gateway. Hermes isn’t answering on 127.0.0.1:9119. Herald treats 502, 503 and 504 from a proxy as no answer and keeps retrying.
  • Signs in, but chats don’t load. The WebSocket upgrade headers are missing from the proxy config.
  • 400 Invalid Host header. The proxy isn’t passing the Host header, or dashboard.public_url doesn’t match the hostname.
  • 429 Too many attempts. Hermes limits password sign-ins, 10 a minute per client by default. Behind a proxy on another machine without trusted_proxies, every device shares one limit.
  • For anything else, see Herald can’t connect and dashboard sign-in problems.

Questions

Can I serve Hermes under a path such as example.com/hermes?

Not for Herald. Herald keeps only the scheme, host and port of an address, so Hermes needs its own hostname, such as hermes.example.com.

Do I need dashboard.trusted_proxies?

Only when the proxy connects from another container or host. Hermes trusts forwarding headers from loopback automatically, so a proxy on the same computer needs nothing extra.

Can I use a self-signed certificate?

Not with Herald on Android, which trusts only system certificate authorities. Use a certificate from a public authority such as Let’s Encrypt; Caddy gets one for you.

Is a reverse proxy better than Tailscale?

Only if you need a public HTTPS name. Otherwise Tailscale Serve gives you the same TLS-in-front-of-loopback setup, reachable only by your own devices, with herald-setup doing the work.

Sources

Get Herald for Hermes Agent

iPhone, iPad, Apple Watch, CarPlay and Android. $12.99 once, no subscription, no account and no Herald server.

Herald is an independent, unofficial app. It is not made, endorsed or supported by Nous Research.