Cloudflare shipped Protect all Workers on 14 August 2026, and WebSocket Workers return 403 behind it

Worker-level Access policies 403 WebSocket upgrades, and ctx.access does not survive Service Bindings or RPC.

Read time
11 min
Word count
1.7K
Sections
11
FAQs
8
Share
Cloudflare Protect all Workers Access limitations, WebSocket 403 and ctx.access gaps, August 2026
Cloudflare added account-wide Access for Workers on 14 August 2026.
On this page · 11 sections
  1. What shipped
  2. The limitation that breaks production
  3. ctx.access stops at the first Worker
  4. The Static Assets trap you did not opt into
  5. The two samples disagree on the status code
  6. Local testing needs an aud, or Wrangler will not start
  7. What to do before you flip the account-wide switch
  8. India-specific considerations
  9. FAQ
  10. How eCorpIT can help
  11. References

Summary. On 14 August 2026 Cloudflare added two ways to put Cloudflare Access in front of Workers: attach a policy to a single Worker so it covers every route, Custom Domain and workers.dev URL at once, or set an account-wide default that protects every existing and future Worker. The changelog runs to roughly 500 words and lists no limitations. The Workers Cloudflare Access documentation page, last updated 18 August 2026, four days later, carries four. The first is the one that will page you: "Worker-level Access policies do not currently support WebSocket connections. WebSocket upgrade requests to a Worker protected by a worker-level Access policy will fail with a 403 error." Durable Objects, real-time applications and RDP-over-WebSocket are named explicitly. The second is that ctx.access, the new identity object, does not cross a Service Binding or an RPC call. The third is that Workers with Static Assets run behind an internal router Worker that does not pass ctx.access through. The fourth is that the Cloudflare Vite plugin can add assets to a deployment configuration on its own, so the third limitation can hit a project whose own Wrangler file never asked for it. TanStack Start is named as an affected framework.

What shipped

The changelog entry, You can now enable Access on a Worker or all Workers at once, carries a dateModified of 2026-08-14. Its pitch is precise about the problem it solves:

"Until now, if a Worker was reachable on a route, a Custom Domain, and a workers.dev URL, you had to manually add each one to an Access application and keep the list in sync whenever routes or domains changed. Now, Access attaches the policy to the Worker itself, so every associated domain and preview URL stays protected even when its routes or domains change."

The second half is the account-wide switch: "Make all Workers private by default, so every existing and newly created Worker requires sign-in before anyone can reach it." A Worker that should stay public gets a Worker-level bypass.

The API surface behind the dashboard is a self-hosted Access application with a new destinations array. The documentation page gives four destination types: all_preview_workers, all_workers, preview_worker and worker, the last two taking a worker_id. All four are created with POST /accounts/{account_id}/access/apps.

I want to protect Destination type Scope
Preview deployments for all Workers all_preview_workers Account-wide, previews only
Production and previews for all Workers all_workers Account-wide, all traffic
Preview deployments for one Worker preview_worker One worker_id, previews only
Production and previews for one Worker worker One worker_id, all traffic
One hostname, Custom Domain or path Self-hosted application domain That exact URL only

Zero Trust has to be enabled on the account first, and you need permission to manage both Workers and Access applications.

The limitation that breaks production

The documentation page places this in a callout headed "WebSocket limitation", inside the "Protect one Worker" section:

"Worker-level Access policies do not currently support WebSocket connections. WebSocket upgrade requests to a Worker protected by a worker-level Access policy will fail with a 403 error. If your Worker uses WebSockets (including Durable Objects, real-time applications, or RDP-over-WebSocket), protect it with a hostname-based Access application instead."

Read that against the account-wide switch. all_workers applies a Worker-level policy to every Worker in the account, including ones you have not deployed yet. Nothing in the enable flow filters out the Workers that terminate WebSockets. So the failure shape for a team that turns on "Protect all Workers" on a Friday afternoon is: every HTTP Worker starts prompting for sign-in as intended, and every WebSocket application in the account starts returning 403 on upgrade. Collaborative editors, live dashboards, chat backends, anything hibernating a Durable Object on a socket.

A 403 on an upgrade request is also a bad failure to diagnose. Browser clients report it as a generic connection failure, and the WebSocket close code will not tell you Access is involved. If your Durable Objects are behind a Worker and you enabled account-wide Access at some point in the last week, that is the first thing to check. The Cloudflare Agents SDK and Durable Objects production guide covers the socket lifecycle these applications depend on.

The documented workaround is to use a hostname-based Access application instead, which protects one exact URL rather than the Worker. But hostname-based Access is precisely what the 14 August change was meant to replace, because it means going back to enumerating routes and Custom Domains by hand. WebSocket Workers do not get the new ergonomics. They get the old ones plus an account-wide default they must be carved out of.

