# Deploying OpenHermit behind Caddy

Caddy is the simplest way to put OpenHermit on a public domain with HTTPS:
it auto-issues Let's Encrypt certificates, terminates TLS, and reverse-proxies
to the local gateway and web processes. This guide covers two layouts —
single domain (most users) and split subdomains (if you want to expose the
web UI publicly but keep the gateway on a private hostname).

The web UI uses the Web Crypto API for device-key auth, which browsers only
expose on **secure contexts** (HTTPS, `http://localhost`, or `http://127.0.0.1`).
That makes a real HTTPS terminator like Caddy effectively required for any
remote access.

## Prerequisites

- A box with `openhermit` installed (`npm install -g openhermit`) and the
  gateway + web running on their default localhost ports:
  - Gateway: `127.0.0.1:4000` — `hermit gateway start`
  - Web: `127.0.0.1:4310` — `hermit web start`
- A domain pointing at the box (DNS `A` / `AAAA` record).
- Ports **80** and **443** open in your firewall. Caddy needs 80 for the
  ACME HTTP-01 challenge and 443 for HTTPS itself.
- Caddy installed:
  ```bash
  # Debian / Ubuntu
  sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
  curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
    | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
  curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
    | sudo tee /etc/apt/sources.list.d/caddy-stable.list
  sudo apt update && sudo apt install -y caddy
  ```

Make sure the gateway and web are listening on `127.0.0.1` (the defaults), not
`0.0.0.0`. Caddy must be the only public listener:

```bash
hermit gateway status
# Listening on http://127.0.0.1:4000

hermit web status
# Listening on http://127.0.0.1:4310
```

If you previously used `--host 0.0.0.0`, restart with the defaults so the
processes are no longer reachable bypassing Caddy.

## Pattern A — single domain (recommended)

One hostname, one TLS cert, one URL for users to remember. Caddy routes by
path: `/api/*` and `/admin/*` go to the gateway, everything else goes to the
web UI.

```caddy
# /etc/caddy/Caddyfile

hermit.example.com {
    # Gateway: API + admin UI + WebSocket
    handle /api/* {
        reverse_proxy 127.0.0.1:4000
    }
    handle /admin/* {
        reverse_proxy 127.0.0.1:4000
    }

    # Web UI gets everything else
    handle {
        reverse_proxy 127.0.0.1:4310
    }
}
```

Reload:

```bash
sudo systemctl reload caddy
```

Visit `https://hermit.example.com`. The web UI's `gatewayUrl` defaults to
`window.location.origin`, so it talks to the gateway on the same hostname —
the path-prefix split lets Caddy route those calls to the right backend.

WebSocket connections (`/api/agents/<id>/ws`) and Server-Sent Events
(`/api/agents/<id>/sessions/<id>/events`) ride the same `reverse_proxy`
directive — Caddy handles `Upgrade`/`Connection` headers and streaming bodies
natively, no extra config needed.

## Pattern B — split subdomains

Use this if you want the gateway on a different hostname (private DNS,
internal-only, different rate-limiting policy, etc.).

```caddy
# /etc/caddy/Caddyfile

hermit.example.com {
    reverse_proxy 127.0.0.1:4310
}

api.hermit.example.com {
    reverse_proxy 127.0.0.1:4000
}
```

Both hostnames need DNS records pointing at the box. Caddy auto-issues a cert
for each.

After visiting `https://hermit.example.com`, the web UI's **Connect** screen
asks for a gateway URL — enter `https://api.hermit.example.com`. The browser
stores it locally and uses it for HTTP + WebSocket from then on.

## Verifying the deployment

```bash
# Gateway through Caddy (no token → 401, but TLS is up)
curl -i https://hermit.example.com/api/agents
#  HTTP/2 401

# Web UI
curl -I https://hermit.example.com
#  HTTP/2 200
```

Open the browser to `https://hermit.example.com`, you should reach the
Connect/Setup flow. If you see the **"HTTPS required"** banner instead of the
app, the page isn't actually loading over a secure context — double-check
your DNS, firewall, and that you're using the `https://` scheme.

## Behind Cloudflare or another CDN

If you proxy through Cloudflare:

- SSL/TLS mode: **Full (strict)** so Cloudflare verifies Caddy's cert.
- WebSockets are on by default in Cloudflare; no toggle needed for free plans.
- Don't enable aggressive caching for `/api/*` — agents and SSE responses
  must hit Caddy every request.

## Operational notes

- **Restarts**: Caddy reloads in-place (`systemctl reload caddy`); the gateway
  and web reload independently via `hermit gateway start` / `stop` and
  `hermit web start` / `stop`.
- **Logs**:
  - Caddy: `journalctl -u caddy -f`
  - Gateway: `~/.openhermit/gateway/gateway.log`
  - Web: `~/.openhermit/gateway/web.log`
- **Cert renewal**: Caddy renews automatically; nothing to do.
- **Internal channel callbacks**: Telegram / Discord / Slack adapters call
  back into the gateway via `127.0.0.1:4000`, never through the public
  domain — that means a misconfigured public route can't break inbound
  channel traffic.
