CLOUDFLARE TUNNEL ARCHITECTURE Your Local Network (No Open Ports) Service localhost:8080 Web App / API cloudflared Tunnel Daemon Outbound Only TLS Encrypted CF Edge DDoS Protection WAF + Access SSL Termination Zero Trust Users https://app.domain HTTPS Secure No port forwarding needed — cloudflared creates outbound-only encrypted tunnels

If you self-host applications at home or in an office — a media server, a home automation dashboard, a development environment, a personal wiki — you have traditionally needed to open ports on your router, configure NAT rules, set up dynamic DNS, and hope your ISP does not block inbound connections. Each open port is a potential attack vector, and the configuration is fragile.

Cloudflare Tunnels eliminate all of that. You run a lightweight daemon (cloudflared) on your local machine that creates an outbound-only encrypted connection to Cloudflare’s edge network. Traffic flows from the internet through Cloudflare to your service, with no inbound ports opened on your firewall whatsoever.

This guide covers everything from installation to advanced configurations including Zero Trust access policies, SSH tunneling, and multi-service setups.

Why Cloudflare Tunnels?

No Port Forwarding Required

Traditional self-hosting requires forwarding ports 80/443 (and possibly others) through your router to your server. With Cloudflare Tunnels, your firewall can block all inbound connections. The cloudflared daemon initiates outbound connections to Cloudflare, and traffic is relayed back through those connections.

No Public IP Needed

Even if your ISP uses Carrier-Grade NAT (CGNAT) and you do not have a public IP address, Cloudflare Tunnels work perfectly. The connection is outbound from your network to Cloudflare.

Built-in DDoS Protection

All traffic passes through Cloudflare’s network, which provides automatic DDoS mitigation, rate limiting, and bot protection. Your home IP address is never exposed.

Free Tier

Cloudflare Tunnels are included in the free Cloudflare plan. You need a Cloudflare account and a domain managed by Cloudflare DNS, but there is no additional cost for the tunnel itself.

Automatic TLS

Cloudflare handles SSL/TLS certificates automatically. Your local service can run on plain HTTP, and Cloudflare terminates TLS at their edge, presenting a valid certificate to visitors.

How Cloudflare Tunnels Work

