Skip to content

Zero Trust Access Control Based on Cloudflare Access: A Perfect Solution from Canary Release to Official Launch ​

Zero Trust Access Control Based on Cloudflare Access: Schematic Diagram of a Perfect Solution from Canary Release to Official Launch

Use cases

  • Pre-release environment/Canary validation environment/Internal test environment

  • Core requirements Need to implement access control for an unfinished product, allowing only team members to access it, but do not want to implement login logic in the application code, and do not want Nginx basic authentication to conflict with application authentication.

In the development process of SaaS products, we often face a dilemma:How can we protect an unfinished preview product without interfering with normal development and debugging?

Traditional Nginx Basic Auth, although simple, will ... with the application's Authorization: Bearer Token Authentication conflicts arise. Worse still, when the product officially goes live, we often have to modify the Nginx configuration, restart services, or even change code to remove this "temporary wall".

This paper introduces a type of ... based on Cloudflare Access a zero-trust access control solution that can establish an authentication wall that can be toggled on and off at any time after DNS resolution and before requests reach the origin server, enabling seamless switching from “canary release” to “official launch,”No code or configuration changes required..

1. Problem Background: Pain Points of Traditional Solutions ​

1.1 Architecture Scenario ​

Suppose we have a standard SaaS project architecture:

subdomainPurposeTech stack
dash.myapp.comSaaS Console (Frontend)Nuxt.js
api.myapp.comBackend API serviceGolang

1.2 Problems with Traditional Solutions ​

Issue 1: Conflict Between Nginx Basic Authentication and Application Authentication

  • Nginx Basic Authentication usage Authorization: Basic Request header

  • SaaS application certification requires Authorization: Bearer Request headers

  • The two conflict, causing the application to be unable to log in normally

Issue 2: Configuration needs to be modified when going live.

  • Need to manually edit the Nginx configuration file.

  • Delete or comment out auth_basic Related instructions

  • Execute nginx -s reload Restart the service

  • It carries operational risk and is not elegant enough.

Problem 3: Poor management experience

  • Adding or removing team members requires manual modification..htpasswd File

  • Unable to by email domain (e.g.@company.com) Batch authorization

  • Without audit logs, there is no way to know who accessed it and when.

2. Solution: Cloudflare Access Zero Trust Access Control ​

2.1 What is Cloudflare Access? ​

Cloudflare Access is a core component of Cloudflare's Zero Trust architecture. It can Before the request reaches the origin server, perform authentication and access control on Cloudflare edge nodes.

Working principle:

2.2 Solution Selection: Zero Trust Free Is Completely Sufficient ​

Many people worry that Cloudflare Access requires payment. In fact,Zero Trust Free Plan Provides complete Access functionality:

FunctionFree PlanDoes it meet the requirements?
Self-hosted application✅ Support✅ Satisfied
Email domain policy✅ Support✅ Satisfied
Service Token✅ Support✅ Satisfied
Seat quantityFree for 50 people✅ The team is sufficient
Cost$0/month✅ Completely free

Activation steps:

  • Go to the Cloudflare dashboard → Zero Trust

  • Select Zero Trust Free Plan

  • Confirm payment information (no charge will be made; used only for risk control)

  • Activation successful; you can now begin configuration.

2.3 The Essential Difference from Traditional Solutions ​

Comparison dimensionNginx Basic AuthenticationCloudflare Access
Authentication locationOrigin server Nginx layerCloudflare edge nodes
Whether occupied Authorization head✅ Yes, it causes conflict.❌ No, do not modify the request headers at all
Go-live cutover methodModify the Nginx configuration and restartConsole one-click shutdown policy
Authentication methodUsername/PasswordSupports GitHub, Google, OTP, and email verification codes
Team managementManual editing .htpasswdWeb interface management, supports email domain authorization
audit log❌ None✅ Complete record

2.4 Why uniformly manage two subdomains? ​

During the canary release phase, we need to simultaneously protect:

  • Console: Prevent external users from accessing unfinished pages

  • API Prevent unauthorized API calls

Cloudflare Access allows us to ... for two subdomains.Create policies separately but use a unified management interface., implement:

  • Unified identity authentication source

  • Unified team permission management

  • Unified go-live cutover operation

3. Architecture Design ​

3.1 Overall Architecture Diagram ​

3.2 Core Design Principles ​

  • Authentication and business decoupling: Access control is handled entirely at the Cloudflare layer, and the origin Nginx and application do not need to be aware of it.

  • Unified policy management: Two subdomains are managed in the same console and can be switched with one click when going live.

  • Zero-configuration deployment: When officially launched, you only need to disable the Access policy; no server configuration changes are required.

4. Nginx configuration (simplified) ​

4.1 dash.myapp.com Configuration ​

