Admin panels, staging sites and internal dashboards should not be open to the whole internet. Cloudflare Access puts a login check in front of them at Cloudflare's edge, so only the people you choose ever reach the app.
Cloudflare Access puts a zero-trust gateway in front of your internal apps: admin panels, staging sites, internal dashboards. No VPN needed. Staff sign in with Google Workspace, Microsoft 365 or a one-time PIN, and Cloudflare blocks everyone else at the edge. Pair it with Cloudflare Tunnel on your own VPS so the app has no public port at all, and always verify Cloudflare's JWT in the app.
Why Zero Trust for internal apps
Traditional: admin panel at admin.yourcompany.com, protected with basic auth and an IP allow-list.
Problems:
- Basic auth passwords leak and get shared
- IP allow-lists break when staff work from home or a café
- No audit trail of who accessed what
- A VPN for everyone is heavy and brittle
Zero Trust:
- Staff log in with SSO (Google, Microsoft, Okta) or a one-time PIN
- Cloudflare verifies identity at the edge
- Each app has its own policy (which users, from where, with which login method)
- Every login is logged
- No shared passwords
Cloudflare Zero Trust has a free plan for small teams, with paid plans per user above that. Limits and prices change, so check Cloudflare's pricing page before you plan around them.
Architecture
Employee browser
│
▼
Cloudflare edge ◄── SSO provider (Google/Microsoft/Okta)
│
▼ (identity verified)
Your origin (admin app, staging, internal tool)Cloudflare adds a signed JWT (the Cf-Access-Jwt-Assertion header) to each request it lets through. Your origin should verify that JWT. With Cloudflare Tunnel, the origin also has no public port, so traffic can only arrive through Cloudflare.
Step 1: Enable Cloudflare Zero Trust
- Log in to the Cloudflare dashboard.Your domain's DNS must be on Cloudflare for Access to protect a hostname.
- Open Zero Trustfrom the left sidebar.
- Choose a team name.It becomes
team-name.cloudflareaccess.com. - Select the Free planto start. It is enough to test and for small teams.
Cloudflare renames dashboard menus from time to time. If a menu below has moved, search the Zero Trust dashboard for the feature name.
Step 2: Add an SSO provider
Zero Trust → Settings → Authentication → Login methods → Add new.
Google Workspace:
- Create OAuth 2.0 credentials in Google Cloud Console
- Add the redirect URI shown by Cloudflare
- Copy the Client ID and Secret into Cloudflare
One-time PIN (no SSO):
- Simplest: users get a code by email
- Good for external contractors and partners
Step 3: Create an Access application
Zero Trust → Access → Applications → Add an application → Self-hosted.
- Application name:
Admin Panel - Subdomain:
admin - Domain:
yourcompany.com - Session duration: 24 hours (shorter for sensitive apps)
Policy:
- Action: Allow
- Include: Emails ending in
@yourcompany.com - Require: Login method is Google
Optional extras:
- Require MFA at your identity provider (enforce it in Google Workspace or Microsoft Entra)
- Include specific IP ranges (office network)
- Require certain countries
Step 4: Cloudflare Tunnel (recommended on a VPS)
Traditional: expose your origin to the internet and use Access to block strangers. The origin IP is still reachable directly.
Better: Cloudflare Tunnel. A small daemon, cloudflared, on your server makes an outbound connection to Cloudflare, so the app never needs a public port.
Tunnel needs root access and a process that runs all the time, so it belongs on your own VPS, not on shared hosting.
- Zero Trust → Networks → Tunnels → Create a tunnel
- Name the tunnel
- Copy the install command the dashboard shows and run it on your VPS. On an RPM-based Linux (AlmaLinux, Rocky) it looks like this:
bash curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-x86_64.rpm -o cloudflared.rpm sudo rpm -i cloudflared.rpm sudo cloudflared service install <TOKEN_FROM_DASHBOARD> - Add a public hostname route:
- Hostname:
admin.yourcompany.com - Service:
http://localhost:3000(your internal app)
- Hostname:
- Cloudflare creates the DNS record for the hostname; you don't add one by hand.
Bind the app itself to 127.0.0.1 so it is never exposed on the server's public IP. Traffic reaches it only through the tunnel.
Step 5: Verify identity at the origin
Your app should trust only requests that Cloudflare Access has approved. Two layers:
Layer A: verify the Cf-Access-Jwt-Assertion header (always do this).
Cloudflare adds this JWT to every authenticated request. Verify it in your app:
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
const client = jwksClient({
jwksUri: 'https://team-name.cloudflareaccess.com/cdn-cgi/access/certs',
});
app.use(async (req, res, next) => {
const token = req.headers['cf-access-jwt-assertion'];
if (!token) return res.status(401).send('No access token');
try {
const decoded = jwt.decode(token, { complete: true });
const key = await client.getSigningKey(decoded.header.kid);
const payload = jwt.verify(token, key.getPublicKey(), {
algorithms: ['RS256'],
audience: 'YOUR_AUD', // the Application Audience (AUD) tag in the Access app settings
issuer: 'https://team-name.cloudflareaccess.com',
});
req.user = { email: payload.email };
next();
} catch (err) {
return res.status(403).send('Invalid access token');
}
});Layer B: accept traffic only from Cloudflare. With Tunnel this is automatic. Without Tunnel, allow only Cloudflare's IP ranges in your server firewall. This is weaker on its own, because any Cloudflare customer's traffic comes from those ranges, so keep Layer A as well.
Step 6: Policies for fine-grained access
Different rules for different apps (pseudo-configuration):
Application: Production database console
Policy: Allow
Include: members of the "DBA" group
Require: country is India
Session: 1 hourApplication: Marketing CMS
Policy: Allow
Include: emails ending in @yourcompany.com, OR [email protected]
Session: 8 hoursApplication: Staging site
Policy 1: Bypass
Include: office IP range 203.0.113.0/24
Policy 2: Allow
Include: emails ending in @yourcompany.com
Session: 24 hoursBypass is its own policy action. Use it sparingly: a bypassed request carries no identity.
Step 7: Logs and audit
Zero Trust → Logs → Access shows:
- Who accessed which app, when, and from where
- Failed login attempts (useful for spotting attacks)
You can export logs through Cloudflare's API, or with Logpush on plans that include it.
Cloudflare WARP for outbound
For a fuller zero-trust setup, the WARP client on staff laptops sends their traffic, including DNS queries, through Cloudflare. That adds DNS filtering of malicious sites, even on public Wi-Fi.
Common patterns
Pattern 1: Protect Grafana or internal dashboards
Grafana → Tunnel (localhost:3000) → Access policy (admins only) → grafana.yourcompany.com
Grafana's JWT authentication ([auth.jwt] in grafana.ini) can read the Cf-Access-Jwt-Assertion header, so users are signed in with the identity Access has already checked.
Pattern 2: Partner extranet
Invite external partners by email. They sign in with a one-time PIN. The policy restricts them to specific paths (for example /partner/*). No accounts to provision.
Pattern 3: SSH through Access
Access can broker SSH to your VPS as well. Users connect through cloudflared access ssh, so the server needs no public SSH port and every session is logged. Keep a separate emergency route to the server (see the pitfalls below).
What about shared hosting?
Access protects any hostname that is proxied through Cloudflare (the orange cloud), so you can put it in front of a site on shared hosting too. Two limits apply:
- You can't run
cloudflaredon shared hosting, so there is no Tunnel. The site stays reachable on the hosting server's IP. - That makes the JWT check in your app (Step 5, Layer A) essential, not optional.
For a truly private internal tool, run it on a VPS behind Tunnel. See Cloudflare for Indian websites for the general Cloudflare setup.
Migration: from password-protected admin to Zero Trust
- Set up Zero Trust and a login method.Google, Microsoft or one-time PIN.
- Create an Access applicationfor the admin panel.
- Install a Cloudflare Tunnelon the VPS and route the hostname to the app.
- Make sure the hostname is proxiedthrough Cloudflare (orange cloud).
- Add JWT verificationin the app and test with a test user.
- Remove basic auth from the originonce Access and the JWT check work.
- Tell the teamand share the login URL.
A single app usually takes well under a day, most of it testing.
Common pitfalls
FAQ
How much does Cloudflare Zero Trust cost?
Cloudflare offers a free plan for small teams and per-user paid plans above that. Limits and prices are set by Cloudflare and change, so check Cloudflare's current pricing page.
Zero Trust or VPN?
Zero Trust checks identity per application; a VPN gives network-level access. Zero Trust has a smaller blast radius if one user's account is compromised, and there is no VPN client to maintain for web apps.
Can I use Access without moving my DNS to Cloudflare?
Access protects hostnames that Cloudflare proxies, so in practice your domain's DNS needs to be on Cloudflare. Partial (CNAME) setups exist only on some higher Cloudflare plans.
Does Tunnel slow my app down?
It adds a hop through Cloudflare's edge. For internal tools the extra latency is usually small; measure it from where your users are.
Can I run Cloudflare Tunnel on shared hosting?
No. Tunnel needs root access to install cloudflared and a process that runs all the time, and shared hosting allows neither. Use a VPS, or put Access in front of the shared-hosting site and validate the JWT in your app.
Does Access work for non-web protocols?
Yes. SSH, RDP and other TCP services can go through Access using cloudflared or the WARP client. That setup is beyond the scope of this article.
Ready to put your internal tools behind Cloudflare Access? Run them on a Domain India VPS with Cloudflare Tunnel, or read the Cloudflare setup guide first. For hosting questions, open a ticket.
A self-managed VPS with full root access lets you run Cloudflare Tunnel and keep internal apps off the public internet.
View VPS plans