Complete Terminal-Based Remote Access with Docker
This guide enables SSH access to your server from anywhere using your terminal, with Cloudflare Tunnel running in Docker.
What you'll achieve: Run ssh myserver from any network and connect to your home/office server securely.
Architecture Overview
Security Benefits:
- No ports exposed on your router
- All traffic encrypted through Cloudflare
- Server IP hidden from the internet
- SSH key authentication (no passwords)
- Cloudflare Access authentication layer
Prerequisites
- Cloudflare account (free tier works)
- Domain name with DNS managed by Cloudflare
- Server: Ubuntu with Docker installed
- Client: macOS, Linux, or Windows machine
PART 1: SERVER SETUP
Run all commands in this section on your server.
Step 1: Install SSH Server
Ubuntu may not have SSH server installed by default.
# Update package list
sudo apt update
# Install OpenSSH server
sudo apt install openssh-server -y
# Start and enable it
sudo systemctl start ssh
sudo systemctl enable ssh
# Verify it's running
sudo systemctl status ssh
You should see: Active: active (running)
Verify it's listening on port 22:
sudo ss -tlnp | grep :22
Expected output:
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=xxxx,fd=3))
Step 2: Create Tunnel in Cloudflare Dashboard
- Open Cloudflare Zero Trust Dashboard
- Go to Networks → Tunnels
- Click Create a tunnel
- Select Cloudflared as connector type
- Name your tunnel (e.g.,
home-server) - Select Docker as your environment
- Cloudflare shows a command like:
docker run cloudflare/cloudflared:latest tunnel --no-autoupdate run --token eyJhIjoiNWFi...
-
Copy the token (the long string after
--token)
Step 3: Create Docker Compose Configuration
Create a directory for your tunnel:
mkdir -p ~/cloudflare-tunnel
cd ~/cloudflare-tunnel
Create the environment file:
nano .env
Add your token:
TUNNEL_TOKEN=eyJhIjoiNWFi...paste-your-full-token-here...
Save (Ctrl+O, Enter) and exit (Ctrl+X).
Secure the file:
chmod 600 .env
Create the Docker Compose file:
nano docker-compose.yml
Add this configuration:
services:
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared
restart: unless-stopped
command: tunnel --no-autoupdate run --token ${TUNNEL_TOKEN}
network_mode: host
Save and exit.
Important: network_mode: host is required so the container can access the host's SSH on port 22.
Step 4: Start the Tunnel
docker compose up -d
Verify it's running:
docker compose ps
Expected output:
NAME IMAGE STATUS
cloudflared cloudflare/cloudflared:latest Up X minutes
Check logs to confirm connection:
docker compose logs -f cloudflared
Look for: Registered tunnel connection or Connection registered
Press Ctrl+C to exit logs.
Step 5: Add SSH Public Hostname
Back in Cloudflare Dashboard:
- Go to Networks → Tunnels
- Click on your tunnel name (should show "HEALTHY")
- Click Public Hostname tab
- Click Add a public hostname
- Configure:
| Field | Value |
|---|---|
| Subdomain |
ssh (or any name you prefer) |
| Domain | Select your domain from dropdown |
| Type | SSH |
| URL | localhost:22 |
- Click Save hostname
Your SSH endpoint is now: ssh.yourdomain.com
Step 6: Create Access Application
This adds authentication before anyone can connect.
In Cloudflare Zero Trust Dashboard:
- Go to Access → Applications
- Click Add an application
- Select Self-hosted
- Configure:
| Field | Value |
|---|---|
| Application name | SSH Access |
| Session duration | 24 hours |
| Application domain | ssh.yourdomain.com |
- Click Next
Add Access Policy:
- Policy name:
Allow SSH - Action: Allow
- Add a rule:
- Selector: Emails
- Value:
your.email@example.com
- Click Next
- Leave advanced settings as default
- Click Add application
PART 2: CLIENT SETUP
Run all commands in this section on your client machine.
Step 7: Install SSH Client
macOS
SSH client is pre-installed. Verify:
ssh -V
Linux (Ubuntu/Debian)
sudo apt update
sudo apt install openssh-client -y
# Verify
ssh -V
Windows
Option 1: Via Settings
- Settings → Apps → Optional Features → Add a feature → OpenSSH Client → Install
Option 2: PowerShell (Admin)
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
Verify:
ssh -V
Step 8: Install cloudflared
macOS
brew install cloudflared
Linux (Ubuntu/Debian)
curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb
Windows
winget install --id Cloudflare.cloudflared
Or download from: https://github.com/cloudflare/cloudflared/releases/latest
Verify installation:
cloudflared --version
Step 9: Generate SSH Key
ssh-keygen -t ed25519 -C "your-email@example.com"
When prompted:
-
File location: Press Enter to accept default (
~/.ssh/id_ed25519) - Passphrase: Enter a password (recommended) or leave empty
This creates:
-
~/.ssh/id_ed25519— Private key (keep secret, never share) -
~/.ssh/id_ed25519.pub— Public key (goes to server)
Step 10: Configure SSH
Create/edit your SSH config:
macOS / Linux:
mkdir -p ~/.ssh
nano ~/.ssh/config
Windows (PowerShell):
mkdir -Force $env:USERPROFILE\.ssh
notepad $env:USERPROFILE\.ssh\config
Add this configuration:
Host myserver
HostName ssh.yourdomain.com
User your-linux-username
ProxyCommand cloudflared access ssh --hostname %h
IdentityFile ~/.ssh/id_ed25519
Replace:
-
myserver→ Any alias you want (e.g.,homelab,workserver) -
ssh.yourdomain.com→ Your subdomain from Step 5 -
your-linux-username→ Your username on the server
Save and exit.
Step 11: Copy Public Key to Server
First, connect to server with password:
ssh myserver
You'll go through:
- Cloudflare authentication (browser opens, enter email, get code)
- Linux password prompt
Once connected on the server, set up the authorized_keys:
# Create .ssh directory
mkdir -p ~/.ssh
chmod 700 ~/.ssh
# Open authorized_keys
nano ~/.ssh/authorized_keys
On your client (new terminal window), display your public key:
cat ~/.ssh/id_ed25519.pub
Copy the entire output (starts with ssh-ed25519 ...).
Paste it into the server's authorized_keys file, save and exit.
Set correct permissions:
chmod 600 ~/.ssh/authorized_keys
Exit the server:
exit
Step 12: Test Key Authentication
ssh myserver
This time it should:
- Go through Cloudflare authentication (if session expired)
- Connect directly using your SSH key — no password prompt!
Step 13: (Optional) Disable Password Authentication
Once key authentication works, disable passwords for better security.
On SERVER:
sudo nano /etc/ssh/sshd_config
Find and change (or add):
PasswordAuthentication no
PubkeyAuthentication yes
Restart SSH:
sudo systemctl restart ssh
Warning: Make sure key auth works before doing this, or you could lock yourself out!
Quick Reference
Server Commands
| Task | Command |
|---|---|
| Check tunnel status | docker compose ps |
| View tunnel logs | docker compose logs -f cloudflared |
| Restart tunnel | docker compose restart cloudflared |
| Stop tunnel | docker compose down |
| Start tunnel | docker compose up -d |
| Update cloudflared | docker compose pull && docker compose up -d |
| Check SSH status | sudo systemctl status ssh |
| Restart SSH | sudo systemctl restart ssh |
Client Commands
| Task | Command |
|---|---|
| Connect to server | ssh myserver |
| Connect with verbose | ssh -v myserver |
| Clear Cloudflare auth | cloudflared access logout |
| View public key | cat ~/.ssh/id_ed25519.pub |
File Structure Summary
Server
~/cloudflare-tunnel/
├── docker-compose.yml
└── .env # chmod 600
~/.ssh/
└── authorized_keys # chmod 600 (contains client's public key)
Client
~/.ssh/
├── config # SSH configuration
├── id_ed25519 # Private key (never share!)
└── id_ed25519.pub # Public key (copied to server)
Troubleshooting
Tunnel shows "INACTIVE" in Cloudflare dashboard
# On SERVER
docker compose ps
docker compose logs cloudflared
docker compose restart cloudflared
"Connection refused"
# On SERVER - Check SSH is running
sudo systemctl status ssh
# Check cloudflared is using host network
docker inspect cloudflared | grep NetworkMode
# Should show: "NetworkMode": "host"
"Permission denied" after setting up keys
# On SERVER - Check permissions
ls -la ~/.ssh/
# authorized_keys should be -rw------- (600)
# .ssh directory should be drwx------ (700)
# Fix if needed
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
Browser never opens for Cloudflare auth
# On CLIENT
cloudflared access login ssh.yourdomain.com
Session expired / keeps asking to authenticate
# On CLIENT
cloudflared access logout
ssh myserver
Verify everything is working
# On CLIENT - Connect with verbose output
ssh -v myserver
Complete Configuration Files
Server: docker-compose.yml
services:
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared
restart: unless-stopped
command: tunnel --no-autoupdate run --token ${TUNNEL_TOKEN}
network_mode: host
Server: .env
TUNNEL_TOKEN=eyJhIjoiNWFiY2...your-full-token-from-cloudflare...
Client: ~/.ssh/config
Host myserver
HostName ssh.yourdomain.com
User your-linux-username
ProxyCommand cloudflared access ssh --hostname %h
IdentityFile ~/.ssh/id_ed25519
Summary Checklist
Server Setup
- [ ] SSH server installed and running
- [ ] Cloudflare tunnel created in dashboard
- [ ] Docker compose file created with
network_mode: host - [ ] Tunnel running (
docker compose up -d) - [ ] Public hostname added (Type: SSH, URL: localhost:22)
- [ ] Access application created with email policy
- [ ] Client's public key added to
~/.ssh/authorized_keys
Client Setup
- [ ] SSH client installed
- [ ] cloudflared installed
- [ ] SSH key generated (
~/.ssh/id_ed25519) - [ ] SSH config created with ProxyCommand and IdentityFile
- [ ] Public key copied to server
Test
- [ ]
ssh myserverconnects successfully - [ ] No password prompt (key auth working)

Top comments (0)