A secure, outbound-only tunnel that connects local services to the internet through Cloudflare's network without exposing public IP addresses or opening inbound ports.

Table of Contents#

  1. Overview
  2. Key Features
  3. Architecture
  4. Installation
  5. Creating and Configuring a Tunnel
  6. Ingress Rules
  7. DNS Configuration
  8. Running as a Service
  9. Token Management and Rotation
  10. Monitoring and Alerting
  11. Cloudflare Access Integration
  12. Troubleshooting
  13. See Also
  14. Sources

1. Overview#

Cloudflare Tunnel establishes a secure, outbound-only connection from your server to Cloudflare's edge network. The cloudflared daemon running on your server initiates and maintains this connection, eliminating the need for inbound firewall rules, public IP addresses, or port forwarding. Traffic to your services is routed through Cloudflare by configuring DNS records that point to the tunnel, effectively hiding your server's real IP address from the public internet.

This is fundamentally different from a traditional VPN: Cloudflare Tunnel exposes specific services to the internet (or to authenticated users via Cloudflare Access), rather than providing network-level connectivity between endpoints.

Note: Cloudflare Tunnel (cloudflared) creates an outbound connection from your server to Cloudflare, with no inbound ports needed. This is different from VLESS+CDN (which uses Cloudflare as a reverse proxy for an inbound connection). For censorship bypass using Cloudflare as a CDN front, see the Censorship Bypass guide, Section 6.

2. Key Features#

  • No inbound ports - the tunnel is outbound-only; no firewall holes, no public IP required
  • Automatic TLS - Cloudflare handles SSL certificates for public-facing traffic
  • Zero Trust integration - combine with Cloudflare Access for identity-based access control
  • Reduced attack surface - origin servers are invisible to the internet
  • Load balancing - run multiple cloudflared instances for redundancy and failover
  • Protocol support - HTTP, HTTPS, SSH, RDP, TCP, and UDP (via WARP-to-Tunnel)
  • Centralized management - configure tunnels via the Cloudflare dashboard or CLI

3. Architecture#

User -> Cloudflare Edge (TLS termination, WAF, Access) -> Tunnel -> cloudflared -> Local Service

cloudflared maintains persistent outbound connections to Cloudflare's edge.
By default, cloudflared opens 4 connections to 2 different data centers for redundancy.

Components:

  • cloudflared - lightweight daemon that creates the encrypted tunnel
  • Cloudflare Dashboard - manage tunnels, DNS, Access policies, and monitoring
  • DNS CNAME records - route traffic from public hostnames through the tunnel
  • Cloudflare Access (optional) - identity-aware proxy for authentication and authorization

4. Installation#

4.1. Arch Linux#

sudo pacman -S cloudflared

4.2. 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 update
sudo apt install cloudflared

4.3. Fedora/RHEL#

sudo dnf install cloudflared

4.4. Binary Download#

curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o cloudflared
chmod +x cloudflared
sudo mv cloudflared /usr/local/bin/

5. Creating and Configuring a Tunnel#

5.1. Authenticate#

cloudflared tunnel login
# Opens a browser for Cloudflare authentication
# Saves certificate to ~/.cloudflared/cert.pem

5.2. Create a Tunnel#

cloudflared tunnel create <tunnel-name>
# Creates the tunnel and generates a credentials file
# Output: Created tunnel <tunnel-name> with id <tunnel-id>
# Credentials saved to ~/.cloudflared/<tunnel-id>.json

5.3. Basic Configuration#

Create the configuration file at ~/.cloudflared/config.yml (or /etc/cloudflared/config.yml for system-wide):

tunnel: <tunnel-id>
credentials-file: /path/to/<tunnel-id>.json

ingress:
  - hostname: app.example.com
    service: http://localhost:8080
  - service: http_status:404

5.4. Run the Tunnel#

cloudflared tunnel --config /path/to/config.yml run

6. Ingress Rules#

Ingress rules define how incoming requests are routed to local services. Rules are evaluated top-to-bottom; the first match wins. The last rule must be a catch-all with no hostname.

