🛡️ Tutorial: Secure SSH Access over Cloudflare Tunnel (Docker Optional)

🛡️ 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 cloudflared CLI (for tunnel creation)

  • Either:

    • Docker and Docker Compose (used in this example), or
    • A native systemd service (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.com with 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-tunnel directly under systemd or 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 systemd service instead of Docker for lower overhead
  • Use autossh or systemd to maintain persistent reverse tunnels
  • Expand to forward additional ports (e.g., Bitcoin RPC, application APIs)
  • Apply strict firewall rules to limit SSH access to localhost only
This post and comments are published on Nostr.