Cloudflare, CDN & Edge

Cloudflare Zero Trust and Access for Internal Apps

By Domain India Team · DomainIndia EngineeringPublished 9 min read
Knowledge base article
Contents (15 sections)

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.

Key takeaways

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

code
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

  1. Log in to the Cloudflare dashboard.
    Your domain's DNS must be on Cloudflare for Access to protect a hostname.
  2. Open Zero Trust
    from the left sidebar.
  3. Choose a team name.
    It becomes team-name.cloudflareaccess.com.
  4. Select the Free plan
    to 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

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.

  1. Zero Trust → Networks → Tunnels → Create a tunnel
  2. Name the tunnel
  3. 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>
  4. Add a public hostname route:
    • Hostname: admin.yourcompany.com
    • Service: http://localhost:3000 (your internal app)
  5. 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:

javascript
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):

code
Application: Production database console
  Policy: Allow
    Include: members of the "DBA" group
    Require: country is India
  Session: 1 hour
code
Application: Marketing CMS
  Policy: Allow
    Include: emails ending in @yourcompany.com, OR [email protected]
  Session: 8 hours
code
Application: 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 hours

Bypass 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 cloudflared on 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

  1. Set up Zero Trust and a login method.
    Google, Microsoft or one-time PIN.
  2. Create an Access application
    for the admin panel.
  3. Install a Cloudflare Tunnel
    on the VPS and route the hostname to the app.
  4. Make sure the hostname is proxied
    through Cloudflare (orange cloud).
  5. Add JWT verification
    in the app and test with a test user.
  6. Remove basic auth from the origin
    once Access and the JWT check work.
  7. Tell the team
    and share the login URL.

A single app usually takes well under a day, most of it testing.

Common pitfalls

Not validating the JWT at the origin
If the origin is still public, anyone who finds its IP can skip Access. Always validate.
DNS record not proxied
Access only works on proxied (orange cloud) records. A grey cloud sends traffic straight to your server.
Sessions too long
24 hours can be too lax for sensitive data. Shorten it for admin apps.
No fallback if Cloudflare has a problem
Rare but possible. Keep an emergency way into the server that doesn't depend on Cloudflare.
Policy sprawl
Document your access model. Start with two or three apps and expand gradually.

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.

Host your internal apps on a VPS

A self-managed VPS with full root access lets you run Cloudflare Tunnel and keep internal apps off the public internet.

View VPS plans

Was this article helpful?

Your answer helps us decide what to improve next.

Still need help? Open a support ticket and our team will reply.

Prefer an app? Add this site to your home screen.Get the app
Cloudflare Zero Trust — Secure Internal Apps on DomainIndia