6.1. Basic Ingress#

ingress:
  - hostname: app.example.com
    service: http://localhost:8080
  - hostname: api.example.com
    service: http://localhost:3000
  - service: http_status:404

6.2. Path-Based Routing#

ingress:
  - hostname: app.example.com
    path: /api/*
    service: http://localhost:3000
  - hostname: app.example.com
    path: /static/*
    service: http://localhost:8080
  - hostname: app.example.com
    service: http://localhost:8080
  - service: http_status:404

6.3. Multiple Services#

ingress:
  # Web application
  - hostname: app.example.com
    service: http://localhost:8080
    originRequest:
      connectTimeout: 30s
      noTLSVerify: true

  # Grafana dashboard
  - hostname: grafana.example.com
    service: http://localhost:3000

  # SSH access (via cloudflared access on client side)
  - hostname: ssh.example.com
    service: ssh://localhost:22

  # RDP access
  - hostname: rdp.example.com
    service: rdp://localhost:3389

  # TCP service on arbitrary port
  - hostname: db.example.com
    service: tcp://localhost:5432

  # Catch-all
  - service: http_status:404

6.4. Origin Request Options#

Configure per-service connection behavior:

ingress:
  - hostname: app.example.com
    service: https://localhost:8443
    originRequest:
      # TLS settings
      noTLSVerify: true              # Skip TLS verification for self-signed certs
      originServerName: app.local    # SNI for the origin TLS connection
      caPool: /path/to/ca.pem       # Custom CA bundle

      # Timeouts
      connectTimeout: 30s
      tlsTimeout: 10s
      keepAliveTimeout: 90s

      # Connection management
      keepAliveConnections: 100
      disableChunkedEncoding: false

      # HTTP settings
      httpHostHeader: app.internal   # Override the Host header sent to origin
      noHappyEyeballs: false

  - service: http_status:404

6.5. Validate Configuration#

cloudflared tunnel ingress validate
cloudflared tunnel ingress rule https://app.example.com/api/test
# Shows which ingress rule matches a given URL

7. DNS Configuration#

cloudflared tunnel route dns <tunnel-name> app.example.com
# Creates a CNAME record: app.example.com -> <tunnel-id>.cfargotunnel.com

7.2. Manual DNS#

In the Cloudflare dashboard:

  1. Navigate to DNS settings for your domain
  2. Create a CNAME record:
    • Name: app (or your chosen subdomain)
    • Target: <tunnel-id>.cfargotunnel.com
    • Proxy status: Proxied (orange cloud)

8. Running as a Service#

8.1. Automatic Service Installation#

sudo cloudflared service install
sudo systemctl enable --now cloudflared

This installs the service using the config at /etc/cloudflared/config.yml.

8.2. Manual systemd Service#

# /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=5
TimeoutStartSec=0
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

8.3. Token-Based Service (Remotely Managed)#

For tunnels managed entirely from the Cloudflare dashboard:

sudo cloudflared service install <token>
# The token contains all configuration; no local config.yml needed

9. Token Management and Rotation#

9.1. Understanding Tunnel Credentials#

Each tunnel has a credentials file (<tunnel-id>.json) generated at creation. This file contains the tunnel secret used to authenticate with Cloudflare.

9.2. Rotating Tunnel Credentials#

Cloudflare does not provide a direct credential rotation command. The process is:

# 1. Create a new tunnel
cloudflared tunnel create <new-tunnel-name>

# 2. Update config.yml with the new tunnel ID and credentials
# 3. Update DNS records to point to the new tunnel
cloudflared tunnel route dns <new-tunnel-name> app.example.com

# 4. Restart cloudflared with the new config
sudo systemctl restart cloudflared

# 5. Delete the old tunnel
cloudflared tunnel delete <old-tunnel-name>

9.3. Token-Based Tunnel Rotation#

For remotely managed tunnels, generate a new token from the dashboard:

  1. Go to Zero Trust > Networks > Tunnels
  2. Select the tunnel
  3. Click Configure > rotate the connector token
  4. Update the service with the new token:
sudo cloudflared service uninstall
sudo cloudflared service install <new-token>
sudo systemctl restart cloudflared

9.4. API Token Rotation#

If using the Cloudflare API for tunnel management:

  1. Go to My Profile > API Tokens
  2. Create a new token with the required permissions
  3. Revoke the old token
  4. Update any automation that references the old token

10. Monitoring and Alerting#

10.1. Tunnel Status#

# List all tunnels and their status
cloudflared tunnel list

# Detailed tunnel info (connections, data centers)
cloudflared tunnel info <tunnel-name>

# Check connector health
cloudflared tunnel run --metrics localhost:2000
# Then access http://localhost:2000/metrics for Prometheus metrics

10.2. Metrics Endpoint#

Enable a Prometheus-compatible metrics endpoint:

# In config.yml
metrics: localhost:2000

Key metrics to monitor:

  • cloudflared_tunnel_active_streams - number of active connections
  • cloudflared_tunnel_request_errors - request error count
  • cloudflared_tunnel_response_by_code - HTTP response code distribution
  • cloudflared_tunnel_server_locations - connected Cloudflare data centers

10.3. Logging#

# Increase log verbosity
cloudflared tunnel --loglevel debug run

# Log to file
cloudflared tunnel --logfile /var/log/cloudflared.log run

# JSON-formatted logs (for log aggregation)
cloudflared tunnel --log-format json run

10.4. Cloudflare Dashboard Monitoring#

In the Zero Trust dashboard:

  • Networks > Tunnels - shows tunnel status, connected data centers, and connector health
  • Logs > Access - shows authentication events (if using Cloudflare Access)
  • Analytics > Gateway - shows traffic patterns and policy hits

10.5. Alerting#

Configure alerts in the Cloudflare dashboard:

  1. Go to Notifications > Create
  2. Select "Tunnel health alert"
  3. Choose notification method (email, webhook, PagerDuty)
  4. Configure thresholds (tunnel down, high error rate)

11. Cloudflare Access Integration#

Protect tunnel services with identity-based access control:

User -> Cloudflare Edge -> Access Policy Check -> Tunnel -> Origin

11.1. Create an Access Application#

  1. Go to Zero Trust > Access > Applications
  2. Add a self-hosted application
  3. Set the application domain (must match a tunnel ingress hostname)
  4. Create an access policy:
Application: app.example.com
Policy: Allow
    Include: Emails ending in @example.com
    Require: Country is AT, DE

11.2. Service Tokens (Machine-to-Machine)#

For API or automated access:

  1. Create a service token in Access > Service Auth
  2. Include headers in requests:
curl -H "CF-Access-Client-Id: <client-id>" \
     -H "CF-Access-Client-Secret: <client-secret>" \
     https://app.example.com/api/endpoint

Troubleshooting#

IssueCauseSolution
"failed to connect to tunnel"Credentials file missing or invalidVerify the credentials-file path in config.yml; re-create the tunnel if needed
"connection refused" to local serviceTarget service not running or wrong portVerify the service is listening: ss -tlnp | grep <port>
502 Bad GatewayOrigin service returning errors or TLS mismatchAdd noTLSVerify: true for self-signed certs; check origin logs
DNS not resolving to tunnelCNAME record missing or not proxiedVerify the CNAME points to <tunnel-id>.cfargotunnel.com with orange cloud enabled
Tunnel shows "inactive" in dashboardcloudflared process not running or network issueCheck systemctl status cloudflared; verify outbound connectivity to Cloudflare
WebSocket connections failingMissing or incorrect ingress configurationCloudflare Tunnel supports WebSockets natively; verify the origin service accepts WS connections
Large file uploads timing outDefault timeout too shortSet connectTimeout and originRequest timeouts in the ingress rule
Multiple tunnels conflictingSame hostname configured in multiple tunnelsEnsure each hostname is routed to exactly one tunnel

See Also#

Sources#