Files
hermes-skills/skills/devops/infrastructure-automation/references/cloudflare-access-setup.md
T

3.1 KiB

Cloudflare Access (Zero Trust) Setup

Overview

Cloudflare Access sits in front of subdomains and checks user identity before traffic reaches the origin server. Provides SSO and 2FA for any self-hosted service without server-side auth code.

When to Use

  • Replace Caddy basic_auth for internal pages
  • Add 2FA layer over existing login systems (TwentyCRM magic link, DocuSeal)
  • Unify auth across multiple services (CRM, internal dashboard, DocuSeal)
  • Protect any self-hosted web app without modifying the app itself

Prerequisites

  • Cloudflare API token with Access: Apps and Policies: Edit permission
  • Existing DNS-only token can be updated (Edit → Add permission, then regenerate)
  • Subdomains must be proxied through Cloudflare (orange cloud) for Access to work

Setup via API

Account

  • Account ID: from GET /client/v4/accounts
  • Token: with Access: Apps and Policies: Edit permission

Create an Access Application

curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCT/access/apps" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"App Name","domain":"subdomain.yourdomain.com","type":"self_hosted","session_duration":"24h"}'

Add a Policy to an Application

APP_ID=$(curl -s "https://api.cloudflare.com/client/v4/accounts/$ACCT/access/apps" ... | jq '.result[] | select(.name=="App Name").id')

# Policy with email domain include (no 2FA requirement yet)
curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCT/access/apps/$APP_ID/policies" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Team","decision":"allow","include":[{"email_domain":{"domain":"germainebrown.com"}}]}'

Policy Types

  • include — who is ALLOWED (email, email_domain, everyone, etc.)
  • require — what is REQUIRED (totp, mfa, etc.)
  • exclude — who is BLOCKED

Known Issues

  • require: [{totp: {}}] returned invalid 'require' configuration in testing (Jul 7, 2026). May need to configure 2FA at the Zero Trust org level first, or use the dashboard UI.
  • 2FA/one-time passcode is a "require" rule but may need to be set via the Cloudflare dashboard rather than API for initial setup.
  • Session duration of "24h" means users re-authenticate daily. Adjust based on security needs.

Dashboard Setup (Fallback)

If API fails:

  1. Login at https://one.dash.cloudflare.com/
  2. Access → Applications → Add application → Self-hosted
  3. Enter subdomain, session duration
  4. Add policy: name, include (email or email_domain), require (2FA if needed)
  5. Save

DNS Requirement

  • Subdomains covered by Access must be proxied (orange cloud) in Cloudflare DNS
  • This enables Cloudflare to intercept requests before they reach the origin
  • Non-proxied (gray cloud) records bypass Access entirely

Removing Caddy Basic Auth After Access is Working

Once Access is confirmed working, remove the basic_auth block from the Caddyfile or the old auth section:

# In Caddyfile: remove @internal handle block and basic_auth
# Reload: caddy reload --config /etc/caddy/Caddyfile