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¶
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¶
On first connect, a browser window opens for authentication.
Server Setup¶
Step 1: Enable Zero Trust (One-time)¶
- Go to https://one.dash.cloudflare.com
- Create organization if prompted
- Note your team domain (e.g.,
scandora.cloudflareaccess.com)
Step 2: Create Tunnel¶
- Go to Networks > Tunnels
- Click Create a tunnel
- Choose Cloudflared as connector type
- Name it (e.g.,
pluto-tunnelordumbo-tunnel) - Copy the tunnel token (long string starting with
eyJ...) - Save token to 1Password as "Cloudflare Tunnel Token -
"
Step 3: Configure Tunnel Route¶
In the tunnel configuration:
- Add a Public Hostname:
- Subdomain:
ssh-pluto(orssh-dumbo) - Domain:
scandora.net - Service Type:
SSH - URL:
localhost:22
Step 4: Create Access Policy¶
- Go to Access > Applications
- Click Add an application > Self-hosted
- Configure:
- Name:
SSH - pluto - Subdomain:
ssh-pluto.scandora.net - Accept all available identity providers
- Add policy:
- Name:
US Only - Action:
Allow - 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:
- Clear browser cookies for cloudflareaccess.com
- Try incognito/private window
- 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