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) to127.0.0.1:9119 - Hermes settings
dashboard.public_urland a dashboard password, written by herald-setup
Caddy
hermes.example.com {
reverse_proxy 127.0.0.1:9119
}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;
}
}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
- 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. - 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 - 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
/hermesfor 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_urldoesn’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.
