On 2 October Cloudflare published a changelog entry for a new Zero Trust setting, strict service token authentication. If your scripts, cron jobs or CI runners reach an Access-protected app with a service token, this setting changes how failures look and which policies apply. Organizations created on or after 5 October 2026 get it switched on and cannot turn it off. Existing organizations can choose when to switch it on.
This guide shows what changes, how to find the callers that will break, and how to switch it on and check the result. It takes about 15 minutes for a small account. Everything below comes from Cloudflare's changelog and service-token docs. We have not run our own benchmarks here.
What strict mode changes
The docs list three behaviour changes:

- Failures are explicit. If authentication or authorization fails, Access always returns
401or403instead of redirecting the client to the login page with a302. - Only Service Auth policies count. Only policies with the Service Auth action can authorize the request. Access ignores Allow policies and any
CF_Authorizationcookie sent with the request. - No cookie on success. Access does not return a
CF_Authorizationcookie to the client after it authenticates successfully.
Cloudflare also says that failed requests for recognised service tokens show up in the Access authentication logs, including expired tokens, disabled tokens and wrong client secrets.
The first change is the one that helps. Without strict mode, a script with a bad token often gets a 302 to an HTML login page. If the script follows redirects, it then gets a 200 for the login page, and only fails later when it tries to parse that page as JSON. With strict mode it gets a clean 401 or 403 on the first request.
Who breaks
Three patterns stop working when you turn strict mode on:

- Service tokens matched by an Allow policy. If a policy includes a service token rule but its action is Allow, the token no longer authorizes anything. The policy action has to be Service Auth.
- Clients that send the headers once and then rely on the cookie. Some scripts authenticate on the first call, save cookies with a cookie jar, and drop the headers on later calls. With no cookie issued (and any cookie ignored), every request after the first gets a
401or403. - Anything that expects the redirect. Health checks or wrappers that treat
302as "not logged in" and branch on it will see a different status code.
Step 1: find Allow policies that contain service tokens
In the dashboard, open each Access application that machines call and check its policies. Any policy that includes a service token (or "any valid service token") should have the Service Auth action. In the API, the Service Auth action appears as "decision": "non_identity".
To check reusable policies from the command line, use an API token with Access read permissions:
export ACCOUNT_ID="your-account-id"
export CLOUDFLARE_API_TOKEN="your-api-token"
curl -s "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/access/policies" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
| jq '.result[]
| select(.decision == "allow")
| select([.include[]? | has("service_token") or has("any_valid_service_token")] | any)
| {id, name, decision}'Every policy this prints is a policy that strict mode will ignore for your tokens. Change its action to Service Auth in the dashboard, or split it into two policies: one Allow policy for people and one Service Auth policy for tokens. Policies attached directly to a single application are listed under that application in the dashboard. Check those too.
Step 2: make every caller send both headers on every request
A service token is two headers. Per the docs:
CF-Access-Client-Id: <CLIENT_ID>
CF-Access-Client-Secret: <CLIENT_SECRET>The docs also describe a single-header form for clients that can only set Authorization:
Authorization: {"cf-access-client-id": "<CLIENT_ID>", "cf-access-client-secret": "<CLIENT_SECRET>"}Search your repos and CI config for cookie jars and saved sessions next to Access hostnames, for example curl -c/-b, requests.Session() that only sets the headers on login, or a stored CF_Authorization value. Change them so the two headers go on every request.
Before you flip the setting, run this check against each protected endpoint. It does not follow redirects, so a 302 stays visible:
#!/usr/bin/env bash
# check-access.sh: call an Access-protected URL with a service token, no redirects.
set -euo pipefail
URL="${1:?usage: check-access.sh https://app.example.com/api/health}"
curl -s -o /dev/null \
-w "status=%{http_code} redirect=%{redirect_url}\n" \
--header "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
--header "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
"$URL"export CF_ACCESS_CLIENT_ID="xxxx.access"
export CF_ACCESS_CLIENT_SECRET="xxxx"
bash check-access.sh https://app.example.com/api/healthYou want status=200 (or whatever your origin normally returns) with an empty redirect. A 302 to a login page before the switch means the token is not being matched by any policy that admits it.



