How to deploy Hermes Agent on Ubuntu
Phase 1: Server Initialization & Base Setup
- Purchase a VPS, deploy Ubuntu 24.04 LTS
- Create system user
tianhe, add to sudo group - Go to https://hermes-agent.nousresearch.com/, run the install script to complete setup
Phase 2: Data Migration (from old Railway deployment)
- Download backup from Cloudflare R2 via AWS CLI
- Restore
.hermesdirectory from backup tarball - Rebuild corrupted
state.db— reimport from sessions/*.json files - Honcho was originally on Railway; later migrated to local Docker Compose(PostgreSQL + pgvector + Redis)
Phase 3: R2 Backup Script Adaptation
- Update
backup-to-r2.pypaths from old user home to/home/tianhe - Set up cron: daily at 03:00 UTC, keep 14 most recent backups
- Optimize backup: exclude venv/node_modules (119 MB → 27.5 MB)
Phase 4: Security Hardening
- Bind Dashboard to
0.0.0.0with--insecureflag (due to DNS rebinding protection interfering with reverse proxy) - Harden SSH:
PermitRootLogin prohibit-password, force publickey auth, disable password auth, disable X11 forwarding, limit MaxAuthTries to 3 - Enable UFW firewall: allow only 22/tcp (SSH) and 443/tcp (HTTPS)
- Configure fail2ban: 3 failures → 24-hour ban((sshd jail only))
- Enable unattended-upgrades for automatic security patches
Phase 5: Dashboard + Caddy Reverse Proxy
- Build Web UI: npm install → npm run build
- Create
systemdservice for hermes-dashboard on127.0.0.1:9119 - Configure Caddy reverse proxy:
herma.tianheg.co:443→ localhost:9119 - Host Header issue: Dashboard's built-in DNS rebinding protection rejects the forwarded hostname. Bind
0.0.0.0with--insecureflag - Integrate Cloudflare: SSL mode
Full, Cloudflare Access auth layer
Phase 6: Gateway / Telegram Integration
- Clear stale
gateway_state.json(draining state from old environment) - Install Gateway as user-level systemd service; fix service file path (was written to wrong location by
hermes gateway install) - Configure Telegram Bot Token in
.env - Enable linger (
loginctl enable-linger) to keep user services alive after SSH logout - Verify: send /start to the bot, confirm auto-response and zero errors
Phase 7: Cleanup
- Remove Railway leftovers: RAILWAY_TOKEN env var, railway CLI binary(11 MB)
- Create / update DevOps skills (security audit, Dashboard, Caddy proxy, Gateway service, data maintenance, R2 ops — 6 skills total)
Phase 8: Hermes Desktop Remote Access
Hermes Desktop is an Electron GUI for Hermes Agent. Shares the same =~.hermes= config, sessions, skills, and memory as the CLI. Runs on macOS, Windows, Linux.
Desktop defaults to local mode (starts a local hermes dashboard backend). Remote mode turns Desktop into a thin client — agent runs on VPS, Desktop renders UI via WebSocket.
Server-side Setup
The dashboard must bind to 0.0.0.0 (not 127.0.0.1) so Tailscale IPs can reach the WebSocket endpoint. --insecure disables Host header validation which rejects non-loopback connections.
- Restart dashboard on 0.0.0.0:
hermes dashboard --host 0.0.0.0 --port 9120 --no-open --insecure
- Get session token (used by Desktop for auth):
# from terminal (run on VPS):
curl -s http://127.0.0.1:9120/ | grep -oP '__HERMES_SESSION_TOKEN__="\\K[^"]+'
# or open http://127.0.0.1:9120/ in browser, open console, type:
__HERMES_SESSION_TOKEN__
- Pin the token (optional — avoids token change after restart):
# Add to ~/.hermes/.env:
HERMES_DASHBOARD_SESSION_TOKEN=<token-from-step-2>
Fixed token lets Desktop use a stable HERMES_DESKTOP_REMOTE_TOKEN that survives dashboard restarts.
Network: Tailscale
SSH tunnel (ssh -L 9120:localhost:9120) works but breaks on disconnect. Tailscale is more stable — zero port exposure, auto-reconnection.
Install native Tailscale on VPS:
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
Desktop connects via http://<tailscale-ip>:9120. No basic auth needed — port is only reachable inside tailnet.
tailscale serve trap: If you previously ran tailscale serve on this port, the rule persists in state and intercepts traffic silently, returning 404. Fix:
sudo tailscale serve --http=9120 off
Clash Verge conflict: If you use Clash Verge TUN mode on the same machine, it may hijack Tailscale traffic (100.64.0.0/10) and route it through the proxy, making the Tailscale IP unreachable. Fix: add bypass rules at the top of Clash config:
IP-CIDR,100.64.0.0/10,DIRECT,no-resolve
DOMAIN-SUFFIX,ts.net,DIRECT
If using Fake-IP DNS mode, also add *.ts.net to fake-ip-filter.
Desktop Client Setup
On Arch (CLI already installed):
hermes desktop
On Windows: download .exe from https://hermes-agent.nousresearch.com/desktop.
In Desktop settings → Gateway → Remote Gateway:
- Remote URL:
http://<tailscale-ip>:9120 - No auth (open inside tailnet), or use token from step 2
Experience Notes
- First connection: session list loads slowly (initial sync of all history)
- After warm-up: chat is smooth, no perceptible latency
- Both Arch and Windows work
- Thin client — all tools (terminal, file ops) execute on VPS
Final System State
- 8 services active (Dashboard, Gateway, Caddy, SSH, fail2ban, UFW, Tailscale, Desktop)
- Dashboard:
0.0.0.0:9120with--insecure(Tailscale-only, no public reverse proxy) - Telegram platform: connected and responding
- Hermes Desktop connected via Tailscale: Arch + Windows clients
- Backup: daily at 03:00 UTC via Hermes internal cron (successful)
- Daily automatic backup to Cloudflare R2 at 03:00 UTC
- Memory provider: Honcho (self-hosted on local Docker)
Comments
✍️ Reply by email