cloudflared is the command-line client for Cloudflare Tunnel. It creates encrypted tunnels between your origin server and Cloudflare's edge network, eliminating the need for public IP addresses or open firewall ports.
Installation
Linux (amd64)
# Download latest cloudflared
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64
sudo mv cloudflared-linux-amd64 /usr/local/bin/cloudflared
sudo chmod +x /usr/local/bin/cloudflared
# Verify
cloudflared versionDebian / Ubuntu (via apt)
# Add Cloudflare 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 any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install cloudflaredmacOS (Homebrew)
brew install cloudflaredDocker
docker run cloudflare/cloudflared --versionWindows (winget)
winget install --id Cloudflare.cloudflaredAuthentication
# Authenticate with Cloudflare account
cloudflared tunnel loginThis opens a browser window to authorize cloudflared. A certificate file (cert.pem) is saved to ~/.cloudflared/.
Tunnel Lifecycle
Create a Tunnel
cloudflared tunnel create <tunnel-name>Creates a tunnel and generates a credentials file (<tunnel-id>.json) in ~/.cloudflared/.
List Tunnels
cloudflared tunnel listTunnel Info
cloudflared tunnel info <tunnel-name>Delete a Tunnel
cloudflared tunnel delete <tunnel-name>Cleanup (force delete with DNS)
cloudflared tunnel cleanup <tunnel-name>
cloudflared tunnel delete -f <tunnel-name>DNS Configuration
Route DNS to Tunnel
# Route a subdomain
cloudflared tunnel route dns <tunnel-name> <subdomain.example.com>
# Route a wildcard
cloudflared tunnel route dns <tunnel-name> *.example.com
# Route the root domain
cloudflared tunnel route dns <tunnel-name> example.comRoute to Load Balancer
cloudflared tunnel route lb <tunnel-name> <lb-name> <lb-pool>List Routes
cloudflared tunnel route ip showConfiguration File (config.yml)
The configuration file lives at ~/.cloudflared/<tunnel-id>.json (credentials) and you create ~/.cloudflared/config.yml for tunnel settings.
Basic config.yml
tunnel: <tunnel-name>
credentials-file: /root/.cloudflared/<tunnel-id>.json
ingress:
- hostname: app.example.com
service: http://localhost:3000
- service: http_status:404Multiple Hostnames
tunnel: my-tunnel
credentials-file: /root/.cloudflared/<uuid>.json
ingress:
- hostname: blog.example.com
service: http://localhost:8080
- hostname: api.example.com
service: http://localhost:4000
- hostname: static.example.com
service: http://localhost:9000
- service: http_status:404With Path-Based Routing
tunnel: my-tunnel
credentials-file: /root/.cloudflared/<uuid>.json
ingress:
- hostname: app.example.com
path: /api/*
service: http://localhost:4000
- hostname: app.example.com
path: /*
service: http://localhost:3000
- service: http_status:404HTTPS Backend (self-signed)
tunnel: my-tunnel
credentials-file: /root/.cloudflared/<uuid>.json
ingress:
- hostname: app.example.com
service: https://localhost:443
originRequest:
noTLSVerify: true
- service: http_status:404SSH / TCP via Tunnel
tunnel: my-tunnel
credentials-file: /root/.cloudflared/<uuid>.json
ingress:
- hostname: ssh.example.com
service: ssh://localhost:22
- service: http_status:404RDP via Tunnel
tunnel: my-tunnel
credentials-file: /root/.cloudflared/<uuid>.json
ingress:
- hostname: rdp.example.com
service: tcp://localhost:3389
- service: http_status:404Note: TCP services (SSH, RDP) require Cloudflare Zero Trust and the Cloudflare WARP client or browser-based terminal to access.
Run Tunnel
Run (foreground)
cloudflared tunnel run <tunnel-name>Run with config file
cloudflared tunnel --config ~/.cloudflared/config.yml runRun as a Service (systemd)
# Install as a systemd service
sudo cloudflared service install
# Start service
sudo systemctl start cloudflared
# Enable at boot
sudo systemctl enable cloudflared
# Check status
sudo systemctl status cloudflared
# View logs
sudo journalctl -u cloudflared -fService Management
# Stop service
sudo systemctl stop cloudflared
# Restart service
sudo systemctl restart cloudflared
# Uninstall service
sudo cloudflared service uninstallRun as a Service (Docker)
# Create config directory
mkdir -p ~/.cloudflared
# Copy credentials file
cp /path/to/<tunnel-id>.json ~/.cloudflared/
# Create config.yml in ~/.cloudflared/
# Run Docker container
docker run -d \
--name cloudflare-tunnel \
--restart unless-stopped \
-v ~/.cloudflared:/etc/cloudflared \
cloudflare/cloudflared tunnel --config /etc/cloudflared/config.yml runQuick Tunnels (TryCloudflare)
Create a temporary tunnel without authentication — great for testing local servers.
# Expose local server on port 3000
cloudflared tunnel --url http://localhost:3000
# Expose with a specific port (no command)
cloudflared tunnel --url localhost:8080This generates a random <random>.trycloudflare.com URL that's publicly accessible until the process is stopped.
Limitations:
- No custom domains
- No load balancing
- No access policies
- Temporary (process must stay running)
- Rate-limited
Advanced Operations
Multiple Tunnels
Run multiple tunnels simultaneously for different services:
# Tunnel for web app
cloudflared tunnel run web-app
# Tunnel for API
cloudflared tunnel run api-gatewayTunnel with Metrics Server
tunnel: my-tunnel
credentials-file: /root/.cloudflared/<uuid>.json
metrics: 0.0.0.0:2000
ingress:
- hostname: app.example.com
service: http://localhost:3000
- service: http_status:404Graceful Shutdown
# Send SIGINT (Ctrl+C) for graceful shutdown
kill -SIGINT <cloudflared-pid>Environment Variables
# Set log level
export TUNNEL_LOGLEVEL=info # debug, info, warn, error
# Set metrics port
export TUNNEL_METRICS=0.0.0.0:2000
# Set retries
export TUNNEL_RETRIES=5
# Set transport protocol
export TUNNEL_TRANSPORT_PROTOCOL=auto # auto, http2, quicProtocol Options
tunnel: my-tunnel
credentials-file: /root/.cloudflared/<uuid>.json
protocol: quic # auto, http2, quic (auto is default)
# or via flag:
# cloudflared tunnel run --protocol quic my-tunnelQUIC is generally recommended for better performance and reliability.
Load Balancing
Create a Load Balancer Pool
# Through Cloudflare Dashboard or API
# Then route tunnel to it
cloudflared tunnel route lb <tunnel-name> <lb-name> <pool-name>Multiple Origins per Tunnel
tunnel: my-tunnel
credentials-file: /root/.cloudflared/<uuid>.json
ingress:
- hostname: app.example.com
service: http://localhost:3000
- hostname: app.example.com
service: http://localhost:3001
- service: http_status:404Monitoring & Logging
View Real-Time Logs
cloudflared tunnel run <tunnel-name> --loglevel debug
# Or with systemd
journalctl -u cloudflared -f -n 100Metrics Endpoint
Access http://localhost:2000/metrics (or your configured metrics port) to view Prometheus metrics including:
- Active connections
- Bytes transferred
- Error rates
- Tunnel uptime
Troubleshooting
Tunnel Not Connecting
# Verify authentication
cloudflared tunnel list
# Check DNS propagation
dig +short <subdomain.example.com>
# Verify tunnel credentials exist
ls -la ~/.cloudflared/*.json
# Run with debug logging
cloudflared tunnel run <tunnel-name> --loglevel debugCommon Errors
| Error | Solution |
|---|---|
failed to connect to Cloudflare | Check firewall / outbound connectivity (ports 7844, 80, 443) |
No DNS records found | Run cloudflared tunnel route dns to add DNS |
cert.pem not found | Run cloudflared tunnel login |
Tunnel not found | Verify tunnel name with cloudflared tunnel list |
Connection reset by peer | Network interruption — cloudflared auto-retries |
400 bad request | Check ingress rules in config.yml |
Failed to load config | Validate YAML syntax in config.yml |
Firewall / Network Requirements
Outbound connectivity required:
- Tunnel connections: TCP port 7844 (QUIC) or 80/443 (HTTP/2 fallback)
- API access: api.cloudflare.com (HTTPS)
- Metrics: Local port 2000 (optional, for monitoring)
Health Check
# Check tunnel status
cloudflared tunnel info <tunnel-name>
# Monitor through Cloudflare Dashboard
# Zero Trust > Networks > TunnelsUpgrade
# Linux (manual)
sudo cloudflared update
# Debian/Ubuntu
sudo apt-get update && sudo apt-get install --only-upgrade cloudflared
# macOS
brew upgrade cloudflared
# Docker
docker pull cloudflare/cloudflared
docker stop cloudflare-tunnel && docker rm cloudflare-tunnel
# Re-run with same configFull Example: End-to-End Setup
# 1. Install
sudo apt-get install cloudflared
# 2. Authenticate
cloudflared tunnel login
# 3. Create tunnel
cloudflared tunnel create my-first-tunnel
# Output: Created tunnel my-first-tunnel with id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# 4. Create config file
cat > ~/.cloudflared/config.yml << 'EOF'
tunnel: my-first-tunnel
credentials-file: /root/.cloudflared/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.json
ingress:
- hostname: app.example.com
service: http://localhost:3000
- service: http_status:404
EOF
# 5. Route DNS
cloudflared tunnel route dns my-first-tunnel app.example.com
# 6. Run
cloudflared tunnel run my-first-tunnel
# 7. (Optional) Install as service
sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflaredTunnel Migration
Move a Tunnel to a New Server
# On old server
# Copy credentials file
cat ~/.cloudflared/<tunnel-id>.json
# On new server
# Install cloudflared and authenticate
cloudflared tunnel login
# Create the credentials file manually
# Copy the JSON content from the old server
vim ~/.cloudflared/<tunnel-id>.json
# Copy config
scp user@old-server:~/.cloudflared/config.yml ~/.cloudflared/
# Update credentials-file path in config.yml if needed
# Run the tunnel
cloudflared tunnel run <tunnel-name>Note: The tunnel credentials file is tied to the tunnel, not the server. You can copy it to any machine.
Useful One-Liners
# Quick expose local development server
cloudflared tunnel --url http://localhost:3000
# Run tunnel with custom log level
cloudflared tunnel run --loglevel debug my-tunnel
# Check version and update availability
cloudflared version
# Show all available commands
cloudflared --help
# Run with specific protocol
cloudflared tunnel run --protocol quic my-tunnel
# Test config syntax (dry run)
cloudflared tunnel run --config path/to/config.yml my-tunnel