Skip to content

Cloudflare Zero Trust

Overview

Cloudflare Zero Trust tunnels provide secure SSH access without exposing port 22 to the internet.

Why Zero Trust Tunnel?

Benefit Description
No open ports Port 22 can be closed to the internet
Geo-blocking Built into Cloudflare Access policies (US only)
Identity Can require authentication before SSH
Free Up to 50 users on free tier

Architecture

graph LR
    CLIENT[Client<br/>luna] --> |cloudflared| CF[Cloudflare Edge]
    CF --> |Zero Trust Tunnel| SERVER[Server<br/>pluto/dumbo]

    subgraph "No Port 22 Needed"
        SERVER
    end

Deployed Tunnels

Host Hostname Status
pluto ssh-pluto.scandora.net ✅ Active
dumbo ssh-dumbo.scandora.net Planned

Client Setup

Install cloudflared

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

SSH Config

Add to ~/.ssh/config:

Host ssh-pluto.scandora.net
    ProxyCommand /opt/homebrew/bin/cloudflared access ssh --hostname %h
    User joe

Host ssh-dumbo.scandora.net
    ProxyCommand /opt/homebrew/bin/cloudflared access ssh --hostname %h
    User joe

Connect

ssh ssh-pluto.scandora.net

On first connect, a browser window opens for authentication.

Server Setup

Step 1: Enable Zero Trust (One-time)

  1. Go to https://one.dash.cloudflare.com
  2. Create organization if prompted
  3. Note your team domain (e.g., scandora.cloudflareaccess.com)

Step 2: Create Tunnel

  1. Go to Networks > Tunnels
  2. Click Create a tunnel
  3. Choose Cloudflared as connector type
  4. Name it (e.g., pluto-tunnel or dumbo-tunnel)
  5. Copy the tunnel token (long string starting with eyJ...)
  6. Save token to 1Password as "Cloudflare Tunnel Token - "

Step 3: Configure Tunnel Route

In the tunnel configuration:

  1. Add a Public Hostname:
  2. Subdomain: ssh-pluto (or ssh-dumbo)
  3. Domain: scandora.net
  4. Service Type: SSH
  5. URL: localhost:22

Step 4: Create Access Policy

  1. Go to Access > Applications
  2. Click Add an application > Self-hosted
  3. Configure:
  4. Name: SSH - pluto
  5. Subdomain: ssh-pluto.scandora.net
  6. Accept all available identity providers
  7. Add policy:
  8. Name: US Only
  9. Action: Allow
  10. Include: Country = United States

Step 5: Deploy with Ansible

# Get token from 1Password
TOKEN=$(op item get "Cloudflare Tunnel Token - pluto" --fields credential --reveal)

# Deploy
ansible-playbook -i inventory/production.yml playbooks/site.yml \
  --tags cloudflared \
  -e cloudflared_tunnel_token="$TOKEN"

Step 6: Close Port 22 (Optional)

Once tunnel is working, close port 22 in:

  • AWS Security Group (pluto)
  • GCP Firewall Rules (dumbo)

Keep ZeroTier Access

Keep port 22 open from ZeroTier (192.168.194.0/24) as backup.

Ansible Role

The cloudflared role at cloud/ansible/roles/cloudflared/ handles:

  • Installing cloudflared
  • Configuring systemd service
  • Setting up tunnel token

Variables

Variable Default Description
cloudflared_tunnel_token "" Required - Token from Cloudflare dashboard
cloudflared_service_enabled true Enable systemd service

Troubleshooting

Check Tunnel Status

# On server
sudo systemctl status cloudflared

# View logs
sudo journalctl -u cloudflared -f

# Test tunnel
cloudflared tunnel info

Client Issues

# Test ProxyCommand manually
cloudflared access ssh --hostname ssh-pluto.scandora.net

# Verify cloudflared is installed
which cloudflared
cloudflared --version

Authentication Loop

If browser auth keeps looping:

  1. Clear browser cookies for cloudflareaccess.com
  2. Try incognito/private window
  3. Check Access policy in Cloudflare dashboard

Security Considerations

  • Tokens are secrets: Store in 1Password, pass via Ansible --extra-vars
  • Geo-blocking: Access policies can restrict by country
  • Audit logs: All connections logged in Cloudflare dashboard
  • Session duration: Configure in Access policies