{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreigygvbr4mam5ygbm6oems4rvlygtsfpwhdyhnhyzkkfp2tobvdb7y",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mp2oq6hkjfw2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreiclskfnbk7mhdxeem5zesqarlyh5h6c2iuyaejty2ruymvlg3tsvy"
    },
    "mimeType": "image/webp",
    "size": 80528
  },
  "path": "/mosesman831/self-hosted-hermes-agent-on-ios-cloudflare-tunnel-access-service-tokens-hermex-103e",
  "publishedAt": "2026-06-24T19:24:59.000Z",
  "site": "https://dev.to",
  "tags": [
    "hermes",
    "ai",
    "cloudflare",
    "hermesagent",
    "http://127.0.0.1:8642/health",
    "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64",
    "http://localhost:8642",
    "http://localhost:8787",
    "http://localhost:80",
    "http://127.0.0.1:8642",
    "https://hermes-api.yourdomain.com",
    "https://tailscale.com/install.sh"
  ],
  "textContent": "#  Self-Hosted Hermes Agent on iOS: Cloudflare Tunnel + Access Service Tokens + Hermex\n\nGet your Hermes Agent on your iPhone - without paying for a relay, without a VPN, with full Cloudflare edge protection.\n\n##  The Problem\n\nYou're running Hermes Agent self-hosted on a VPS. You want to chat with it from your iPhone. There are a few paths:\n\n  * **HermesPilot P2P relay** - works great until it goes paid\n  * **Tailscale VPN** - works but you need the VPN connected every time\n  * **Cloudflare Tunnel + CF Access** - great for the browser, but iOS apps can't do OAuth redirects\n\n\n\nThe last option is the most interesting because it gives you Cloudflare's edge protection, your own domain, and no per-device VPN. The problem is that Cloudflare Access normally redirects to a browser login (Google, GitHub, etc.) - which doesn't work for a native app.\n\n**The fix: Cloudflare Access Service Tokens + custom headers.**\n\n##  The Architecture\n\nThere are two common paths - both work with Service Tokens:\n\n###  Path A: Cloudflare Tunnel → Nginx Proxy Manager (NPM)\n\n\n    Hermex iOS App\n    │ Custom headers: CF-Access-Client-Id, CF-Access-Client-Secret\n    ▼ HTTPS (orange cloud)\n    Cloudflare Edge - validates Service Token\n    ▼\n    Cloudflare Tunnel (cloudflared) - connects to NPM\n    ▼\n    Nginx Proxy Manager (origin certificate) - routes to backend\n    ▼\n    Hermes API Server (:8642) or Hermes WebUI (:8787)\n\n\nNPM handles SSL termination with origin certs and gives you a nice UI for managing proxy hosts.\n\n###  Path B: Cloudflare Tunnel → Direct to Hermes\n\n\n    Hermex iOS App\n    │ Custom headers\n    ▼ HTTPS\n    Cloudflare Edge - validates Service Token\n    ▼\n    Cloudflare Tunnel (cloudflared) - pointed at localhost:8642\n    ▼\n    Hermes API Server\n\n\nSimpler, no reverse proxy. Cloudflare handles SSL at the edge.\n\n##  Prerequisites\n\n  * A domain on Cloudflare (orange-cloud proxied)\n  * Hermes Agent with the API Server enabled\n  * An iOS device and the **Hermex** app from the App Store\n\n\n\n##  Step 1: Enable the Hermes API Server\n\nIn your ~/.hermes/.env:\n\nAPI_SERVER_ENABLED=true\nAPI_SERVER_KEY=generate-a-strong-random-key\nAPI_SERVER_HOST=127.0.0.1\nAPI_SERVER_PORT=8642\n\nRestart and verify:\n\nhermes gateway restart\ncurl -s -o /dev/null -w \"%{http_code}\" http://127.0.0.1:8642/health\n\n#  → 200\n\n##  Step 2: Install and Configure Cloudflare Tunnel\n\n###  2a. Install cloudflared\n\ncurl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared\nchmod +x /usr/local/bin/cloudflared\n\n#  Or on Debian/Ubuntu:\n\nsudo apt install cloudflared\n\n###  2b. Authenticate and Create a Tunnel\n\ncloudflared tunnel login\ncloudflared tunnel create hermes-tunnel\n\n###  2c. Route DNS\n\ncloudflared tunnel route dns hermes-tunnel hermes-api.yourdomain.com\n\n###  2d. Configure the Tunnel\n\nCreate ~/.cloudflared/config.yml:\n\ntunnel:\ncredentials-file: /home/ubuntu/.cloudflared/.json\n\ningress:\n\n  * hostname: hermes-api.yourdomain.com service: http://localhost:8642\n  * hostname: hermes-webui.yourdomain.com service: http://localhost:8787\n  * service: http_status:404\n\n\n\nRun it:\n\ncloudflared tunnel run hermes-tunnel\n\nOr install as a systemd service:\n\nsudo cloudflared service install\n\n###  2e. Tunnel → Nginx Proxy Manager\n\nIf using NPM, point the tunnel at localhost:80 instead:\n\ningress:\n\n  * hostname: hermes-api.yourdomain.com service: http://localhost:80\n  * service: http_status:404\n\n\n\nThen in NPM, add a proxy host:\n\n  * Domain: hermes-api.yourdomain.com\n  * Forward to: http://127.0.0.1:8642\n  * Enable Websockets\n  * SSL tab → Cloudflare Origin Certificate\n  * Cloudflare SSL/TLS → Full (strict)\n\n\n\n##  Step 3: Cloudflare Access - Service Token\n\nMode | How it works | Use for\n---|---|---\nAllow | Redirects to OAuth (Google, GitHub, etc.) | Browser users\nService Auth | Validates static headers | Apps, APIs, scripts\n\n###  3a. Create the Service Token\n\nCloudflare Zero Trust Dashboard → Access → Service Auth → Create Service Token\nName it hermex-ios.\nCopy the Client ID and Client Secret immediately.\n\n###  3b. Create the Access Application\n\nAccess → Applications → Add an application → Self-hosted → set domain\nAdd a policy with:\n\n  * Action: Service Auth ← NOT \"Allow\"\n  * Select the hermex-ios service token\n\n\n\nSave. Cloudflare now accepts requests with the correct headers.\n\n##  Step 4: Configure Hermex\n\nOn your iPhone:\n\n  * Instance URL: https://hermes-api.yourdomain.com\n  * Custom Header 1: CF-Access-Client-Id:\n  * Custom Header 2: CF-Access-Client-Secret:\n\n\n\nPress connect.\n\n##  Why This Works\n\nService Tokens are designed for machine-to-machine auth. Cloudflare's edge reads CF-Access-Client-Id and CF-Access-Client-Secret headers on every request and validates before anything reaches your tunnel. The app never sees a login page. Same pattern as CI/CD pipelines and Terraform - just works for iOS too.\n\n##  What About mTLS?\n\nmTLS would be ideal but iOS support is painful. No native NSURLSession support without painful workarounds, certificate distribution is a UX nightmare, renewal and revocation need custom code.\n\nService Tokens give the same \"pre-shared credential at the edge\" model over HTTP headers instead of TLS handshakes.\n\n##  Troubleshooting\n\n  * **Cloudflare login page** → Policy set to Allow instead of Service Auth\n  * **401 Unauthorized** → Header spelling wrong (case-sensitive)\n  * **502 Bad Gateway** → tunnel/NPM can't reach the backend\n  * **Connection timeout** → Tunnel not running or DNS not proxied\n\n\n\nQuick health check:\nsystemctl status cloudflared\ncurl localhost:8642/health\ndig hermes-api.yourdomain.com +short\n\n##  Alternative: Tailscale\n\ncurl -fsSL https://tailscale.com/install.sh | sh\nsudo tailscale up\nhermeslink config set lanHost \"100.x.x.x\"\n\nWorks over WireGuard. Downside: VPN needed every time.\n\n##  Conclusion\n\nCloudflare Access Service Tokens are the missing piece for authenticating native apps behind Cloudflare. With Hermex's custom header support, this takes about 10 minutes if you already have the tunnel running.",
  "title": "Self-Hosted Hermes Agent on iOS: Cloudflare Tunnel + Access Service Tokens + Hermex"
}