ctx.access stops at the first Worker

The changelog's identity story reads cleanly: "When Access is enabled on your Worker, every authenticated request includes ctx.access. Call ctx.access.getIdentity() to get the user's email, name, and groups — no manual JWT validation required."

The documentation adds the boundary. Under "ctx.access limitations":

"ctx.access applies only to the Worker invocation authenticated by Access. Cloudflare Access does not propagate ctx.access through Service Binding HTTP requests or remote procedure call (RPC) invocations. The downstream Worker does not receive the caller's Access context."

For anyone running a front Worker that fans out to internal Workers over Service Bindings, which is the standard shape for a Workers-based service mesh, this means identity is available exactly once, at the edge, and every hop after it is anonymous unless you pass the identity yourself. The documented alternative is to send a fetch() subrequest to an Access-protected hostname carrying service token headers or a valid CF_Authorization cookie, at which point "Access evaluates the new request and creates a ctx.access object for the downstream Worker. This is a newly authenticated context, not context propagated from the caller." That is a second full Access evaluation on the internal hop, with the latency and the policy-maintenance cost that implies.

Invocation path Does the downstream Worker get ctx.access? What you must do
Direct HTTP request through Access Yes Nothing
Service Binding HTTP request No Pass identity explicitly, or re-authenticate
RPC invocation No Pass identity explicitly, or re-authenticate
fetch() to an Access hostname with a service token Yes, as a new context Manage service tokens and a second policy
Static Assets router to user Worker No No documented workaround

The Static Assets trap you did not opt into

The last row deserves its own paragraph. The documentation states: "Workers with Static Assets execute behind an internal router Worker. Access still protects the application and its assets. However, the router does not pass ctx.access to the user Worker."

So the protection holds and the identity disappears. A Worker that reads ctx.access to personalise a response will find it undefined, and code written from the changelog's example will treat an authenticated user as unauthenticated.

Then the sentence that turns this from a footnote into a trap: "The Cloudflare Vite plugin can add assets to the generated deployment configuration when the input Wrangler configuration omits it. Frameworks that use the plugin, including TanStack Start, can therefore be affected even when their source configuration does not declare Static Assets."

Grep your own wrangler.jsonc for assets and you may find nothing, and still be behind the router Worker. The generated deployment configuration is what matters, not the input. That is a real gap between what a reviewer reads and what runs.

The two samples disagree on the status code

A smaller inconsistency, worth knowing if you are copying code. The changelog's sample returns 401 when Access did not run:


            if (!ctx.access) {
  return new Response("Access did not run", { status: 401 });
}
          

The documentation page's sample, for the same check, returns 403:


            if (!ctx.access) {
  return new Response("Access required", { status: 403 });
}
          

Neither is wrong on its own terms. But an unauthenticated request that reaches your Worker with ctx.access undefined is the same condition in both files, and two Cloudflare-authored samples answer it differently. Pick one deliberately, because the choice changes how clients and monitoring behave: 401 invites a retry with credentials, 403 says stop.

Local testing needs an aud, or Wrangler will not start

The wrangler dev support is genuinely useful and has one hard requirement. The access.dev block takes aud, described as "your Access application's audience tag, available as ctx.access.aud. Wrangler will not start without it." The identity object is optional; omit it if your Worker only checks whether Access ran.


            {
	"access": {
		"dev": {
			"aud": "my-app",
			"identity": { "email": "admin@example.com" }
		}
	}
}
          

To test the unauthenticated path, remove the dev block entirely and ctx.access becomes undefined. There is no flag for it. That makes "test both paths" a config edit and a restart rather than a switch, so it will not sit naturally in a CI matrix.

What to do before you flip the account-wide switch

Inventory the WebSocket Workers first. Anything using Durable Objects with hibernation, anything doing server-sent real-time work, anything proxying RDP. Those need hostname-based Access applications or a Worker-level bypass, decided before all_workers goes on, not after.

Start with all_preview_workers rather than all_workers. Previews-only carries the same policy machinery with none of the production blast radius, and it is the configuration most teams actually wanted when they asked for this feature.

Audit for the Static Assets path by reading the generated deployment configuration rather than your Wrangler source, especially on any Vite-plugin framework. If assets appears there, treat ctx.access as unavailable in that Worker and read identity another way.

Map your Service Binding graph and decide, once, how identity travels. Passing a validated claim set in ctx.props on the binding is cheaper than a second Access evaluation on every internal hop, and it keeps one policy rather than two. Whichever you pick, write it down, because the failure mode is silent: the downstream Worker does not error, it simply sees nobody.

India-specific considerations

