Blob: docs/CLOUDFLARE_ACCESS.md
Cloudflare Access Setup
This runbook is for this repo's production deployment on a Cloudflare Worker with a Worker custom domain such as app.example.com.
It assumes "custom hostname" means a Worker custom domain, not Cloudflare for SaaS custom hostnames. If you mean Cloudflare for SaaS, the Access application flow is slightly different and you should use the Access app's Custom hostname input mode instead.
What this repo expects
- Non-local hostnames are rejected unless both
CF_ACCESS_AUDandCF_ACCESS_TEAMare configured in the Worker environment. - The Worker expects Cloudflare Access to inject a
Cf-Access-Jwt-Assertionheader and validates it on every authenticated request. CF_ACCESS_TEAMin this repo is the team name only, for examplemyteam, not the full URL. The code buildshttps://<team>.cloudflareaccess.cominternally.
Relevant code:
Production setup
1. Pick the production hostname
Choose the hostname that will front the Worker, for example:
app.example.com
That hostname must live in a Cloudflare-managed zone.
2. Attach the Worker to the custom domain
Add a custom domain route to wrangler.jsonc.
Example:
{
"routes": [
{
"pattern": "app.example.com",
"custom_domain": true,
},
],
}Cloudflare's current Workers docs recommend custom_domain: true on a routes entry for Worker custom domains.
For this repo's production deployment, you do not need Cloudflare Tunnel. The Worker runs on Cloudflare's edge and the custom domain points directly at the Worker.
3. Create the Access application
In Cloudflare One:
- Go to
Access controls>Applications. - Select
Add an application. - Select
Self-hosted. - Name the app.
- Add a public hostname matching the Worker custom domain, for example
app.example.com. - Add at least one
Allowpolicy for the people who should use the app. - Save the application.
Create the Access application before or at the same time as the public hostname goes live. Otherwise the custom domain may be reachable without Access until the Access app exists.
4. Copy the Access AUD tag
In Cloudflare One:
- Open the Access application.
- Go to
Basic information. - Copy
Application Audience (AUD) Tag.
That value becomes CF_ACCESS_AUD.
5. Find your Zero Trust team name
In Cloudflare One:
- Go to
Settings. - Find your team domain, which looks like
myteam.cloudflareaccess.com. - Use only the
myteamportion for this repo'sCF_ACCESS_TEAMvalue.
Do not store https://myteam.cloudflareaccess.com in CF_ACCESS_TEAM for this project. The Worker code expects only the team slug.
6. Set Worker secrets
Run:
wrangler secret put CF_ACCESS_AUD
wrangler secret put CF_ACCESS_TEAMWhen prompted:
CF_ACCESS_AUD: paste the Access application AUD tagCF_ACCESS_TEAM: enter the team name only, for examplemyteam
7. Deploy
Run:
npm run deployThis repo's deploy command builds the frontend and runs wrangler deploy.
8. Verify
Check the following:
- Visiting
https://app.example.comredirects to Cloudflare Access login. - After login, the SPA loads normally.
- Terminal WebSocket connections under
/ws/terminalsucceed after login. - A request that bypasses Access does not work because the Worker requires a valid
Cf-Access-Jwt-Assertionheader.
Important repo-specific gotchas
Health checks
The Worker itself allows /api/health without app-level auth:
But if Access protects the whole hostname, Access will still block unauthenticated requests before they reach the Worker.
If you need unauthenticated health checks in production, use one of these patterns:
- Preferred: a separate public hostname for health or status traffic
- Alternative: a more specific Access application or policy for
/api/healthwith aBypassrule - Alternative for automation: use an Access service token instead of making the endpoint public
Account-level default deny
After the app is working, consider enabling Cloudflare's Require Cloudflare Access Protection setting at the account level. That prevents future hostnames from being exposed without an Access application.
Be careful: it blocks any hostname in the account that does not already have an Access application or explicit exemption.
Service tokens for CI, probes, or automation
If uptime checks, CI jobs, or other machines need access, create a Cloudflare Access service token and use a Service Auth policy.
Initial request headers look like:
CF-Access-Client-Id: <CLIENT_ID>
CF-Access-Client-Secret: <CLIENT_SECRET>Local dev testing with Access
If you want to test the full Access flow before production:
- Create a second Access application on a dev hostname such as
dev-app.example.com. - Create
.dev.varsfrom .dev.vars.example. - Put the dev app's AUD tag into
CF_ACCESS_AUD. - Put your team name slug into
CF_ACCESS_TEAM. - Start the app with
npm run dev. - Expose the local dev server through
cloudflaredon the same Access-protected dev hostname.
This repo treats localhost as open dev mode, so you only need the tunnel when you want to test the real Access login and JWT flow end to end.
Official docs
- Cloudflare Access self-hosted apps: https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/self-hosted-public-app/
- Validate Access JWTs: https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/authorization-cookie/validating-json/
- Application paths and path precedence: https://developers.cloudflare.com/cloudflare-one/access-controls/policies/app-paths/
- Service tokens: https://developers.cloudflare.com/cloudflare-one/access-controls/service-credentials/service-tokens/
- Require Access protection: https://developers.cloudflare.com/cloudflare-one/access-controls/access-settings/require-access-protection/
- Workers custom domains: https://developers.cloudflare.com/workers/configuration/routing/custom-domains/
- Zero Trust team domain FAQ: https://developers.cloudflare.com/cloudflare-one/faq/getting-started-faq/