Guides Zero Trust
Put a login in front of a tunnelled app with Cloudflare Access
A published tunnel hostname is public until you say otherwise. Add an Access policy with identity or a one-time email code, and use service tokens for machines.
A Cloudflare Tunnel makes your service reachable. It does not decide who is allowed to reach it. Publish files.example.com and, unless you add something in front, anyone on the internet can load it, probe it and try its login page.
Cloudflare Access is that something. It sits at Cloudflare’s edge, in front of the tunnel, and asks every visitor to prove who they are before a single request is passed down to your server. Visitors who fail never touch your origin, which is a different position from an app that has to defend its own login page.
This guide protects a tunnelled web app with an email-based policy, then covers service tokens for scripts and the traps that catch people out.
Order matters. Create the Access application first, and publish the hostname second. If you do it the other way round, there is a window in which your service is live and unprotected. Public-facing scanners find new hostnames quickly.
What Access adds
Access works on applications and policies:
- An application is a hostname, or part of one, that Access protects. Here that is
files.example.com. - A policy says who gets in. It has an action, such as Allow, and rules, such as “emails ending in a particular domain”.
- An identity provider is where people prove who they are: Google, Microsoft, GitHub, a corporate single sign-on, or Cloudflare’s built-in emailed one-time PIN.
The one-time PIN is the easiest place to start. There is nothing to set up: the visitor types an email address, Cloudflare emails them a code if that address is allowed by a policy, and they type the code in.
Set it up
Cloudflare moves menu items around, so look for the names rather than following a click path. You want the Zero Trust area of the dashboard, then Access, then Applications.
- If you have never used Zero Trust on the account, it asks you to create an organisation and pick a team name. That name becomes the address of the login page,
your-team.cloudflareaccess.com. The free plan has covered up to 50 users, so check the current limits on Cloudflare’s plan page if you expect to grow. - Add an application and choose Self-hosted.
- Name it something you will recognise, such as
Files. - Set the public hostname to
files.example.com. Leave the path empty so the whole hostname is protected. - Pick a session duration. Shorter is safer and more annoying. Twenty-four hours is a reasonable starting point for something you use daily.
- Create a policy. Name it
Me, set the action to Allow, and add an Include rule of type Emails with your address. For a household or small team, add each address, or use an Emails ending in rule for a domain you control. - Check that One-time PIN is enabled as a login method for the application, and save.
Now publish the hostname:
cloudflared tunnel route dns homelab files.example.com
Open https://files.example.com in a private window. You should land on a Cloudflare login page, not on your app. Enter your address, collect the code and you are through. Try again with an address that is not in the policy: you should see a message that you are not allowed, or no email at all, and your server’s logs should show nothing.
Policy rules worth knowing
| Action | What it does | When to use it |
|---|---|---|
| Allow | Lets in anyone who meets the rules | The normal case |
| Block | Refuses anyone who meets the rules | Excluding one identity from a broad Allow |
| Bypass | Skips Access entirely for matching requests | Rarely. It switches protection off |
| Service Auth | Lets in machines that present a valid service token | Scripts, monitoring and CI |
Keep policies small and boring. A single Allow policy that you can read in one glance is easier to trust than five layered ones. And treat Bypass like a hole in a wall: use it for a specific, narrow path such as a public webhook endpoint, never for the whole application.
Letting machines in with service tokens
A script cannot type a one-time code. For machine access, create a service token in the Access settings. Cloudflare shows you a client ID and a client secret once, and the secret cannot be retrieved afterwards, so store it in a password manager or secrets store straight away.
Then add a policy to the application with the action Service Auth and an Include rule that names that token. The script sends the two values as headers:
curl -H "CF-Access-Client-Id: 0123456789abcdef0123456789abcdef.access" \
-H "CF-Access-Client-Secret: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" \
https://files.example.com/api/status
The values above are made up. Give each system its own token, so you can revoke one without breaking the rest, and set an expiry you will actually remember to renew.
The traps
Your origin is still reachable another way. Access protects the public hostname. If the same service is also open on your LAN, or on a forwarded port, anyone who can reach that route walks straight past Access. Close the other doors. For extra safety, have the application check the Cf-Access-Jwt-Assertion header that Access adds to each request, and reject requests without a valid one. Cloudflare publishes the keys needed to verify it.
Protect the hostname, not a path. An application covering only /admin leaves /api and everything else open. Unless you have a specific reason, protect the entire hostname.
One tunnel, many hostnames, many applications. Each hostname you publish needs its own application, or a wildcard that covers it. A new hostname added to config.yml later is not protected automatically. Make “create the Access application” part of the checklist for adding any route.
Locking yourself out. If your only Allow rule is an email address you can no longer receive mail at, you are locked out of your own app. Keep a second address, or a second identity provider, as a backup.
The login proves identity, not safety. Access means only the people you chose can reach the app. If the app has a flaw, a chosen person, or someone who has stolen their session, can still exploit it. Keep it patched.
Where this fits
Access is the right answer for web apps and for shells reached through the tunnel. For SSH through a Cloudflare Tunnel, the same application and policy model applies. If you need to reach whole networks rather than single hostnames, you are into private network routing, and the comparison explains where that sits next to WireGuard and Tailscale.
Sources: Cloudflare’s documentation on Access applications and policies and service tokens. Navigation and plan details change, so check them against the live dashboard.