Overview
Cloudflare Tunnel (formerly Argo Tunnel) creates secure, outbound-only connections from your infrastructure to Cloudflare's edge network without opening inbound ports in your firewall. This guide demonstrates how to expose SSH access through Cloudflare Tunnel, eliminating the need for VPNs or port forwarding while maintaining security through Cloudflare's zero-trust network architecture.
Key Benefits:
- No inbound firewall rules required - connections are established outbound from your server
- Protection against DDoS attacks through Cloudflare's edge network
- Automatic certificate management and encryption via TLS
- Access control and audit logging capabilities
- No public IP exposure of your SSH service
Prerequisites:
- A Cloudflare account with an active domain
- Root or sudo access on the target server
- A domain or subdomain managed through Cloudflare DNS
Install Cloudflared
The cloudflared daemon is Cloudflare's lightweight connector that maintains persistent connections to Cloudflare's edge network. It runs on your server and proxies traffic between your local services and Cloudflare.
1curl -fsSL https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -o cloudflared.deb2sudo dpkg -i cloudflared.debNote: For other operating systems and architectures, visit the Cloudflared releases page. Available packages include RPM for Red Hat/Fedora, binaries for macOS (ARM/Intel), Windows, and various Linux architectures.
Authenticate Cloudflared
Authentication links your server's cloudflared instance to your Cloudflare account, granting it permission to create and manage tunnels under your domain. This step opens a browser window where you'll authorize the connection.
1cloudflared tunnel loginThis command generates a certificate file (cert.pem) stored in ~/.cloudflared/ that authenticates your tunnel operations.
Create a Tunnel
A tunnel is a secure pathway between your server and Cloudflare's network. Each tunnel is identified by a unique UUID and name. The creation process generates credentials that authenticate your tunnel's connection to Cloudflare's edge.
1cloudflared tunnel create <TUNNEL_NAME>Replace <TUNNEL_NAME> with a descriptive name (e.g., ssh-tunnel, home-server-ssh). This command creates a tunnel credentials JSON file in ~/.cloudflared/ containing your tunnel's UUID and authentication secret.
Example output:
Tunnel credentials written to /home/user/.cloudflared/abc12345-def6-7890-gh12-ijklm3456789.json
Created tunnel ssh-tunnel with ID abc12345-def6-7890-gh12-ijklm3456789
Configure the Tunnel
The configuration file defines how traffic is routed through your tunnel. It specifies which hostnames map to which local services using an ingress rules system. Rules are evaluated top-to-bottom, with the first matching rule handling the request.
1nano ~/.cloudflared/config.ymlAdd the following configuration
1tunnel: ssh-tunnel2credentials-file: /home/youruser/.cloudflared/<tunnel-uuid>.json3
4ingress:5 - hostname: ssh.example.com6 service: ssh://localhost:227 - service: http_status:404Configuration Explanation:
tunnel: The name of your tunnel (must match the name used during creation)credentials-file: Absolute path to the tunnel credentials JSON file created in the previous stepingress: Routing rules that define traffic handling- First rule: Routes traffic from
ssh.example.comto the local SSH daemon on port 22 - Last rule: Catch-all rule (required) that returns 404 for any unmatched traffic - this prevents unauthorized service exposure
- First rule: Routes traffic from
Important: Replace <tunnel-uuid> with your actual tunnel UUID from the credentials filename, and update youruser with your actual username. The service ssh://localhost:22 points to your local SSH server.
Copy the configuration to the system directory for the cloudflared service:
1sudo cp ~/.cloudflared/config.yml /etc/cloudflared/config.ymlAlso copy the credentials file to the system directory:
1sudo mkdir -p /etc/cloudflared2sudo cp ~/.cloudflared/<tunnel-uuid>.json /etc/cloudflared/Then update the config.yml credentials path to point to the system location:
1sudo nano /etc/cloudflared/config.ymlChange the credentials-file path to: /etc/cloudflared/<tunnel-uuid>.json
Route DNS
DNS routing creates a CNAME record in your Cloudflare DNS that points your subdomain to the tunnel. This tells Cloudflare's edge network to route traffic for this hostname through your specific tunnel.
1cloudflared tunnel route dns ssh-tunnel ssh.example.comThis command automatically creates a CNAME record in your Cloudflare DNS dashboard pointing ssh.example.com to <tunnel-uuid>.cfargotunnel.com. You can verify this in your Cloudflare DNS settings.
Behind the scenes: Cloudflare's edge servers receive requests for ssh.example.com, identify the associated tunnel via the CNAME, and forward the traffic through the secure tunnel to your server.
Install as a Service
Running cloudflared as a systemd service ensures automatic startup on boot and recovery from failures. The service runs with system privileges and maintains a persistent connection to Cloudflare's edge network.
1sudo cloudflared service install2sudo systemctl start cloudflared3sudo systemctl enable cloudflaredService management commands:
- Check status:
sudo systemctl status cloudflared - View logs:
sudo journalctl -u cloudflared -f - Restart:
sudo systemctl restart cloudflared
The service automatically establishes four redundant connections to different Cloudflare edge locations for high availability and load balancing.
Client Connection Methods
Connect via SSH
Method 1: Using Cloudflared Access Proxy
This method uses cloudflared on the client side to proxy your SSH connection through Cloudflare's network. No SSH configuration changes are required.
1cloudflared access ssh --hostname ssh.example.comNote: This command requires cloudflared to be installed on your client machine as well.
Method 2: Native SSH with ProxyCommand (Recommended)
For seamless integration with standard SSH workflows, configure SSH to automatically use cloudflared as a proxy. This allows you to use normal SSH commands like ssh ssh.example.com while the connection is transparently routed through Cloudflare.
First, ensure cloudflared is installed on your client machine, then configure SSH:
1nano ~/.ssh/configAdd the following configuration
1Host ssh.example.com2 ProxyCommand /usr/local/bin/cloudflared access ssh --hostname %h3 User yourlinuxuserConfiguration Parameters:
Host: The hostname pattern this configuration applies toProxyCommand: Command that SSH executes to establish the connection (cloudflared handles the proxying)%h: SSH variable that expands to the hostname you're connecting toUser: Your username on the remote server (optional, can be specified at connection time)
Usage:
After configuration, simply run: ssh ssh.example.com
SSH will automatically invoke cloudflared to establish the secure tunnel connection.
Security Considerations
Encryption: All traffic through Cloudflare Tunnel is encrypted end-to-end with TLS. SSH provides an additional layer of encryption, creating double encryption for your connection.
Authentication: You can enhance security by enabling Cloudflare Access policies, requiring additional authentication (email OTP, SSO, etc.) before allowing SSH connections.
Firewall: Since the tunnel establishes outbound connections only, your SSH service never needs to be exposed to the internet. Your firewall can block all inbound SSH connections while maintaining remote access capability.
Audit Logging: Cloudflare Tunnel logs connection attempts and traffic patterns, providing visibility into access patterns and potential security events.
Troubleshooting
Connection Issues:
- Verify the cloudflared service is running:
sudo systemctl status cloudflared - Check DNS propagation:
nslookup ssh.example.com - Review logs for errors:
sudo journalctl -u cloudflared -n 50
Common Problems:
- 404 errors: DNS may not be propagated yet (wait 5-10 minutes), or the hostname doesn't match the ingress rule
- Connection refused: SSH service may not be running on the target server
- Certificate errors: Ensure the credentials file path in config.yml is correct and accessible