close

DEV Community

Moses Man
Moses Man

Posted on

Self-Hosted Hermes Agent on iOS: Cloudflare Tunnel + Access Service Tokens + Hermex

Self-Hosted Hermes Agent on iOS: Cloudflare Tunnel + Access Service Tokens + Hermex

Get your Hermes Agent on your iPhone - without paying for a relay, without a VPN, with full Cloudflare edge protection.

The Problem

You're running Hermes Agent self-hosted on a VPS. You want to chat with it from your iPhone. There are a few paths:

  • HermesPilot P2P relay - works great until it goes paid
  • Tailscale VPN - works but you need the VPN connected every time
  • Cloudflare Tunnel + CF Access - great for the browser, but iOS apps can't do OAuth redirects

The 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.

The fix: Cloudflare Access Service Tokens + custom headers.

The Architecture

There are two common paths - both work with Service Tokens:

Path A: Cloudflare Tunnel → Nginx Proxy Manager (NPM)

Hermex iOS App
│ Custom headers: CF-Access-Client-Id, CF-Access-Client-Secret
▼ HTTPS (orange cloud)
Cloudflare Edge - validates Service Token
▼
Cloudflare Tunnel (cloudflared) - connects to NPM
▼
Nginx Proxy Manager (origin certificate) - routes to backend
▼
Hermes API Server (:8642) or Hermes WebUI (:8787)
Enter fullscreen mode Exit fullscreen mode

NPM handles SSL termination with origin certs and gives you a nice UI for managing proxy hosts.

Path B: Cloudflare Tunnel → Direct to Hermes

Hermex iOS App
│ Custom headers
▼ HTTPS
Cloudflare Edge - validates Service Token
▼
Cloudflare Tunnel (cloudflared) - pointed at localhost:8642
▼
Hermes API Server
Enter fullscreen mode Exit fullscreen mode

Simpler, no reverse proxy. Cloudflare handles SSL at the edge.

Prerequisites

  • A domain on Cloudflare (orange-cloud proxied)
  • Hermes Agent with the API Server enabled
  • An iOS device and the Hermex app from the App Store

Step 1: Enable the Hermes API Server

In your ~/.hermes/.env:

API_SERVER_ENABLED=true
API_SERVER_KEY=generate-a-strong-random-key
API_SERVER_HOST=127.0.0.1
API_SERVER_PORT=8642

Restart and verify:

hermes gateway restart
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8642/health

→ 200

Step 2: Install and Configure Cloudflare Tunnel

2a. Install cloudflared

curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared
chmod +x /usr/local/bin/cloudflared

Or on Debian/Ubuntu:

sudo apt install cloudflared

2b. Authenticate and Create a Tunnel

cloudflared tunnel login
cloudflared tunnel create hermes-tunnel

2c. Route DNS

cloudflared tunnel route dns hermes-tunnel hermes-api.yourdomain.com

2d. Configure the Tunnel

Create ~/.cloudflared/config.yml:

tunnel:
credentials-file: /home/ubuntu/.cloudflared/.json

ingress:

Run it:

cloudflared tunnel run hermes-tunnel

Or install as a systemd service:

sudo cloudflared service install

2e. Tunnel → Nginx Proxy Manager

If using NPM, point the tunnel at localhost:80 instead:

ingress:

Then in NPM, add a proxy host:

  • Domain: hermes-api.yourdomain.com
  • Forward to: http://127.0.0.1:8642
  • Enable Websockets
  • SSL tab → Cloudflare Origin Certificate
  • Cloudflare SSL/TLS → Full (strict)

Step 3: Cloudflare Access - Service Token

Mode How it works Use for
Allow Redirects to OAuth (Google, GitHub, etc.) Browser users
Service Auth Validates static headers Apps, APIs, scripts

3a. Create the Service Token

Cloudflare Zero Trust Dashboard → Access → Service Auth → Create Service Token
Name it hermex-ios.
Copy the Client ID and Client Secret immediately.

3b. Create the Access Application

Access → Applications → Add an application → Self-hosted → set domain
Add a policy with:

  • Action: Service Auth ← NOT "Allow"
  • Select the hermex-ios service token

Save. Cloudflare now accepts requests with the correct headers.

Step 4: Configure Hermex

On your iPhone:

Press connect.

Why This Works

Service 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.

What About mTLS?

mTLS 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.

Service Tokens give the same "pre-shared credential at the edge" model over HTTP headers instead of TLS handshakes.

Troubleshooting

  • Cloudflare login page → Policy set to Allow instead of Service Auth
  • 401 Unauthorized → Header spelling wrong (case-sensitive)
  • 502 Bad Gateway → tunnel/NPM can't reach the backend
  • Connection timeout → Tunnel not running or DNS not proxied

Quick health check:
systemctl status cloudflared
curl localhost:8642/health
dig hermes-api.yourdomain.com +short

Alternative: Tailscale

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
hermeslink config set lanHost "100.x.x.x"

Works over WireGuard. Downside: VPN needed every time.

Conclusion

Cloudflare 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.

Top comments (0)