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).
cloudflaredinstalled on the host/container that can reach chmonitor (cloudflared tunnelruns as a sidecar or on the same box).- chmonitor running somewhere
cloudflaredcan 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:
- Access → Applications → Add an application → Self-hosted.
- Set the Application domain to the hostname you'll route through the Tunnel, e.g.
chmonitor.example.com. - Under Session duration, pick a reasonable window (e.g. 24 hours) — this controls how often users have to re-authenticate.
- Add an Access policy: e.g. "Allow" → Include → Emails ending in
@yourcompany.com, or a specific Google/GitHub/SAML group. - 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. - 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.comThis 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 ruleRunning 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:
- chmonitorRun it directly instead of via compose:
cloudflared tunnel --config ~/.cloudflared/config.yml run chmonitorConfigure 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_AUDRedeploy 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/meTroubleshooting
Related
Proxy auth overview
Compare all three proxy patterns.
nginx + oauth2-proxy
Self-hosted OIDC login in front of nginx.
Traefik ForwardAuth + OIDC
Traefik middleware forwarding an OIDC session.
Cloudflare Access reference
Full env var reference for the proxy provider.
Authentication overview
Compare all chmonitor auth providers.