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#
- Overview
- Key Features
- Architecture
- Installation
- Creating and Configuring a Tunnel
- Ingress Rules
- DNS Configuration
- Running as a Service
- Token Management and Rotation
- Monitoring and Alerting
- Cloudflare Access Integration
- Troubleshooting
- See Also
- 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
cloudflaredinstances 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 cloudflared4.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 cloudflared4.3. Fedora/RHEL#
sudo dnf install cloudflared4.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.pem5.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>.json5.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:4045.4. Run the Tunnel#
cloudflared tunnel --config /path/to/config.yml run6. 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:4046.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:4046.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:4046.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:4046.5. Validate Configuration#
cloudflared tunnel ingress validate
cloudflared tunnel ingress rule https://app.example.com/api/test
# Shows which ingress rule matches a given URL7. DNS Configuration#
7.1. Automatic DNS (Recommended)#
cloudflared tunnel route dns <tunnel-name> app.example.com
# Creates a CNAME record: app.example.com -> <tunnel-id>.cfargotunnel.com7.2. Manual DNS#
In the Cloudflare dashboard:
- Navigate to DNS settings for your domain
- Create a CNAME record:
- Name:
app(or your chosen subdomain) - Target:
<tunnel-id>.cfargotunnel.com - Proxy status: Proxied (orange cloud)
- Name:
8. Running as a Service#
8.1. Automatic Service Installation#
sudo cloudflared service install
sudo systemctl enable --now cloudflaredThis 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.target8.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 needed9. 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:
- Go to Zero Trust > Networks > Tunnels
- Select the tunnel
- Click Configure > rotate the connector token
- Update the service with the new token:
sudo cloudflared service uninstall
sudo cloudflared service install <new-token>
sudo systemctl restart cloudflared9.4. API Token Rotation#
If using the Cloudflare API for tunnel management:
- Go to My Profile > API Tokens
- Create a new token with the required permissions
- Revoke the old token
- 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 metrics10.2. Metrics Endpoint#
Enable a Prometheus-compatible metrics endpoint:
# In config.yml
metrics: localhost:2000Key metrics to monitor:
cloudflared_tunnel_active_streams- number of active connectionscloudflared_tunnel_request_errors- request error countcloudflared_tunnel_response_by_code- HTTP response code distributioncloudflared_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 run10.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:
- Go to Notifications > Create
- Select "Tunnel health alert"
- Choose notification method (email, webhook, PagerDuty)
- 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 -> Origin11.1. Create an Access Application#
- Go to Zero Trust > Access > Applications
- Add a self-hosted application
- Set the application domain (must match a tunnel ingress hostname)
- Create an access policy:
Application: app.example.com
Policy: Allow
Include: Emails ending in @example.com
Require: Country is AT, DE11.2. Service Tokens (Machine-to-Machine)#
For API or automated access:
- Create a service token in Access > Service Auth
- Include headers in requests:
curl -H "CF-Access-Client-Id: <client-id>" \
-H "CF-Access-Client-Secret: <client-secret>" \
https://app.example.com/api/endpointTroubleshooting#
| Issue | Cause | Solution |
|---|---|---|
| "failed to connect to tunnel" | Credentials file missing or invalid | Verify the credentials-file path in config.yml; re-create the tunnel if needed |
| "connection refused" to local service | Target service not running or wrong port | Verify the service is listening: ss -tlnp | grep <port> |
| 502 Bad Gateway | Origin service returning errors or TLS mismatch | Add noTLSVerify: true for self-signed certs; check origin logs |
| DNS not resolving to tunnel | CNAME record missing or not proxied | Verify the CNAME points to <tunnel-id>.cfargotunnel.com with orange cloud enabled |
| Tunnel shows "inactive" in dashboard | cloudflared process not running or network issue | Check systemctl status cloudflared; verify outbound connectivity to Cloudflare |
| WebSocket connections failing | Missing or incorrect ingress configuration | Cloudflare Tunnel supports WebSockets natively; verify the origin service accepts WS connections |
| Large file uploads timing out | Default timeout too short | Set connectTimeout and originRequest timeouts in the ingress rule |
| Multiple tunnels conflicting | Same hostname configured in multiple tunnels | Ensure each hostname is routed to exactly one tunnel |