Cloudflare Zero Trust + Tunnel
This guide configures SSH access through Cloudflare Zero Trust and cloudflared so your server does not expose public SSH on 22/tcp.
Goal and outcome
- SSH is reachable only through Cloudflare identity checks.
- Server SSH listens on localhost (
127.0.0.1) instead of public interfaces. - UFW does not allow inbound
22/tcpfrom the internet.
Prerequisites
- Ubuntu VPS with
sudoaccess. - Domain managed in Cloudflare.
- Cloudflare Zero Trust account.
- Local machine with
sshclient. - Console/break-glass access from VPS provider in case of lockout.
Access model
Admin laptop
-> Cloudflare Access (identity policy)
-> Cloudflare Tunnel (cloudflared)
-> localhost SSH on VPS (127.0.0.1:22)
Step 1: Create an admin user and SSH key login
Create user and sudo access:
sudo adduser admin
sudo usermod -aG sudo admin
From your local machine, generate and copy key:
ssh-keygen -t ed25519 -C "admin@yourdomain.com"
ssh-copy-id admin@YOUR_SERVER_IP
Step 2: Harden SSH and bind to localhost
Backup config:
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%F-%H%M%S)
Edit config:
sudo nano /etc/ssh/sshd_config
Set minimum required directives:
Port 22
ListenAddress 127.0.0.1
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
AuthenticationMethods publickey
Validate and reload:
sudo sshd -t && sudo systemctl restart ssh
sudo systemctl status ssh --no-pager
Step 3: Configure UFW
Do not open SSH publicly.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
Confirm there is no 22/tcp allow rule from internet.
Step 4: Install cloudflared
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 jammy main" | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update
sudo apt install -y cloudflared
cloudflared --version
If using Ubuntu 24.04, replace jammy with noble.
Step 5: Create tunnel and route DNS
Authenticate:
cloudflared tunnel login
Create tunnel:
cloudflared tunnel create ssh-tunnel
Map DNS hostname:
cloudflared tunnel route dns ssh-tunnel ssh.yourdomain.com
Step 6: Configure tunnel ingress for SSH
Create config file:
sudo mkdir -p /etc/cloudflared
sudo nano /etc/cloudflared/config.yml
Example config:
tunnel: ssh-tunnel
credentials-file: /home/admin/.cloudflared/<TUNNEL_ID>.json
ingress:
- hostname: ssh.yourdomain.com
service: ssh://127.0.0.1:22
- service: http_status:404
Install and start service:
sudo cloudflared service install
sudo systemctl enable --now cloudflared
sudo systemctl status cloudflared --no-pager
Step 7: Add Zero Trust Access policy
In Cloudflare Zero Trust dashboard:
- Create self-hosted application for
ssh.yourdomain.com. - Add Access policy (allow your identity/email group).
- Use OTP or your selected IdP.
Step 8: Client connection methods
Method A: cloudflared access ssh
cloudflared access ssh --hostname ssh.yourdomain.com
Method B: SSH ProxyCommand
Add to local ~/.ssh/config:
Host vps-cf
HostName ssh.yourdomain.com
User admin
ProxyCommand cloudflared access ssh --hostname %h
Connect:
ssh vps-cf
Verification checklist
ss -tulpen | grep :22shows SSH bound to127.0.0.1:22.- UFW does not expose
22/tcppublicly. systemctl status cloudflaredis active.- SSH works through Cloudflare Access flow.
Troubleshooting
Tunnel not connecting
sudo journalctl -u cloudflared -n 100 --no-pager
SSH denied after auth
- Check
sshd_configand key permissions. - Verify Access policy includes your user/group.
Lockout risk
- Keep one active root/console session while testing.
- Restore backup if needed:
sudo cp /etc/ssh/sshd_config.bak.* /etc/ssh/sshd_config
sudo systemctl restart ssh