콘텐츠로 이동

Cloudflared Cheat Sheet

Overview

Cloudflared is the open-source daemon that powers Cloudflare Tunnel (formerly Argo Tunnel). It creates persistent, encrypted tunnels from your servers or local machines to Cloudflare’s global edge network, allowing you to expose services publicly without opening any inbound firewall ports or configuring NAT rules. The tunnel works by establishing outbound WebSocket connections from your server to Cloudflare’s nearest edge PoP, and all incoming traffic is proxied inward through this connection.

The security model is fundamentally different from traditional reverse proxies or VPNs. With cloudflared, your origin server never needs a public IP address or exposed ports — the connection is always initiated outbound from your infrastructure. All traffic passes through Cloudflare’s network, providing DDoS protection, WAF capabilities, caching, and Cloudflare Access (zero-trust identity-aware proxy) without additional configuration on your end. Cloudflare Access can gate any tunneled application behind Google, GitHub, Azure AD, or any OIDC/SAML identity provider, requiring authentication before any request reaches your origin.

Beyond HTTP/HTTPS tunneling, cloudflared supports TCP and UDP tunneling via cloudflare tunnel route tcp, private network routing (making entire subnets accessible to WARP-connected users), and SSH/RDP proxying. It integrates directly with systemd, Docker, Kubernetes, and can be deployed as a Kubernetes DaemonSet or deployment for highly available tunnel endpoints.

Installation

Linux (Official Package)

# Debian/Ubuntu — add Cloudflare repo
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \
  | sudo gpg --dearmor -o /usr/share/keyrings/cloudflare-main.gpg
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared jammy main' \
  | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install -y cloudflared

# RHEL/CentOS/Fedora
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.repo \
  | sudo tee /etc/yum.repos.d/cloudflare-main.repo
sudo dnf install -y cloudflared

# Verify installation
cloudflared --version

macOS

# Homebrew
brew install cloudflare/cloudflare/cloudflared

# Or download binary directly
curl -OL https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-amd64.tgz
tar -xzf cloudflared-darwin-amd64.tgz
sudo mv cloudflared /usr/local/bin/
cloudflared --version

Windows

# winget
winget install Cloudflare.cloudflared

# Chocolatey
choco install cloudflared

# Or download MSI from GitHub releases
# https://github.com/cloudflare/cloudflared/releases/latest

Docker

# Run cloudflared tunnel in Docker
docker run -d \
  --name cloudflared \
  --restart unless-stopped \
  cloudflare/cloudflared:latest tunnel \
  --no-autoupdate run --token YOUR_TUNNEL_TOKEN

# Docker Compose
cat > docker-compose.yml << 'EOF'
version: "3.9"
services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    command: tunnel --no-autoupdate run
    environment:
      - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
    networks:
      - app_network

  myapp:
    image: nginx:latest
    container_name: myapp
    networks:
      - app_network

networks:
  app_network:
EOF

Kubernetes

# Create secret with tunnel token
kubectl create secret generic cloudflared-secret \
  --from-literal=TUNNEL_TOKEN=YOUR_TUNNEL_TOKEN \
  -n cloudflared

# Deploy as Deployment
cat > cloudflared-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cloudflared
  namespace: cloudflared
spec:
  replicas: 2    # Run 2 connectors for HA
  selector:
    matchLabels:
      app: cloudflared
  template:
    metadata:
      labels:
        app: cloudflared
    spec:
      containers:
        - name: cloudflared
          image: cloudflare/cloudflared:latest
          args:
            - tunnel
            - --no-autoupdate
            - run
          env:
            - name: TUNNEL_TOKEN
              valueFrom:
                secretKeyRef:
                  name: cloudflared-secret
                  key: TUNNEL_TOKEN
EOF
kubectl apply -f cloudflared-deployment.yaml

Configuration

Authenticate cloudflared

# Log in to Cloudflare (opens browser)
cloudflared tunnel login

# This stores cert.pem in ~/.cloudflared/
ls ~/.cloudflared/
# cert.pem — your account credentials

Tunnel Configuration File (~/.cloudflared/config.yml)

# Tunnel UUID or name
tunnel: my-tunnel-name
# or: tunnel: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

# Path to tunnel credentials file
credentials-file: /home/user/.cloudflared/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.json

# Logging
logfile: /var/log/cloudflared.log
loglevel: info

# Ingress rules — processed top to bottom
ingress:
  # Route app.example.com to local port 3000
  - hostname: app.example.com
    service: http://localhost:3000

  # Route API traffic to different port
  - hostname: api.example.com
    service: http://localhost:8080
    originRequest:
      connectTimeout: 30s
      tlsTimeout: 30s
      tcpKeepAlive: 30s

  # SSH proxy (browser-based SSH via Cloudflare Access)
  - hostname: ssh.example.com
    service: ssh://localhost:22

  # HTTPS with custom origin cert verification
  - hostname: secure.example.com
    service: https://localhost:8443
    originRequest:
      noTLSVerify: true   # Skip origin cert verification

  # Catch-all — required as the last rule
  - service: http_status:404

