Cloudflare Access + Antora Sites
Overview
This runbook documents how to protect Antora documentation sites with Cloudflare Access (Zero Trust) while ensuring static assets (CSS, JS, fonts) load correctly for authenticated users.
The Problem
When you protect an Antora site with Cloudflare Access:
-
Users authenticate and can see the HTML content
-
But static assets at
/_/*return 302 redirects to the Access login page -
The page renders with a white background because CSS/JS can’t load
-
Browser console shows failed requests for
//css/site.css,//js/site.js, etc.
Architecture
Two Access Applications:
| Application | Domain | Policy |
|---|---|---|
|
|
Allow: Your email |
|
|
Bypass: Everyone |
Prerequisites
-
Cloudflare API token with
Account:Access: Apps and Policies:Editpermission -
CF_ACCOUNT_ID and CF_API_TOKEN environment variables
dsource d000 dev/app
Procedure
Step 1: List Existing Access Applications
Verify your current Access configuration:
curl -s "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/access/apps" \
-H "Authorization: Bearer ${CF_API_TOKEN}" | jq '.result[] | {id, name, domain}'
{
"id": "328ba6e8-bf0d-4490-8f66-8837acaaaa82",
"name": "domus-docs",
"domain": "docs.domusdigitalis.dev"
}
Step 2: Create Static Assets Access Application
Create a new Access application specifically for the /_/* path:
curl -X POST "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/access/apps" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "domus-docs-static",
"type": "self_hosted",
"domain": "docs.domusdigitalis.dev/_/*",
"session_duration": "24h"
}'
{
"result": {
"id": "ac2893db-f5c2-40dd-b2e1-08d1a1f047c2",
"name": "domus-docs-static",
"domain": "docs.domusdigitalis.dev/_/*",
"self_hosted_domains": ["docs.domusdigitalis.dev/_/*"]
},
"success": true
}
Save the id from the response for the next step.
Step 3: Add Bypass Policy
Add a bypass policy to allow everyone to access static assets:
# Replace with your app ID from Step 2
STATIC_APP_ID="ac2893db-f5c2-40dd-b2e1-08d1a1f047c2"
curl -X POST "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/access/apps/${STATIC_APP_ID}/policies" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Bypass Static Assets",
"decision": "bypass",
"include": [{"everyone": {}}]
}'
{
"result": {
"id": "fae81e60-b364-4aaa-b6bc-197e660e5b6e",
"name": "Bypass Static Assets",
"decision": "bypass",
"include": [{"everyone": {}}]
},
"success": true
}
Step 4: Verify Configuration
Test that static assets now load without redirect:
curl -sI "https://docs.domusdigitalis.dev/_/css/site.css" | head -5
HTTP/2 200
date: Thu, 12 Feb 2026 04:21:06 GMT
content-type: text/css; charset=utf-8
content-length: 82721
access-control-allow-origin: *
If you see HTTP/2 302 with a redirect to cloudflareaccess.com, the bypass policy is not working.
Troubleshooting
CSS Still Returns 302
-
Verify the static assets app exists:
curl -s "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/access/apps" \ -H "Authorization: Bearer ${CF_API_TOKEN}" | jq '.result[] | select(.name | contains("static"))' -
Verify the bypass policy exists:
STATIC_APP_ID="<your-static-app-id>" curl -s "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/access/apps/${STATIC_APP_ID}/policies" \ -H "Authorization: Bearer ${CF_API_TOKEN}" | jq '.result[]' -
Check that the domain pattern is correct:
The domain must be
docs.domusdigitalis.dev/_/(with the trailing/)
Authentication Error (10000)
Your API token doesn’t have Access permissions. Create a new token at dash.cloudflare.com/profile/api-tokens with:
-
Account → Access: Apps and Policies → Edit
Security Considerations
The /_/* path only contains:
-
Minified CSS (
site.css) -
Minified JavaScript (
site.js,vendor/highlight.js) -
Web fonts
-
Images (logos, icons)
These are not sensitive - they’re the same assets used by the public Antora UI bundle. Bypassing authentication for this path does not expose any documentation content.
API Reference
List Access Applications
curl -s "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/access/apps" \
-H "Authorization: Bearer ${CF_API_TOKEN}" | jq '.result[]'
Delete Access Application
APP_ID="<app-id>"
curl -X DELETE "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/access/apps/${APP_ID}" \
-H "Authorization: Bearer ${CF_API_TOKEN}"