🛡️ Tutorial: Secure SSH Access over Cloudflare Tunnel (Docker Optional)
🛡️ Tutorial: Secure SSH Access over Cloudflare Tunnel (Docker Optional)
🎯 Objective
Set up a Cloudflare Tunnel to securely expose SSH access to your system without revealing your home IP or requiring port forwarding. This enables secure remote access even behind NAT, CGNAT, or dynamic IP environments.
This guide:
- Uses Cloudflare Tunnels to proxy traffic
- Does not expose your home IP address
- Uses Docker + Docker Compose for orchestration (optional)
- Can be adapted to run under systemd directly if preferred
🔧 Prerequisites
A domain managed via Cloudflare DNS
SSH server running on your machine (default port
22)Temporary access to the
cloudflaredCLI (for tunnel creation)Either:
- Docker and Docker Compose (used in this example), or
- A native
systemdservice (alternative not covered here)
🪜 Step-by-Step Instructions
1. Install and Authenticate cloudflared
Install cloudflared (temporary):
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 \
-o cloudflared && chmod +x cloudflared && sudo mv cloudflared /usr/local/bin/
Login with your Cloudflare account:
cloudflared tunnel login
This will open a browser and link the machine to your Cloudflare zone.
2. Create the Tunnel
Create a named tunnel:
cloudflared tunnel create ssh-tunnel
This creates a credential file, e.g.:
~/.cloudflared/5f84da12-e91b-4d2e-b4f0-7ca842f622f1.json
3. Define the Tunnel Routing Configuration
Create the tunnel config:
nano ~/.cloudflared/config.yml
Example:
tunnel: ssh-tunnel
credentials-file: /etc/cloudflared/5f84da12-e91b-4d2e-b4f0-7ca842f622f1.json
ingress:
- hostname: secure-ssh.example.com
service: ssh://localhost:22
- service: http_status:404
Then bind the hostname to the tunnel:
cloudflared tunnel route dns ssh-tunnel secure-ssh.example.com
Replace
secure-ssh.example.comwith your own subdomain under Cloudflare management.
4. Prepare File Permissions for Docker Use (Optional)
If using Docker, cloudflared runs as a non-root user (UID 65532), so grant it access to your config and credentials:
sudo chown 65532:65532 ~/.cloudflared
sudo chown 65532:65532 ~/.cloudflared/*
5. Define docker-compose.yml (Optional)
version: "3.8"
services:
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared-ssh-tunnel
restart: unless-stopped
volumes:
- ${HOME}/.cloudflared:/etc/cloudflared:ro
- ${HOME}/.cloudflared:/home/nonroot/.cloudflared:ro
command: tunnel run ssh-tunnel
network_mode: host
📝 Docker is used here for convenience and automation. You may alternatively run
cloudflared tunnel run ssh-tunneldirectly undersystemdor a background process.
6. Start the Tunnel
Start the container:
cd ~/docker/sshtunnel
docker compose up -d
docker logs -f cloudflared-ssh-tunnel
You should see Registered tunnel connection and other success logs.
7. Connect to the Tunnel from Remote Systems
Option A: Ad-hoc connection with cloudflared access tcp
cloudflared access tcp --hostname secure-ssh.example.com --url localhost:2222
In another terminal:
ssh -p 2222 youruser@localhost
Option B: Permanent SSH Configuration
Edit ~/.ssh/config:
Host secure-home
HostName secure-ssh.example.com
User youruser
IdentityFile ~/.ssh/id_rsa
ProxyCommand cloudflared access ssh --hostname %h
Then connect with:
ssh secure-home
✅ Result
- Secure SSH access via a public domain (e.g.,
secure-ssh.example.com) - No ports open to the public Internet
- IP address of your machine remains hidden from Cloudflare clients
- Easily extendable to expose other services in future
🔁 Optional Enhancements
- Run as a
systemdservice instead of Docker for lower overhead - Use
autosshorsystemdto maintain persistent reverse tunnels - Expand to forward additional ports (e.g., Bitcoin RPC, application APIs)
- Apply strict firewall rules to limit SSH access to
localhostonly