nginx
# HTTP redirect to HTTPS
server {
    listen 80;
    server_name dash.myapp.com;
    return 301 https://$server_name$request_uri;
}

# HTTPS Configuration
server {
    listen 443 ssl http2;
    server_name dash.myapp.com;

    ssl_certificate /etc/nginx/ssl/myapp.com.pem;
    ssl_certificate_key /etc/nginx/ssl/myapp.com.key;

    # SSL optimization configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;

    # Log
    access_log /var/log/nginx/dash.myapp.com-access.log;
    error_log /var/log/nginx/dash.myapp.com-error.log;

    # Cloudflare real IP (used with Access)
    set_real_ip_from 173.245.48.0/20;
    set_real_ip_from 103.21.244.0/22;
    set_real_ip_from 103.22.200.0/22;
    set_real_ip_from 103.31.4.0/22;
    set_real_ip_from 141.101.64.0/18;
    set_real_ip_from 108.162.192.0/18;
    set_real_ip_from 190.93.240.0/20;
    set_real_ip_from 188.114.96.0/20;
    set_real_ip_from 197.234.240.0/22;
    set_real_ip_from 198.41.128.0/17;
    set_real_ip_from 162.158.0.0/15;
    set_real_ip_from 104.16.0.0/13;
    set_real_ip_from 104.24.0.0/14;
    set_real_ip_from 172.64.0.0/13;
    set_real_ip_from 131.0.72.0/22;
    real_ip_header CF-Connecting-IP;

    location / {
        # Note: There is no auth_basic configuration.
        # Authentication is handled entirely by Cloudflare Access.
        proxy_pass http://127.0.0.1:3100;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Optional: pass the logged-in user information to the backend
        proxy_set_header X-Auth-User-Email $http_cf_access_authenticated_user_email;
    }

    # Static asset caching
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf)$ {
        proxy_pass http://127.0.0.1:3100;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

4.2 api.myapp.com Configuration ​

nginx
# HTTP redirect to HTTPS
server {
    listen 80;
    server_name api.myapp.com;
    return 301 https://$server_name$request_uri;
}

# HTTPS Configuration
server {
    listen 443 ssl http2;
    server_name api.myapp.com;

    ssl_certificate /etc/nginx/ssl/myapp.com.pem;
    ssl_certificate_key /etc/nginx/ssl/myapp.com.key;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;

    access_log /var/log/nginx/api.myapp.com-access.log;
    error_log /var/log/nginx/api.myapp.com-error.log;

    # Cloudflare real IP
    set_real_ip_from 173.245.48.0/20;
    set_real_ip_from 103.21.244.0/22;
    set_real_ip_from 103.22.200.0/22;
    set_real_ip_from 103.31.4.0/22;
    set_real_ip_from 141.101.64.0/18;
    set_real_ip_from 108.162.192.0/18;
    set_real_ip_from 190.93.240.0/20;
    set_real_ip_from 188.114.96.0/20;
    set_real_ip_from 197.234.240.0/22;
    set_real_ip_from 198.41.128.0/17;
    set_real_ip_from 162.158.0.0/15;
    set_real_ip_from 104.16.0.0/13;
    set_real_ip_from 104.24.0.0/14;
    set_real_ip_from 172.64.0.0/13;
    set_real_ip_from 131.0.72.0/22;
    real_ip_header CF-Connecting-IP;

    location / {
        # Likewise, there is no auth_basic configuration at all.
        proxy_pass http://127.0.0.1:2759;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Pass the user information authenticated by the Service Token
        proxy_set_header X-Auth-User-Email $http_cf_access_authenticated_user_email;
    }

    # Static asset caching
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf)$ {
        proxy_pass http://127.0.0.1:2759;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Health check authentication exemption
    location = /health {
        proxy_pass http://127.0.0.1:2759;
        proxy_set_header Host $host;
    }
}

5. Cloudflare Access Configuration in Practice ​

5.1 Activate the Zero Trust Free plan ​

Before starting configuration, you first need to activate the Cloudflare Zero Trust Free plan:

  • Log in to the Cloudflare dashboard and click the left menu's Zero Trust

  • If it is your first time using it, you will see the plan selection page.

  • Select Zero Trust Free Plan ($0/seat/month, free for 50 people)

  • Confirm payment information (requires linking a credit card or PayPal,For risk control verification only; no charge will be incurred.)

  • Click Activate Complete activation

After successful activation, you can begin configuring the Access app.

5.2 For dash.myapp.com Configure interactive logon protection ​

Step 1: Create a Self-hosted application

  • Enter Zero Trust→Access → Applications

  • Click Add an application→ Select Self-hosted

Step 2: Fill in the basic application information

FieldFill in the contentExplanation
Application namedash-myappApplication name, for easy identification
SubdomaindashSubdomain part
Domainmyapp.comPrimary domain
PathLeave blank (or fill in *)Protect all paths

After completing, click Add public hostname.

Step 3: Configure access policies

  • 向下滚动到 Access policies Region

  • Click Add a policy

  • Fill in policy information:

fieldValueDescription
Policy nameAllow Team MembersStrategy Name
ActionAllowAllow eligible requests
SelectorEmails Ending InSelect email suffix match
Value@myapp.comYour team's email domain
  • Click Save policy

  • Click at the bottom of the page Save Complete app creation

Verify configuration: Access in browser incognito mode https://dash.myapp.com, it should automatically redirect to the Cloudflare Access login page, enter @myapp.com After entering your email, you can log in once you receive the verification code.

5.3 is api.myapp.com Configure Service Token (service-to-service invocation) ​

For the API subdomain, we need to support two access methods:

  • Inter-service invocation: Frontend service (Nuxt backend) makes silent calls, with no manual interaction required

  • Team member access: Developers debug APIs through the browser

This requires configuration.Service Token and Two access policies.

Step 1: Create Service Token ​

  • In the Access left menu, click Service Credentials→Service Tokens

  • Click Create Service Token

  • Fill in information:

fieldFill in the contentDescription
Namedash-apiserverToken name; it is recommended to associate it with the caller.
Duration2 yearsValidity period: it is recommended to set it longer.
  • Click Generate token

  • ⚠️ Key step: Save the generated immediately Client ID and Client Secret!

  • Client ID format example:888/104a908c1a61de52c89237/26bee0.access

  • Client Secret is a long string of random characters:9eca11cf456625c4ac861f327eee7988...

  • The secret will only be displayed once and cannot be viewed again after closing the page!

Step 2: Create API App ​

  • 回到 Access → Applications

  • Click Create new application → Self-hosted

  • Fill in the application information:

fieldFill in the content
Application nameapi-myapp
Subdomainapi
Domainmyapp.com
PathLeave blank (or fill in *)

Click Add public hostname.

Step 3: Configure the first policy (allow Service Token) ​

  • Click Add a policy

  • Fill in policy information:

fieldvalueExplanation
Policy nameAllow Service TokenStrategy Name
ActionService Auth⚠️ Note: not Allow!
SelectorService Token选择服务令牌
Valuedash-apiserverSelect the newly created Token
  • Click Save policy

Why should Action choose Service Auth?Allow 策略会要求同时满足身份认证(如邮箱登录),导致 Service Token 请求被重定向到登录页。 Service Auth Dedicated to service-to-service calls and does not require additional authentication.

Step 4: Configure the second policy (team member access) ​

  • Click again Add a policy

  • Fill in policy information:

fieldvalue
Policy nameAllow Team Members
ActionAllow
SelectorEmails Ending In
Value@myapp.com
  • Click Save policy

Step 5: Adjust the strategy order ​

In the policy list, ensure Allow Service Token Strategy comes first.,Allow Team Members In second place.

Policy order affects matching logic: Cloudflare Access evaluates policies from top to bottom and stops after the first match. Placing the Service Token policy first ensures that requests carrying a Token are allowed through first.

Step 6: Save the application ​

Click at the bottom of the page Save Complete the application creation.

5.4 Test Verification ​

Test 1: Browser access (team members) ​

Access in browser incognito mode https://api.myapp.com/health:

  • Automatically redirect to the Cloudflare Access login page

  • Input @myapp.com email

  • Receive verification code and log in

  • ✅ Successfully saw the API response:

json
{"service":"myapp-apiserver","status":"ok","timestamp":"2026-05-26T14:23:48Z"}

Test 2: Service Token call ​

Use the curl command to test service-to-service calls:

bash
curl -H "CF-Access-Client-Id: 888/104a908c1a61de52c89237/26bee0.access" \
     -H "CF-Access-Client-Secret: 9eca11cf456625c4ac861f327eee798824496842d718f1b248b3c36936a2b238" \
     https://api.myapp.com/health

Expected result: return 200 and a JSON response directly, with no redirect and no login page.

Test 3: Unauthorized Access ​

bash
curl https://api.myapp.com/health

Expected result: returns a 302 redirect to the Cloudflare Access login page.

5.5 Common Configuration Errors and Solutions ​

Error symptomsPossible causesSolution
curl request returns 302 redirectPolicy Action used AllowChange to Service Auth
curl request returns a 302 redirectPolicy order errorMove the Service Token policy to the first position
Unable to log in in the browserEmail domain input errorConfirm it is @myapp.com Format
Configuration does not take effectForgot to saveEnsure that it has been clicked Save policy and Save
Service Token not foundNot created or expiredRecreate in Service Credentials

5.6 Frontend Service Configuration ​

In Nuxt's backend code, use a Service Token to call the API:

javascript
// server/api/call-backend.js
export default defineEventHandler(async (event) => {
  const response = await $fetch('https://api.myapp.com/user/info', {
    headers: {
      'CF-Access-Client-Id': process.env.CF_ACCESS_CLIENT_ID,
      'CF-Access-Client-Secret': process.env.CF_ACCESS_CLIENT_SECRET,
    }
  })
  return response
})

Environment variable configuration (.env):

ini
CF_ACCESS_CLIENT_ID=888/104a908c1a61de52c89237/26bee0.access
CF_ACCESS_CLIENT_SECRET=9eca11cf456625c4ac861f327eee798824496842d718f1b248b3c36936a2b238

6. Go-live cutover operation ​

When the product is ready for official launch:

OperationStepsEffect
Close dash ProtectionAccess → Applications → Click the application → Change the status from On Change to OffEveryone can access the console directly without logging in.
Close api ProtectionAccess → Applications → Click the application → Change the status from On Change to OffThe API no longer requires a Service Token or email authentication.

It's that simple. No need to modify any Nginx configuration or restart any services.

7. Canary mode vs. production mode comparison ​

visitorGrayscale mode (Access enabled)Online mode (Access disabled)
Team members (email @myapp.com)✅ Normal access✅ Normal access
External users (other email addresses)❌ Blocked by Access✅ Normal access
API call (without Service Token)❌ Returns a 302 redirect✅ Normal response
Frontend service call (with Service Token)✅ Normal call✅ Normal call
Nginx configurationNo changes neededNo changes needed.
Application codeNo changes neededNo changes needed.

8. Summary of Solution Advantages ​

advantageDescription
✅ Resolve head conflictDoes not occupy Authorization Header, Token authentication for SaaS applications is completely unaffected.
✅ Zero-config launchWhen going live, you only need to disable the policy in the Cloudflare dashboard; no server configuration changes are required.
✅ Unified managementThe console and API subdomains are managed in the same interface, with clear rules.
✅ Team-friendlySupports batch authorization by email domain (e.g.@myapp.com), no need to maintain a password file
✅ Audit logFully record who logged in, when, and from where to meet security compliance requirements.
✅ Low costCloudflare Access provides 50 users The free quota is completely sufficient.
✅ Easy to undoWhen a team member leaves, you only need to remove their permissions in the Access console; there is no need to modify the server.

9. Frequently Asked Questions ​

Q1: After official launch, what should we do if the API still wants to retain Token authentication? ​

A: Absolutely no problem. After Cloudflare Access is disabled,Authorization The header is no longer occupied by any middleware, and your Golang API can parse its own JWT token normally.

Q2: Does Cloudflare Access affect SEO? ​

A: SEO is not needed during the gray release phase. After official launch, disabling Access will not affect it. Alternatively, you can configure Bypass rules for search engines.

Q3: How secure is the Service Token? ​

A: It is recommended to use the Service Token only on the frontend.Server-side Use it in code and do not expose it on the client. The Client Secret is displayed only once at creation, so keep it safe.

Q4: Does this plan require payment? ​

A: Cloudflare Access provides 50 users The free quota is completely sufficient for canary release scenarios in most teams.

Q5: Why choose Zero Trust Free instead of other plans? ​

A: The Free plan already includes core features such as Self-hosted apps, Service Tokens, and email domain policies. It is completely free for up to 50 people, with no paid upgrade required.

Q6: Why use the Service Token strategy?Service Auth rather than Allow? ​

A:Allow The policy requires identity authentication (such as email login) to be satisfied as well, causing Service Token requests to be redirected to the login page.Service Auth Specifically for service-to-service calls, no additional authentication is required.

10. Summary ​

With Cloudflare Access, we implemented a perfect access control solution for canary releases:

  • Canary release phase: Access protection at edge nodes dash. and api., Team members log in via email verification

  • Officially launched: Simply disable the policy in the console, and the product will immediately be available to the entire network.

  • No need to modify Nginx or application code throughout the entire process. The authentication layer and the application layer are completely decoupled.

Quick memorization ​

Hand authentication over to Cloudflare; Nginx only acts as a proxy; disable it with one click when going live; no code changes are needed throughout. The Free plan is completely sufficient; Service Tokens must use Service Auth!

Appendix: Placeholder Replacement Checklist ​

placeholderExample valueDescription
myapp.comyourdomain.comMain domain
dash.myapp.compreview.yourdomain.comConsole domain
api.myapp.comapi.yourdomain.comAPI domain name
@myapp.com@company.comTeam email domain
dash-apiserverfrontend-backend-tokenService Token Name

Cloudflare Series ​

CI/CD Series ​

gRPC Series ​

Load Balancing and High Availability ​

Last updated on11/07/2026, 02:16 PM
@fishfinal | @fishfinals | [email protected]