The architecture is straightforward:

  1. You install cloudflared on the machine running your services
  2. cloudflared authenticates with your Cloudflare account
  3. cloudflared establishes persistent outbound connections (using HTTP/2 or QUIC) to Cloudflare’s nearest data centers
  4. When a visitor requests app.knowledgexchange.xyz, Cloudflare routes the request through the tunnel to your local cloudflared instance
  5. cloudflared forwards the request to your local service (e.g., http://localhost:8080)
  6. The response travels back through the same tunnel
Visitor -> Cloudflare Edge (TLS) -> Tunnel -> cloudflared -> localhost:8080

Key Point: Your server never accepts inbound connections. All tunnel connections are initiated outbound by cloudflared. This means even if someone knows your home IP address, they cannot reach your services directly.

Prerequisites

Before starting, you need:

  • A Cloudflare account (free tier is sufficient)
  • A domain name with DNS managed by Cloudflare (you can transfer an existing domain or register a new one through Cloudflare Registrar)
  • A Linux, macOS, or Windows machine running the service you want to expose
  • The service running and accessible on localhost (e.g., a web app on port 8080)

Installing cloudflared

Debian/Ubuntu

# Add the Cloudflare GPG key and repository
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg > /dev/null

echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" | \
  sudo tee /etc/apt/sources.list.d/cloudflared.list

sudo apt-get update
sudo apt-get install -y cloudflared

RHEL/CentOS/Fedora

# Add Cloudflare repository
sudo rpm --import https://pkg.cloudflare.com/cloudflare-main.gpg
curl -fsSL https://pkg.cloudflare.com/cloudflared-ascii.repo | sudo tee /etc/yum.repos.d/cloudflared.repo

sudo yum install -y cloudflared

macOS

brew install cloudflared

Windows

Download the installer from the Cloudflare GitHub releases or use winget:

winget install --id Cloudflare.cloudflared

Docker

docker pull cloudflare/cloudflared:latest

Verify Installation

cloudflared --version
# cloudflared version 2026.1.x (built ...)

Authenticating with Cloudflare

Before creating tunnels, authenticate cloudflared with your Cloudflare account:

cloudflared tunnel login

This opens a browser window where you select the domain you want to use with tunnels. After authorization, a certificate is saved to ~/.cloudflared/cert.pem.

Note: This step only needs to be done once per machine. The certificate is used for all future tunnel operations.

Creating a Tunnel

Create the Tunnel

cloudflared tunnel create my-homelab

This generates:

  • A unique Tunnel ID (a UUID like a1b2c3d4-e5f6-7890-abcd-ef1234567890)
  • A credentials file at ~/.cloudflared/<TUNNEL_ID>.json
Tunnel credentials written to /home/user/.cloudflared/a1b2c3d4-e5f6-7890-abcd-ef1234567890.json
Created tunnel my-homelab with id a1b2c3d4-e5f6-7890-abcd-ef1234567890

List Your Tunnels

cloudflared tunnel list

Configure DNS

Route a subdomain to your tunnel:

cloudflared tunnel route dns my-homelab app.knowledgexchange.xyz

This creates a CNAME record in your Cloudflare DNS pointing app.knowledgexchange.xyz to <TUNNEL_ID>.cfargotunnel.com.

Configuring Ingress Rules

Create a configuration file at ~/.cloudflared/config.yml:

Single Service

tunnel: a1b2c3d4-e5f6-7890-abcd-ef1234567890
credentials-file: /home/user/.cloudflared/a1b2c3d4-e5f6-7890-abcd-ef1234567890.json

ingress:
  - hostname: app.knowledgexchange.xyz
    service: http://localhost:8080
  - service: http_status:404

Important: The last ingress rule must be a catch-all (no hostname specified). This handles requests that do not match any defined hostname. Using http_status:404 returns a 404 for unmatched requests.

Multiple Services on Subdomains

This is where Cloudflare Tunnels truly shine. A single tunnel can expose multiple services on different subdomains:

tunnel: a1b2c3d4-e5f6-7890-abcd-ef1234567890
credentials-file: /home/user/.cloudflared/a1b2c3d4-e5f6-7890-abcd-ef1234567890.json

ingress:
  # Reverse proxy to a web application
  - hostname: app.knowledgexchange.xyz
    service: http://localhost:8080

  # Home Assistant instance
  - hostname: home.knowledgexchange.xyz
    service: http://localhost:8123

  # Gitea self-hosted Git server
  - hostname: git.knowledgexchange.xyz
    service: http://localhost:3000

  # Grafana monitoring dashboard
  - hostname: grafana.knowledgexchange.xyz
    service: http://localhost:3001

  # Jellyfin media server
  - hostname: media.knowledgexchange.xyz
    service: http://localhost:8096

  # Catch-all rule (required)
  - service: http_status:404

For each hostname, create a DNS route:

cloudflared tunnel route dns my-homelab app.knowledgexchange.xyz
cloudflared tunnel route dns my-homelab home.knowledgexchange.xyz
cloudflared tunnel route dns my-homelab git.knowledgexchange.xyz
cloudflared tunnel route dns my-homelab grafana.knowledgexchange.xyz
cloudflared tunnel route dns my-homelab media.knowledgexchange.xyz

Advanced Ingress Options

You can fine-tune each ingress rule with additional options:

ingress:
  - hostname: app.knowledgexchange.xyz
    service: http://localhost:8080
    originRequest:
      # Timeout for connecting to the local service
      connectTimeout: 10s
      # Disable TLS verification for self-signed certs on local services
      noTLSVerify: true
      # Forward the original Host header
      httpHostHeader: app.knowledgexchange.xyz
      # Keep-alive settings
      keepAliveConnections: 100
      keepAliveTimeout: 90s

  - hostname: secure.knowledgexchange.xyz
    service: https://localhost:8443
    originRequest:
      # If your local service uses HTTPS with a self-signed cert
      noTLSVerify: true

  - service: http_status:404

Running the Tunnel

Manual Start (for Testing)

cloudflared tunnel run my-homelab

You should see output indicating the tunnel is connected:

INF Starting tunnel tunnelID=a1b2c3d4-e5f6-7890-abcd-ef1234567890
INF Connection established connIndex=0 location=DFW
INF Connection established connIndex=1 location=IAH
INF Connection established connIndex=2 location=DFW
INF Connection established connIndex=3 location=IAH

Note: cloudflared establishes multiple connections to different Cloudflare data centers for redundancy. If one connection drops, traffic is automatically routed through the remaining connections.

Validate Your Configuration

Before running, validate the configuration:

cloudflared tunnel ingress validate

Test which ingress rule matches a specific URL:

cloudflared tunnel ingress rule https://app.knowledgexchange.xyz
# Using rules from /home/user/.cloudflared/config.yml
# Matched rule #0: hostname=app.knowledgexchange.xyz service=http://localhost:8080

Running as a systemd Service

For production use, run cloudflared as a system service that starts automatically on boot:

Install the Service

sudo cloudflared service install

This creates a systemd service unit and copies your configuration to /etc/cloudflared/config.yml and your credentials to /etc/cloudflared/.

Manage the Service

# Start the service
sudo systemctl start cloudflared

# Enable auto-start on boot
sudo systemctl enable cloudflared

# Check status
sudo systemctl status cloudflared

# View logs
sudo journalctl -u cloudflared -f

Manual systemd Unit (Alternative)

If you prefer to create the service manually:

# /etc/systemd/system/cloudflared.service
[Unit]
Description=Cloudflare Tunnel
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
ExecStart=/usr/bin/cloudflared tunnel --config /etc/cloudflared/config.yml run
Restart=on-failure
RestartSec=5s
User=cloudflared
Group=cloudflared

# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/var/log/cloudflared

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now cloudflared

Dashboard Management

Cloudflare Tunnels can also be created and managed entirely from the Cloudflare Zero Trust dashboard:

  1. Log in to the Cloudflare Zero Trust Dashboard
  2. Navigate to Networks > Tunnels
  3. Click Create a Tunnel
  4. Follow the wizard to name the tunnel and get an installation command
  5. Configure public hostnames and services in the web UI

The dashboard approach generates a token-based connector command:

sudo cloudflared service install <TOKEN>

Tip: Dashboard-managed tunnels are recommended for teams because the configuration is stored centrally in Cloudflare, not in a local file on the server. Multiple team members can update routing rules without SSH access to the server.

Access Policies with Cloudflare Zero Trust

For services that should not be publicly accessible (admin panels, dashboards, internal tools), add Cloudflare Access policies:

Setting Up an Access Policy

  1. In the Zero Trust dashboard, go to Access > Applications
  2. Click Add an Application
  3. Choose Self-hosted
  4. Configure:
    • Application name: Grafana Dashboard
    • Session duration: 24 hours
    • Application domain: grafana.knowledgexchange.xyz
  5. Add a policy:
    • Policy name: Allowed Users
    • Action: Allow
    • Include rule: Emails ending in @knowledgexchange.xyz
  6. Save

Now, anyone accessing grafana.knowledgexchange.xyz will be presented with a Cloudflare Access login page. Only users with authorized email addresses can proceed.

Authentication Options

Cloudflare Access supports multiple identity providers:

  • One-Time PIN (email) — no IdP required, Cloudflare sends a code to the user’s email
  • Google Workspace
  • Microsoft Azure AD / Entra ID
  • GitHub / GitLab
  • Okta, OneLogin, SAML, OIDC

SSH Through Cloudflare Tunnels

You can use Cloudflare Tunnels to provide secure SSH access without exposing port 22:

Server-Side Configuration

Add an SSH ingress rule in your config.yml:

ingress:
  - hostname: ssh.knowledgexchange.xyz
    service: ssh://localhost:22
  - hostname: app.knowledgexchange.xyz
    service: http://localhost:8080
  - service: http_status:404

Route the DNS:

cloudflared tunnel route dns my-homelab ssh.knowledgexchange.xyz

Client-Side Configuration

On the client machine, install cloudflared and add this to ~/.ssh/config:

Host ssh.knowledgexchange.xyz
    ProxyCommand /usr/local/bin/cloudflared access ssh --hostname %h

Now you can SSH normally:

ssh [email protected]

The SSH connection is tunneled through Cloudflare. If you have an Access policy on ssh.knowledgexchange.xyz, the user will be prompted to authenticate through the browser before the SSH connection is established.

Short-Lived SSH Certificates

For even stronger security, configure Cloudflare to issue short-lived SSH certificates, eliminating the need for static SSH keys:

  1. Generate a CA key pair via the Zero Trust dashboard
  2. Configure your SSH server to trust the Cloudflare CA
  3. Users authenticate through Cloudflare Access and receive a temporary certificate

This approach means no SSH keys to manage, rotate, or revoke.

TCP and UDP Tunnels

Beyond HTTP and SSH, Cloudflare Tunnels support arbitrary TCP and UDP protocols:

TCP Example (Database Access)

ingress:
  - hostname: db.knowledgexchange.xyz
    service: tcp://localhost:5432  # PostgreSQL
  - service: http_status:404

Client-side access:

cloudflared access tcp --hostname db.knowledgexchange.xyz --url localhost:5432

Then connect your database client to localhost:5432, and the traffic is tunneled to your remote PostgreSQL server.

UDP Example

ingress:
  - hostname: dns.knowledgexchange.xyz
    service: udp://localhost:53  # DNS server
  - service: http_status:404

Monitoring and Metrics

Built-in Metrics

cloudflared exposes Prometheus metrics on port 60123 by default:

# In config.yml
metrics: localhost:60123

Access metrics at http://localhost:60123/metrics. Key metrics include:

  • cloudflared_tunnel_request_per_second — Request rate
  • cloudflared_tunnel_response_by_code — HTTP response code distribution
  • cloudflared_tunnel_concurrent_requests_per_tunnel — Active connections

Cloudflare Dashboard Monitoring

In the Zero Trust dashboard under Networks > Tunnels, you can see:

  • Connection status (healthy/degraded/down)
  • Connected data center locations
  • Active connections count
  • Tunnel uptime

Troubleshooting

Tunnel Not Connecting

# Check if cloudflared can reach Cloudflare
cloudflared tunnel run --loglevel debug my-homelab

# Verify your credentials file exists
ls -la ~/.cloudflared/

# Check DNS resolution
dig app.knowledgexchange.xyz CNAME

502 Bad Gateway Errors

This usually means cloudflared cannot reach your local service:

# Verify the local service is running
curl -v http://localhost:8080

# Check if the service is bound to localhost vs 0.0.0.0
ss -tlnp | grep 8080

Common Mistake: If your service is running in Docker with -p 127.0.0.1:8080:8080, it is accessible from cloudflared on the host. But if you run cloudflared in Docker too, they need to be on the same Docker network. Use Docker Compose to ensure both containers share a network.

Connection Drops

If connections are frequently dropping:

# Check system logs
sudo journalctl -u cloudflared --since "1 hour ago"

# Ensure your network allows outbound connections on ports 443 and 7844
# Port 7844 is used for QUIC connections

Permission Errors

# Ensure the credentials file is readable
chmod 600 ~/.cloudflared/*.json

# For systemd service, ensure /etc/cloudflared/ has correct permissions
sudo chown -R cloudflared:cloudflared /etc/cloudflared/
sudo chmod 700 /etc/cloudflared/

Summary

Cloudflare Tunnels fundamentally change how you expose self-hosted services to the internet. No more port forwarding, no more dynamic DNS, no more worrying about ISP restrictions or exposed IP addresses. The outbound-only connection model means your firewall stays locked down while your services remain accessible.

Combined with Cloudflare Zero Trust access policies, you get enterprise-grade authentication and authorization for your self-hosted applications — all on the free tier. Whether you are running a home lab, a small business server, or a development environment, Cloudflare Tunnels provide a secure, reliable, and free solution.

For related networking and security topics, see our articles on configuring Nginx and connecting to remote systems using SSH.