Teams in India running multi-tenant SaaS on Workers should note that the account-wide default applies to every Worker in the account, including any tenant-facing Worker deployed later by a different team. Where tenant data falls under the Digital Personal Data Protection Act 2023, an accidental account-wide sign-in requirement on a customer-facing endpoint is an availability incident, not a privacy improvement. Separate accounts per environment remain the cleanest boundary. Our multi-cloud security posture management practice treats account-wide defaults like this one as configuration drift to be detected, not settings to be trusted.

FAQ

How eCorpIT can help

Account-wide security defaults are the settings most likely to be turned on once and never reviewed, and this one has four documented exceptions living on a different page from the announcement. We audit Cloudflare Workers and Zero Trust configurations for exactly that gap, and build the CI checks that catch a generated assets entry or an unprotected Service Binding hop before it ships. eCorpIT is ISO 27001:2022 certified and CMMI Level 5 appraised. If your Workers estate is about to go private by default, book a Workers and Zero Trust configuration review.

References

  1. You can now enable Access on a Worker or all Workers at once, Cloudflare changelog, 14 August 2026
  1. Cloudflare Access, Cloudflare Workers docs
  1. Context (ctx), Cloudflare Workers runtime APIs
  1. Service bindings, Cloudflare Workers docs
  1. Static Assets, Cloudflare Workers docs
  1. Access policies, Cloudflare Zero Trust docs
  1. Choose an application type, Cloudflare Zero Trust docs
  1. Service tokens, Cloudflare Zero Trust docs
  1. Application token user identity, Cloudflare Zero Trust docs
  1. Zero Trust setup, Cloudflare docs
  1. Self-hosted public applications, Cloudflare Zero Trust docs
  1. Cloudflare Workers documentation

Last updated 23 August 2026.

Frequently asked

Quick answers.

01 What did Cloudflare change for Workers and Access on 14 August 2026?
Two things. A policy can now attach to a Worker itself, covering its routes, Custom Domains, workers.dev hostname and previews together. And an account-wide setting can require sign-in on every existing and future Worker, with a Worker-level bypass available for anything that must stay public.
02 Why do my WebSocket connections return 403 after enabling Access?
Because worker-level Access policies do not support WebSocket connections. The Workers Access documentation states that WebSocket upgrade requests to a Worker protected by a worker-level policy fail with a 403 error, and names Durable Objects, real-time applications and RDP-over-WebSocket as affected. Use a hostname-based Access application for those Workers instead.
03 Does ctx.access work across Service Bindings?
No. Cloudflare Access does not propagate ctx.access through Service Binding HTTP requests or RPC invocations, so the downstream Worker receives no Access context. A fetch() subrequest to an Access-protected hostname with service token headers creates a new context for the downstream Worker, but that is a fresh authentication, not propagation.
04 Why is ctx.access undefined in my Worker with Static Assets?
Workers with Static Assets run behind an internal router Worker. Access still protects the application and its assets, but the router does not pass ctx.access to the user Worker. The Cloudflare Vite plugin can add assets to the generated deployment configuration even when your Wrangler file omits it, so check the generated config.
05 Which destination types does the Access applications API accept for Workers?
Four. all_preview_workers and all_workers for account-wide scope, covering previews only or all traffic. preview_worker and worker for a single Worker, each requiring a worker_id. All are sent in a destinations array to POST /accounts/{account_id}/access/apps as part of a self-hosted application.
06 Should I return 401 or 403 when ctx.access is missing?
Cloudflare's own samples disagree. The 14 August changelog returns 401 with the message "Access did not run", while the documentation page returns 403 with "Access required" for the identical check. Choose deliberately: 401 signals that a retry with credentials may succeed, whereas 403 signals a refusal.
07 How do I test Access locally with wrangler dev?
Add an access.dev block to wrangler.jsonc. The aud field is required and Wrangler will not start without it; it surfaces as ctx.access.aud. The identity object is optional and simulates the claims returned by getIdentity(). Remove the whole dev block to test the unauthenticated path.
08 What is the safest way to adopt the account-wide setting?
Enable all_preview_workers first, so preview deployments are protected without touching production traffic. Inventory WebSocket Workers and give them hostname-based applications or Worker-level bypasses beforehand. Then check any Vite-plugin project for a generated assets entry before relying on ctx.access in code.

About the author

Manu Shukla

Founder & Director

Founder of eCorpIT. Hands-on engineer leading senior-only delivery for AI apps, custom software, and cloud systems for global clients.

Subscribe

One engineering note a week. No fluff, no spam.

Senior-architect playbooks on AI agents, mobile apps, cloud, security, data, and marketing — delivered every Wednesday.

Past the reading

Read enough. Let's build something.

A senior architect responds in 24 working hours with scope, indicative cost, and a timeline. NDA before any technical conversation.