Not every website deployed on Cloudflare Workers needs to be public. I wanted my personal investment dashboard accessible only to me, while keeping the in-progress previews for my blog private until publication.
It would be possible to build a login system from scratch. Instead, I used Cloudflare Zero Trust Access to require authentication in front of an existing Worker without substantially changing the application.
I chose different protection scopes for two services.
| Service | Access scope | Desired behavior |
|---|---|---|
| Personal INVEST | All traffic | Require sign-in for production, APIs, workers.dev, and previews |
| Public BLOG | Previews only | Keep the public blog open but require sign-in for previews |
This guide records the dashboard workflow as I used it on October 24, 2026. Cloudflare may change menu names and plan details; consult the official documentation linked at the end if a screen looks different.
1. What does Zero Trust Free include?
At the time of writing, Cloudflare Zero Trust Free costs $0 and supports up to 50 users.
| Item | Zero Trust Free |
|---|---|
| Price | $0 |
| User limit | Up to 50 |
| Trial expiration | No fixed expiration for the Free plan itself |
| Access policies and authentication | Available |
| First-time organization setup | Payment information required, even on Free |
The 50-user limit is not a limit on Cloudflare accounts, domains, Workers, or monthly site visitors. It refers to user seats consumed by identities that authenticate through Access or enroll through the Cloudflare One Client. One identity accessing multiple applications normally consumes a single seat, regardless of how many times that user signs in. Seats may remain occupied until explicitly freed.
Also, Zero Trust Free is separate from Workers, D1, and other Cloudflare product pricing. Providing billing information does not itself create a charge for subscribing to Zero Trust Free, but separate paid products and features should be checked independently.
Cloudflare's official organization setup instructions require payment details even when Free is selected, and explicitly say that the Free-plan setup step does not charge you.
2. Create a Zero Trust organization
Assume that you already have a Cloudflare account and have deployed a Worker.
- Log in to the Cloudflare Dashboard.
- Select Zero Trust from the navigation menu.
- On the initial Cloudflare One setup screen, choose a Team name. This is an organizational identifier, not your email address or password.
- Select Zero Trust Free as the plan.
- Enter the requested billing information and finish setting up the organization.
- Return to Zero Trust and confirm that Access controls is available.
During my setup, Workers & Pages → invest → Access kept redirecting me to Cloudflare One onboarding. I could only proceed with Worker protection after finishing the organization's initial setup.
The onboarding item Connect a private web application may lead through a separate application connection flow. If the application is already deployed as a Cloudflare Worker, a new Tunnel is not required merely to protect the entire Worker. After activating Zero Trust, return to the Worker's Access tab.
3. Create an allow policy for your email address
First define who is allowed to sign in.
Dashboard path:
Zero Trust → Access controls → Policies → Add a policy
For my private dashboard, I used these settings:
| Field | Value |
|---|---|
| Policy name | ONSEMO Invest Owner Only |
| Action | Allow |
| Session duration | 24 hours |
| Include → Selector | Emails |
| Include → Value | My one authorized email address |
| Override global MFA | Off |
- Give the policy a recognizable name and choose Allow.
- Under Include, select Emails.
- Enter the exact email address that should have access.
- Review session duration and MFA settings.
- Select Save policy.
Avoid Everyone for a private site. Email domain can authorize multiple people who share the same email domain. Emails with one exact address is the suitable choice for this owner-only case.
Creating the policy does not protect a website on its own. The next step is to apply it to the intended Worker.
Should MFA be enabled?
I left Override global MFA at Off. This does not mean that Cloudflare Access is instructed to disable all MFA: it leaves the higher-level organization or application settings in effect.
Cloudflare distinguishes between MFA performed by an identity provider and Independent MFA enforced by Access itself. A private application containing important personal records should consider adding a stronger MFA requirement.
To review application-level MFA:
Zero Trust → Access controls → Applications → select the application → Configure → Authentication → MFA
Policy-level MFA can also be reviewed under Policies → select the policy → Configure → Multi-factor authentication. A policy's MFA configuration may override application or organization defaults.
4. Protect all traffic to the personal INVEST Worker
A critical concern was avoiding an incomplete setup that protects only the custom domain while leaving an alternative workers.dev entry point public.
Dashboard path:
Cloudflare Dashboard → Workers & Pages → invest → Access
Follow these steps:
- Select the invest Worker.
- Open the Access tab.
- Click Protect this Worker behind Access.
- Choose All traffic.
- Under Authentication policy, select
ONSEMO Invest Owner Only. - Check the selected Worker and scope, then click Apply Access.
The important choice is Protect this Worker, not Protect all Workers. The latter can put other Workers in the same account, including public websites, behind authentication.
Per Cloudflare's documentation, All traffic at the Worker level covers associated:
- Custom Domains such as
invest.onsemo.com - Worker Routes
workers.devhostnames- Worker Preview URLs
- Application files and API paths reached through those routes
This differs from hostname-based Access, which protects only the configured hostname or path.
One technical limitation: Worker-level Access does not currently support WebSocket upgrades. These requests can return 403. Applications that rely on WebSockets may need a hostname-based Access configuration instead.
5. Protect BLOG previews while keeping production public
BLOG should remain readable by everyone. Only unpublished previews should require authentication, so do not choose All traffic for a public blog.
Dashboard path:
Cloudflare Dashboard → Workers & Pages → blog → Access
- Choose the blog Worker.
- Select Access → Protect this Worker behind Access.
- Select Previews only.
- Select the owner-only Authentication policy.
- Select Apply Access.
This keeps https://blog.onsemo.com public while requiring Access authentication for the Worker's preview deployments.
A reusable Access policy can be shared across different applications. I reused the owner-only policy created for INVEST. Be aware that future edits to that policy can affect every application using it. For a shared policy, a generic name such as ONSEMO Owner Only is easier to maintain.
6. Hostname-based Access versus Worker-level Access
Initially, I also looked at the Zero Trust application creation screen:
Zero Trust → Access controls → Applications → Create new application
A Self-hosted application can protect a specific public hostname or path. It is a valid option, but it has a different scope.
| Approach | What it protects | Typical use |
|---|---|---|
| Self-hosted Public hostname | A specified hostname or URL path | Restrict one domain or endpoint |
| Worker All traffic | Production and previews for one Worker | Make an entire personal Worker private |
| Worker Previews only | Previews for one Worker | Keep production public and previews private |
Because both of my sites already ran on Cloudflare Workers, I used the Worker's Access tab rather than registering hostnames one by one.
If several Access rules match one request, hostname/path policies take precedence over Worker-level policies, followed by account-wide Worker policies. Check for existing exceptions when combining different rule types.
7. Verify the result without an existing login session
An Access configuration is not complete just because its settings were saved. A browser that is already signed in may hide problems. Use incognito mode or another signed-out browser.
Verify INVEST
- Open
https://invest.onsemo.comin incognito mode. - Confirm that the Cloudflare Access login page appears.
- Sign in with the authorized account and check that the dashboard works.
- Verify that an unauthorized account cannot access it.
- Test the
workers.devhostname and important API paths without authentication. - If previews exist, test their URLs as well.
Unauthenticated access may be redirected to a login page or return an access-denied status. The important outcome is that application data and API responses are not available without authorization.
After enabling INVEST Access, I also ran GitHub Actions checks from an unauthenticated environment against key app and API routes.
Verify BLOG
- Open
https://blog.onsemo.comin a signed-out browser. The public site should load without a login. - Open an actual Cloudflare Preview URL from a GitHub pull request. An Access login page should appear.
- Sign in and confirm that the preview renders correctly.
A GitHub branch is not itself a preview website. Its deployed Cloudflare Preview URL must be checked separately. A private GitHub repository also does not automatically make its preview deployments private.
8. Useful administration menu paths
| Task | Cloudflare dashboard |
|---|---|
| Change allowed email addresses | Zero Trust → Access controls → Policies |
| Review Access applications | Zero Trust → Access controls → Applications |
| Change Worker protection scope | Workers & Pages → select Worker → Access |
| Adjust application MFA | Access controls → Applications → application → Authentication → MFA |
| Review user seats | Zero Trust → Team & Resources → Users |
| Review the plan | Zero Trust → Settings → Cloudflare One plan |
Revoking an existing session is not the same as removing a user from an Allow policy. Someone who remains allowed by a policy can authenticate again. Adjust the policy when permanently removing access.
9. Drawing the boundary between public and private access
The main challenge was not simply making a login page appear. It was understanding which Worker was protected, which URLs were included, and who could pass authentication.
For my private investment tool I chose All traffic. For a public blog with unpublished previews I chose Previews only. Separating those use cases let me avoid writing a custom login system without accidentally making the public blog private.
I am keeping these steps as a record of the setup, including the onboarding detours and the need to test alternative URLs and APIs.