Origin Request Options

originRequest:
  connectTimeout: 30s        # Timeout for TCP connection to origin
  tlsTimeout: 10s            # Timeout for TLS handshake
  tcpKeepAlive: 30s          # TCP keepalive interval
  keepAliveConnections: 100  # Max idle keepalive connections
  keepAliveTimeout: 90s      # How long to keep idle connections alive
  httpHostHeader: ""         # Override Host header sent to origin
  noTLSVerify: false         # Skip origin TLS cert verification
  disableChunkedEncoding: false
  proxyAddress: "127.0.0.1"  # Proxy bind address
  proxyPort: 0               # Proxy port (0 = auto)

Core Commands

CommandDescription
cloudflared tunnel loginAuthenticate to Cloudflare account
cloudflared tunnel create NAMECreate a new named tunnel
cloudflared tunnel listList all tunnels in account
cloudflared tunnel info NAMEShow tunnel details and connections
cloudflared tunnel run NAMERun a tunnel (using config.yml)
cloudflared tunnel run --token TOKENRun tunnel using a token (no config file)
cloudflared tunnel delete NAMEDelete a tunnel
cloudflared tunnel cleanup NAMERemove stale tunnel connections
cloudflared tunnel route dns NAME HOSTNAMECreate DNS CNAME record for tunnel
cloudflared tunnel route ip add CIDR NAMERoute CIDR through tunnel (private network)
cloudflared tunnel route ip showList IP routes through tunnels
cloudflared tunnel ingress validateValidate ingress rules in config
cloudflared tunnel ingress rule HOSTNAMETest which ingress rule matches hostname
cloudflared service installInstall as system service (systemd/launchd/Windows)
cloudflared service uninstallRemove system service
cloudflared proxy-dnsRun as DNS-over-HTTPS proxy
cloudflared access tcp --hostname HOST --url local:PORTOpen TCP tunnel (SSH proxy)
cloudflared access ssh --hostname HOSTOpen SSH session through Access
cloudflared versionShow version information
cloudflared helpShow help

Advanced Usage

Creating and Running a Tunnel

# 1. Create the tunnel
cloudflared tunnel create my-app

# Output: Created tunnel my-app with id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# Credentials file stored at: ~/.cloudflared/xxxxxxxx...json

# 2. Create config.yml
cat > ~/.cloudflared/config.yml << 'EOF'
tunnel: my-app
credentials-file: /home/user/.cloudflared/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.json

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

# 3. Create DNS record
cloudflared tunnel route dns my-app app.example.com

# 4. Run the tunnel
cloudflared tunnel run my-app

# 5. Or install as a system service
sudo cloudflared service install
sudo systemctl enable --now cloudflared
# Create tunnel via Cloudflare Dashboard
# Zero Trust > Networks > Tunnels > Create a tunnel
# Copy the tunnel token

# Run with token — no local config file needed
cloudflared tunnel run --token eyJhIjoixxxxxx...

# Install as service with token
cloudflared service install --token eyJhIjoixxxxxx...
sudo systemctl enable --now cloudflared

# In Docker
docker run -d \
  --name cloudflared \
  cloudflare/cloudflared:latest \
  tunnel --no-autoupdate run --token eyJhIjoixxxxxx...

Private Network Routing (WARP to Tunnel)

# Route a private CIDR through your tunnel
# Users must connect via Cloudflare WARP client

# Add IP route to tunnel
cloudflared tunnel route ip add 10.0.0.0/8 my-tunnel

# Or specific subnet
cloudflared tunnel route ip add 192.168.1.0/24 my-tunnel

# Show all routes
cloudflared tunnel route ip show

# Remove a route
cloudflared tunnel route ip delete 192.168.1.0/24

# Users install WARP client and log in to your Zero Trust organization
# Then access 192.168.1.x directly without a VPN config

SSH Access via Cloudflare Access

# In config.yml — expose SSH
ingress:
  - hostname: ssh.example.com
    service: ssh://localhost:22
  - service: http_status:404
# On the SSH server — configure the tunnel
cloudflared tunnel route dns my-tunnel ssh.example.com

# In Cloudflare Zero Trust dashboard:
# Access > Applications > Add Application > Self-hosted
# Configure identity provider policy for ssh.example.com

# On client machine — install cloudflared
# Add to ~/.ssh/config:
cat >> ~/.ssh/config << 'EOF'
Host ssh.example.com
    ProxyCommand cloudflared access ssh --hostname %h
    User ubuntu
EOF

# Now SSH as normal
ssh ssh.example.com

TCP Tunnel for Non-HTTP Services

# Expose a TCP service (e.g., RDP, database)
# In config.yml:
ingress:
  - hostname: rdp.example.com
    service: tcp://localhost:3389
  - service: http_status:404

