Guide
Designing a Minimal Zero‑Trust Access Layer with Cloudflare Access
A step‑by‑step architecture note for deploying Cloudflare Access as a minimal zero‑trust front‑end to an internal web app, covering requirements, config, trust boundaries, checks, and failure modes.
Published by Tasadduq Burney
04 Oct 2026, 03:26 UTC
4 min14.1K views0

Problem
You need to expose an internal web application to employees without deploying a VPN, while ensuring that only authenticated users can reach the origin and that the origin cannot be contacted directly from the internet.
Requirements
- Authenticate users via an existing identity provider (IdP).
- Enforce multi‑factor authentication (MFA) and device posture checks.
- Terminate TLS at the edge and forward requests to the origin over a mutually authenticated tunnel.
- Restrict origin accessibility to Cloudflare’s edge IPs only.
- Validate that every request reaching the origin carries proof of Cloudflare Access authentication.
- Provide observable health‑checks and fail‑over for origin availability.
Smallest Suitable Design
The design uses three Cloudflare services working together:
- Cloudflare Access – authenticates the user at the edge and injects the
X‑Cloudflare‑Access‑Client‑Idheader. - Cloudflare Load Balancing (optional but recommended) – performs health checks on origin pools and can steer traffic to a standby instance.
- Origin firewall / security group – allows inbound traffic only from Cloudflare IP ranges and validates the Access header.
Application‑level configuration (Cloudflare Access)
# Example: Create an Access application via the Cloudflare dashboard or API
{
"name": "internal‑web‑app",
"domain": "app.example.com",
"session_duration": "24h",
"auto_redirect_to_idp": true,
"idp": {
"type": "azure_ad",
"client_id": "",
"client_secret": "",
"tenant_id": ""
},
"policies": [
{
"decision": "allow",
"condition": {
"apps": ["internal‑web‑app"],
"identity": {
"email": {
"regex": "*@example.com"
}
},
"multifactor": true,
"device_posture": {
"require_encrypted_drive": true
}
}
}
]
}
Origin hardening (example for Linux with ufw)
# Allow only Cloudflare IP ranges (IPv4 + IPv6)
for ip in $(curl -s https://www.cloudflare.com/ips-v4); do ufw allow from $ip to any port 443 proto tcp; done
for ip in $(curl -s https://www.cloudflare.com/ips-v6); do ufw allow from $ip to any port 443 proto tcp; done
# Default deny all other inbound traffic
ufw default deny incoming
Header validation in NGINX (origin side)
server {
listen 443 ssl;
server_name app.example.com;
# TLS certificates (can be self‑signed; Cloudflare terminates external TLS)
ssl_certificate /etc/nginx/ssl/app.crt;
ssl_certificate_key /etc/nginx/ssl/app.key;
location / {
# Require the Access header; reject if missing or empty
if ($http_x_cloudflare_access_client_id = "") {
return 403;
}
proxy_pass http://127.0.0.1:8080; # upstream application
proxy_set_header Host $host;
}
}
Trust / Data Boundaries
- Edge trust boundary: Cloudflare edge validates the IdP token and creates the
X‑Cloudflare‑Access‑Client‑Idheader. No user credentials cross this boundary. - Origin trust boundary: The origin only trusts requests that present a valid Access header and originate from Cloudflare IP ranges. All other traffic is dropped.
- Data flow: After authentication, Cloudflare establishes an encrypted TCP tunnel (Cloudflare Tunnel or Argo Tunnel) to the origin; the application data never traverses the public internet in cleartext.
Operational Checks
- Header presence: On the origin, log
$http_x_cloudflare_access_client_idand verify it is non‑empty for every successful request. - IP restriction: Periodically run
ufw status numberedor the equivalent cloud‑security‑group audit to ensure only Cloudflare CIDRs are allowed. - IdP health: Use Cloudflare Access logs or IdP‑specific monitoring to detect authentication failures; set alerts on spikes of
401responses from Access. - Origin availability: Configure Cloudflare Load Balancing health checks (HTTP
/healthzon port 8080) and verify that traffic shifts to the standby pool when the primary fails. - Access test: From an external network, run
curl -I https://app.example.comwithout a valid session – expect403or redirect to IdP login. After logging in via the IdP, repeat the request and confirm a200response and the presence of the Access header in upstream logs.
Failure Modes and Design Triggers
Common failure modes
- IdP unavailability: Users cannot obtain a session; Access returns
403. Mitigation: configure a secondary IdP or enable Cloudflare’s “fallback to email‑code” option. - Origin misconfiguration: If the origin accepts traffic from any IP, an attacker could bypass Access by sending a forged header. Mitigation: strict IP allow‑list + header validation as shown.
- Header stripping: A misplaced proxy or WAF could remove
X‑Cloudflare‑Access‑Client‑Id. Mitigation: place validation as close to the application as possible and monitor for missing header alerts. - TLS termination mismatch: If the origin expects client‑certificate mutual TLS but Cloudflare terminates TLS, the connection will fail. Mitigation: keep origin TLS termination optional or use Cloudflare Tunnel to preserve mutual TLS.
Conditions that would change the design
- Legacy applications that cannot inspect HTTP headers (e.g., raw TCP services). In that case, replace Access with Cloudflare Tunnel + Access‑authorized SSH or use Cloudflare Spectrum with IP‑based allow‑listing.
- Need to cache private, user‑specific content at the edge. Access alone does not permit caching of authenticated responses; you would need to adjust cache‑key policies or move private data to a public endpoint with token‑based authorization.
- Requirement for sub‑second fail‑over across geographically distant origins. Then you would rely more heavily on Cloudflare Load Balancing with health‑check intervals
10sand possibly enable Argo Smart Routing. - Regulatory constraints that forbid any third‑party IdP; you would need to self‑host an IdP inside your VPC and connect it to Cloudflare Access via SAML or OIDC, adding operational overhead.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.