chmonitor
GuideGuides

Proxy auth — Cloudflare Access + Tunnel

Put chmonitor behind Cloudflare Access with a Cloudflare Tunnel — Zero Trust login, signed JWT verification, no shared secret needed.

Cloudflare Access sits in front of chmonitor and does the real login (SSO, email OTP, GitHub/Google, etc.). It signs every authenticated request with a Cf-Access-Jwt-Assertion JWT. chmonitor's proxy auth provider verifies that JWT cryptographically against your team's JWKS — there is no shared secret to manage, the signature itself is the proof.

When to pick this path

Use this when you already run (or want) Cloudflare Zero Trust, don't want to run your own OIDC/ForwardAuth service, and are fine exposing chmonitor to the internet only through a Cloudflare Tunnel (no inbound ports opened).

Prerequisites

  • A Cloudflare account with Zero Trust enabled (Access is available on the Free plan for small teams).
  • A domain managed on Cloudflare (for the Tunnel's public hostname).
  • cloudflared installed on the host/container that can reach chmonitor (cloudflared tunnel runs as a sidecar or on the same box).
  • chmonitor running somewhere cloudflared can reach over plain HTTP (Docker, Kubernetes Service, or bare metal) — it does not need to be reachable from the public internet directly.

Do not also expose chmonitor's port publicly

Access only protects requests that arrive through the Tunnel. If chmonitor's port is also reachable directly (a public LoadBalancer, a port-forwarded router, a Service of type NodePort), that path bypasses Access entirely. Keep chmonitor bound to localhost/an internal network and let the Tunnel be the only ingress.

Setup

Create an Access application

In the Cloudflare Zero Trust dashboard:

  1. Access → Applications → Add an application → Self-hosted.
  2. Set the Application domain to the hostname you'll route through the Tunnel, e.g. chmonitor.example.com.
  3. Under Session duration, pick a reasonable window (e.g. 24 hours) — this controls how often users have to re-authenticate.
  4. Add an Access policy: e.g. "Allow" → Include → Emails ending in @yourcompany.com, or a specific Google/GitHub/SAML group.
  5. Save, then open the application's overview page and copy the Application Audience (AUD) tag — a 64-character hex string. You'll need it for CHM_CF_ACCESS_AUD.
  6. Note your team domain too (Zero Trust → Settings → Custom Pages, or the URL pattern https://<your-team>.cloudflareaccess.com).

Create a Cloudflare Tunnel

cloudflared tunnel login
cloudflared tunnel create chmonitor
cloudflared tunnel route dns chmonitor chmonitor.example.com

This creates a tunnel named chmonitor, provisions a DNS record for chmonitor.example.com pointing at the tunnel, and writes credentials to ~/.cloudflared/<tunnel-id>.json.

Configure the tunnel ingress

Create ~/.cloudflared/config.yml (or mount it into the cloudflared container):

tunnel: chmonitor
credentials-file: /etc/cloudflared/<tunnel-id>.json

ingress:
  - hostname: chmonitor.example.com
    service: http://chmonitor:8080   # chmonitor's internal address/port
  - service: http_status:404          # catch-all, required as the last rule

Running as a container alongside chmonitor (docker-compose extract):

services:
  chmonitor:
    image: ghcr.io/chmonitor/chmonitor:latest
    # do not publish chmonitor's port on the host — the tunnel is the only ingress
    environment:
      CHM_AUTH_PROVIDER: proxy
      CHM_CF_ACCESS_TEAM_DOMAIN: https://your-team.cloudflareaccess.com
      CHM_CF_ACCESS_AUD: <64-char-aud-tag>

  cloudflared:
    image: cloudflare/cloudflared:latest
    command: tunnel --config /etc/cloudflared/config.yml run
    volumes:
      - ./cloudflared:/etc/cloudflared
    depends_on:
      - chmonitor

Run it directly instead of via compose:

cloudflared tunnel --config ~/.cloudflared/config.yml run chmonitor

Configure chmonitor

Prop

Type

CHM_AUTH_PROVIDER=proxy
CHM_CF_ACCESS_TEAM_DOMAIN=https://your-team.cloudflareaccess.com
CHM_CF_ACCESS_AUD=<64-char-aud-tag>

On every request chmonitor verifies the Cf-Access-Jwt-Assertion header: checks the RS256 signature against https://your-team.cloudflareaccess.com/cdn-cgi/access/certs (the JWKS endpoint, cached ~1 hour), then checks aud matches CHM_CF_ACCESS_AUD, iss matches the team domain, and exp hasn't passed. The authenticated subject is the JWT's email claim (falls back to sub). No shared secret is involved — an unset or wrong CHM_CF_ACCESS_AUD/CHM_CF_ACCESS_TEAM_DOMAIN simply disables the mechanism (fails closed).

Set these as Worker secrets in production rather than plaintext config:

wrangler secret put CHM_CF_ACCESS_TEAM_DOMAIN
wrangler secret put CHM_CF_ACCESS_AUD

Redeploy and verify

Open https://chmonitor.example.com in a browser — Cloudflare Access should show its login page before you ever reach chmonitor. After authenticating, you land on the dashboard with data loading normally.

curl -i https://chmonitor.example.com/api/v1/auth/me
# → 401

# A request carrying a valid Access JWT (e.g. copied from browser dev tools,
# or via `cloudflared access curl`) returns the authenticated identity
cloudflared access curl https://chmonitor.example.com/api/v1/auth/me

Troubleshooting

On this page