# On client — create local proxy for TCP connection
cloudflared access tcp --hostname rdp.example.com --url localhost:3389

# Now connect your RDP client to localhost:3389
# Cloudflare Access gates the connection

Kubernetes Ingress with Cloudflare Tunnel

# cloudflared-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: cloudflared-config
  namespace: cloudflared
data:
  config.yaml: |
    tunnel: my-k8s-tunnel
    credentials-file: /etc/cloudflared/creds/credentials.json
    
    ingress:
      - hostname: app.example.com
        service: http://nginx-service.default.svc.cluster.local:80
      - hostname: api.example.com
        service: http://api-service.default.svc.cluster.local:8080
      - service: http_status:404
# Create credentials secret
kubectl create secret generic tunnel-credentials \
  --from-file=credentials.json=$HOME/.cloudflared/TUNNEL_UUID.json \
  -n cloudflared

High Availability Setup

# Run multiple cloudflared instances for HA
# Cloudflare automatically load balances across connectors

# On server 1
cloudflared tunnel run my-tunnel  # Establishes 2 connections

# On server 2 (same tunnel, same credentials)
cloudflared tunnel run my-tunnel  # Establishes 2 more connections

# Tunnel info shows all active connections
cloudflared tunnel info my-tunnel
# 4 connections from 2 servers = HA with automatic failover

# In Kubernetes: set replicas: 2 or 3 in Deployment

Monitoring and Diagnostics

# View tunnel status
cloudflared tunnel info my-tunnel

# Check active connections in Cloudflare dashboard
# Zero Trust > Networks > Tunnels > [tunnel name] > Overview

# Enable debug logging
cloudflared tunnel run --loglevel debug my-tunnel

# Check metrics endpoint (enabled by default on :2000)
curl http://localhost:2000/metrics

# Health check endpoint
curl http://localhost:2000/healthcheck

# View logs (systemd)
journalctl -u cloudflared -f

# View logs (Docker)
docker logs -f cloudflared

Common Workflows

Exposing a Local Development Server

# Quick tunnel without config file (temporary)
cloudflared tunnel --url http://localhost:3000

# Output:
# Your quick Tunnel has been created! Visit it at:
# https://random-name.trycloudflare.com

# No account required for quick tunnels!
# Great for demos, webhooks, and testing

# Named quick tunnel (requires login)
cloudflared tunnel --url http://localhost:8080 \
  --hostname dev.example.com

Migrating from Port Forwarding to Cloudflare Tunnel

# Before: exposed via router port forward + dynamic DNS
# Security risk: open ports, DDoS exposure

# After: close all inbound ports
sudo ufw delete allow 80/tcp
sudo ufw delete allow 443/tcp
sudo ufw delete allow 22/tcp

# Create tunnel
cloudflared tunnel create homeserver

cat > /etc/cloudflared/config.yml << 'EOF'
tunnel: homeserver
credentials-file: /root/.cloudflared/UUID.json

ingress:
  - hostname: home.example.com
    service: http://localhost:80
  - hostname: nas.example.com
    service: http://localhost:5000
  - hostname: ssh.example.com
    service: ssh://localhost:22
  - service: http_status:404
EOF

cloudflared tunnel route dns homeserver home.example.com
cloudflared tunnel route dns homeserver nas.example.com
cloudflared tunnel route dns homeserver ssh.example.com

sudo cloudflared service install
sudo systemctl enable --now cloudflared

Zero-Trust Access Policy Setup

# After exposing service via tunnel, configure Access policy
# in Cloudflare Zero Trust dashboard:
# 1. Access > Applications > Add Application
# 2. Choose "Self-hosted"
# 3. Set hostname: app.example.com
# 4. Configure identity provider (Google, GitHub, OKTA, etc.)
# 5. Define policy: require email matches @example.com

# Now all access to app.example.com requires authentication
# No username/password on the application itself needed

Tips and Best Practices

PracticeDetails
Use remote-managed tunnelsDashboard-managed tunnels are easier to configure and update without SSH
Run 2+ connectors for HADeploy cloudflared on multiple hosts for automatic failover
Token-based auth for serversUse --token flag in production — no need to copy credential files
Restrict origin to localhostBind your services to 127.0.0.1 only; tunnel handles all external access
Use Cloudflare AccessLayer zero-trust identity policies over every tunneled application
Monitor connector healthCheck /healthcheck endpoint or Cloudflare dashboard for connector status
Set noTLSVerify carefullyOnly disable origin TLS verification for internal services you control
Private network over split tunnelUse tunnel route ip for network-wide access instead of many individual tunnels
Pin cloudflared versionUse cloudflare/cloudflared:2024.x.x instead of :latest in production
Use --no-autoupdateIn containers, disable auto-update; control versions via image tags
DNS propagationAfter tunnel route dns, wait up to 60 seconds for DNS to propagate
Cleanup stale connectionsRun cloudflared tunnel cleanup NAME after ungraceful shutdowns