3 min read · Ingress

Cloudflare Tunnel

Outbound-only ingress with cloudflared — no open ports.

The tunnel dials out to Cloudflare, so nothing needs to be exposed inbound. Pairs with Docker Swarm. For SSH and service tokens see Cloudflare Access.

Install

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 cloudflared

Or from the release package directly:

wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared-linux-amd64.deb

Create a tunnel

cloudflared tunnel login
cloudflared tunnel create github-bridge

Configure

/etc/cloudflared/config.yml — wildcard, sending everything to Nginx Proxy Manager:

tunnel: <TUNNEL_ID>
credentials-file: /etc/cloudflared/<TUNNEL_ID>.json

ingress:
  - hostname: "*.example.com"
    service: http://localhost:80

  # Mandatory catch-all, must be last
  - service: http_status:404

Per-subdomain instead:

ingress:
  - hostname: zaideih.example.com
    service: http://localhost:8080
  - hostname: myordbok.example.com
    service: http://localhost:9000
  - service: http_status:404

Route DNS and run

cloudflared tunnel ingress validate
cloudflared tunnel route dns github-bridge "*.example.com"

sudo cloudflared service install <TUNNEL_TOKEN>
cloudflared tunnel run --token <TUNNEL_TOKEN>
sudo systemctl restart cloudflared

The tunnel token grants full control of the tunnel. Keep it in a secret store, never in a config file that gets committed.

Once the tunnel works

sudo ufw delete allow ssh
sudo ufw status