Exposing Internal HTTP Services via Cloudflare Tunnel and Access: Architecture Note
An architecture note on using Cloudflare Tunnel and Access to expose internal HTTP services without opening inbound firewall ports, focusing on trust boundaries and failure modes.
04 Oct 2026, 10:34 UTC

The Problem: Secure External Access Without Inbound Ports
Exposing an internal service to the internet typically requires opening inbound firewall ports and managing public IP addresses, which increases the attack surface. The goal is to provide external access to an internal HTTP service while maintaining a "deny-all" inbound firewall posture and enforcing identity-based authentication before traffic ever reaches the private network.
Requirements
- Network Isolation: No public IP addresses assigned to the origin server and no inbound ports open on the corporate firewall.
- Edge Termination: TLS termination must occur at the Cloudflare edge to offload certificate management.
- Identity Enforcement: Every request must be authenticated via an Identity Provider (IdP) using Cloudflare Access before being routed to the origin.
Smallest Suitable Design
The minimal production-ready architecture consists of three components: a cloudflared connector, a Cloudflare Tunnel, and an Access policy.
- The Connector: Deploy the
cloudflaredbinary inside the private network. It establishes an outbound-only mTLS (mutual TLS) connection to the Cloudflare edge. - The Tunnel: A logical mapping in the Cloudflare dashboard that routes a public hostname (e.g.,
app.example.com) through the established tunnel to a local service address (e.g.,http://10.0.5.12:8080). - The Access Policy: A Zero Trust rule attached to the hostname that requires a valid identity assertion (OIDC, SAML, or email verification) before the edge forwards the request into the tunnel.
Trust and Data Boundaries
Understanding where trust is handed off is critical to preventing origin bypass.
- Edge to Connector: Trust is established via mTLS. The connector proves its identity to Cloudflare using a unique tunnel credential file.
- Connector to Origin: The connector acts as a reverse proxy. Because the connector is inside the perimeter, the origin sees requests coming from the connector's internal IP.
- Identity Assertion: Cloudflare Access inserts a
Cf-Access-Jwt-Assertionheader into requests. The origin must not trust the request based on the fact that it arrived; it should validate this JWT (JSON Web Token) against Cloudflare's public keys (JWKS) to ensure the user was actually authenticated. - Client IP: The origin cannot rely on the source IP for security. Use the
CF-Connecting-IPheader, but only after verifying the request originated from the trusted tunnel.
Operational Checks
To ensure the tunnel is performing as expected, use the following diagnostics:
- Connector Health: By default,
cloudflaredexposes metrics athttp://localhost:2000/metrics. Monitor this for connection drops. - Tunnel Status: Run the following command on the connector host to verify the connection state:
Expected result:cloudflared tunnel info <TUNNEL_ID>Status: healthy. - Policy Audit: Navigate to Zero Trust > Access > Logs in the Cloudflare dashboard to verify that identity assertions are being evaluated and that unauthorized attempts are being blocked.
Failure Modes
| Failure Scenario | Impact | Mitigation |
|---|---|---|
| Connector Process Crashes | External access is lost (Fail-Closed). Internal access remains. | Configure cloudflared as a systemd service with Restart=always. |
| Access Policy Misconfiguration | Legitimate users are blocked or unauthorized users gain entry. | Implement "Audit Mode" for policies before enforcing them in production. |
| Origin Bypass | Attacker hits the origin IP directly, skipping Cloudflare Access. | Bind the origin service to localhost or a private subnet; drop all non-connector traffic at the host firewall. |
| Version Drift | Protocol mismatch between edge and connector. | Pin cloudflared versions in deployment scripts; test updates in staging. |
Example Configuration
This example assumes a Linux environment and cloudflared v2024.x. Run these commands on the internal server intended to be the connector.
# 1. Authenticate the CLI (Requires API token with Tunnel:Edit permissions)
sudo cloudflared tunnel login
# 2. Create the tunnel
cloudflared tunnel create internal-app-tunnel
# Note the TUNNEL_ID from the output
# 3. Create config.yml in /etc/cloudflared/
tunnel: <TUNNEL_ID>
credentials-file: /root/.cloudflared/<TUNNEL_ID>.json
ingress:
- hostname: app.example.com
service: http://localhost:8080
- service: http_status:404
# 4. Install and start as a system service
sudo cloudflared service install
sudo systemctl start cloudflared
Risks: Running cloudflared as root is often required for service installation but increases the blast radius if the binary is compromised. Use a dedicated service account where possible. The http_status:404 rule is mandatory as the final ingress entry to handle unmatched requests.
Verification and Limitations
Verification Steps:
- External Test: Run
curl -I https://app.example.com. You should be redirected to the Cloudflare Access login page. - Bypass Test: Attempt to
curlthe origin's private IP from outside the network. It should timeout. - Connectivity Test: Stop the service (
sudo systemctl stop cloudflared) and verify the external URL returns a 502/504 error.
Limitations:
- Protocol Support: This design is for HTTP/HTTPS/WebSockets. TCP/UDP (SSH, RDP) requires the WARP client or
cloudflared access tcpon the client side. - Bandwidth: Extremely high-throughput requirements may be limited by the connector's CPU/NIC; consider multiple connectors for high availability and load distribution.
Conditions for Design Change
- Non-Web Traffic: If you need to expose a database or SSH, transition to Cloudflare Access for Infrastructure.
- High Availability: For production, deploy multiple connectors across different availability zones using the same Tunnel ID to prevent a single point of failure.
- Direct Interconnect: If latency requirements are sub-millisecond or bandwidth exceeds several Gbps, replace the tunnel with Cloudflare Network Interconnect (CNI).
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.