Securing Your SSH Connection Using Cloudflare

Cloudflare Protects Your Website. What About SSH?

If you run a website, chances are you’ve already put it behind Cloudflare. Flip the DNS record to “proxied,” and suddenly you’ve got DDoS mitigation, a web application firewall, and your server’s real IP hidden from the internet. All for free, in about five minutes.

However, if you try to use a secure shell (ssh) to connect you your server, you will find it inaccessible.

Cloudflare’s orange cloud only proxies HTTP and HTTPS traffic, which carries web pages. ssh is a different protocol on a different port, so the standard proxy connection won’t work.

You might be tempted to leave proxy protection off for ssh and avoid the extra hassle. Not a good idea.

Any internet-facing ssh port gets probed constantly. Automated bots try default credentials and known key patterns, 24/7, from the moment the IP becomes reachable. Security software only might only act after the fact. The better goal: don’t let that traffic reach the server at all.

So what’s the equivalent protection for ssh, a secure shell protocol for remote network access? How do I securely use SSH with Cloudflare, and still remotely access my website?

Cloudflare is the goto for providing a fairly good level of website security for free… but Cloudflare covers a whole lot more that the average website owner typically uses.

If you have an advanced website, you might use ssh for remote access. This post explores how to configure tunnels and routes to allow ssh traffic without compromising security.

Less Desirable Security Options

So what options do you have to secure ssh?

  • IP whitelisting: you might be able to grant your IP access, which is great until it changes; very likely on residential internet or “free” wifi.
  • VPN software: requires additional infrastructure (hardware and/or software) that needs maintenance.
  • Cloudflare: offers a paid add-on to allow other protocols and ports access, but it’s overkill for ssh. It also potentially routes all your internet traffic through Cloudflare, which might not be desirable.

The Actual Fix: a Cloudflare Tunnel

You can create a Cloudflare Tunnel using a free software tool called cloudflared. When running on the server cloudflared opens an outbound connection. Nothing is exposed, and as far as the internet is concerned, the ssh port (port 22) doesn’t exist.

On your laptop, a matching cloudflared invocation tunnels your ssh connection through Cloudflare’s network to that outbound link, and out the other side to your server’s real ssh daemon. Two small programs, two different machines, one job each:

  • Client (your laptop): invoked automatically by ssh via a ProxyCommand. Forwards your ssh data to Cloudflare’s edge.
  • Server (the box you’re protecting): a persistent background service. Receives traffic from Cloudflare’s edge and hands it to 127.0.0.1:22, its own local ssh daemon.

If you’ve used ssh before, your authentication process doesn’t change. You’re just removing the open door and replacing it with one that only opens from the inside.

Walkthrough

Throughout the walkthrough, I’m using a fictional setup — a company called Acme Widgets, its site acmewidgets.dev, a client project subdomain, and a second unrelated domain acmenonprofit.org on the same Cloudflare account. None of it is real, but every problem it runs into is one I actually hit. For these examples, the server is Debian-based and the laptop is macOS.

Server Install

We will be configuring our tunnel and routes on the Cloudflare dashboard. However, the tunnel creation process checks for an active connector before you can add routes.

To get started, you will need to install cloudflared on your server.

sudo mkdir -p --mode=0755 /usr/share/keyrings
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 any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install -y cloudflared

Verify that it is installed:

cloudflared --version

Create and Connect Tunnel

The dashboard flow and the server setup are interleaved by design. You can’t fully finish one before starting the other. Do these in order.

Start Creating The Tunnel

In the Cloudflare dashboard: Networking > Tunnels > Create a Tunnel > name the tunnel acmewidgets-tunnel > Create Tunnel.

Install and Run

After you click Create Tunnel, the dashboard shows you the tunnel id of the new tunnel, as well as instructions for installing and configuring the cloudflared client for multiple operating systems. Grab the token from one of these commands. We will use the token to create a systemd service on our server. The page will not allow you to proceed until it detects the tunnel on the server.

The ACMEWIDGETS_TOKEN is a placeholder in these examples. Tokens grant access to an account, so these examples don’t show an actual alphanumeric token.

We are going to create one tunnel and route for each domain. That means we need to create one systemd install per tunnel. Let’s create our acmewidgets.dev tunnel at /etc/systemd/system/cloudflared-acmewidgets.service:

[Unit]
Description=Cloudflare Tunnel - acmewidgets.dev
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
ExecStart=/usr/bin/cloudflared --no-autoupdate tunnel run --token ACMEWIDGETS_TOKEN
Restart=on-failure
RestartSec=5
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target

To install the service, run the following commands:

sudo systemctl daemon-reload
sudo systemctl enable --now cloudflared-acmewidgets
sudo systemctl status cloudflared-acmewidgets

Once this service is up and running on the server, the dashboard will show an active connection and let you proceed.

Add The Route

On the Routes tab, click Add Route.> choose Public Application. Set the subdomain to ssh, and select the domain from the dropdown list. To connect the route to sshd (the ssh server), enter a Service URL of ssh://127.0.0.1:22.

Repeat As Necessary

For each tunnel that you want to create on Cloudflare, you also need a service listening on the server. You can generally duplicate the service file we created previously, changing the file name and adding the new tunnel token. Run the service installation again with the updated service file.

Setting Up Your Notebook

I am assuming you are already familiar with ssh and generating keys for remote access. If you are using a password manager like 1Password or Bitwarden, your ssh keys may be contained 100% in the password manager. If this is the case, copy your public key into a file (e.g. id_acmewidgets.pub) and place it in ~./ssh.

SSH Configuration

Configuring ssh to connect to different servers is done through your ssh config file, usually found at ~/.ssh/config.

Host *
  IdentityAgent ~/Library/Containers/com.bitwarden.desktop/Data/.bitwarden-ssh-agent.sock

Host ssh.acmewidgets.dev
  ProxyCommand /opt/homebrew/bin/cloudflared access ssh --hostname %h
  User acme-username
  IdentitiesOnly yes
  IdentityFile ~/.ssh/id_acmewidgets.pub

Host ssh.portal.acmewidgets.dev
  ProxyCommand /opt/homebrew/bin/cloudflared access ssh --hostname ssh.acmewidgets.dev
  User acme-portal-user
  IdentitiesOnly yes
  IdentityFile ~/.ssh/id_widgetsportal.pub

Host ssh.acmenonprofit.org
  ProxyCommand /opt/homebrew/bin/cloudflared access ssh --hostname %h
  User acme-nonprofit-user
  IdentitiesOnly yes
  IdentityFile ~/.ssh/id_acmenonprofit.pub
  

Let’s break this config down:

  • The IdentityAgent line tells the system where to look for passwords and ssh keys. Password managers like 1Password and BitWarden can store ssh keys.
  • The User directive tells ssh which user to log in with.
  • The IdentitiesOnly directive indicates we don’t want ssh to try all identities (this will cause the login to fail)
  • When using a password manager for ssh keys, both the public and private keys are stored. The IdentityFile directive gives apps that understand ~/.ssh/config a hint at which saved key to use.
  • ProxyCommand execute the cloudflared connect. The --hostname %h directive passes in the host name.

Take note of the portal.acmewidgets.dev URL. The --hostname directive is not using a placeholder; it has a specific URL. Why?

SSL certificates are used to provide encryption between a website and the web browser, keeping everything safe. You can get free SSL certificates through Cloudflare, but they only apply to one sub domain level. That means a URL like portal.acmewidgets.dev is covered, but something like ssh.portal.acmewidgets.dev is not. Cloudflare does offer a paid advanced certificate manager, but we can use a few workarounds here.

  • Create an additional tunnel route using another Public Application. The Service URL is once again ssh://1127.0.0.1:22, but we can set a subdomain like ssh-portal. This remains human-readable and still falls under the one subdomain level covered by the SSL certificate.
  • On a VPS, different domain names may point to the same server. In that case, what you have access to is controlled by your login. This means you can reuse a tunnel, use ProxyCommand to map to an existing server and User directive to specify a different user.

Getting on the Guest List

We’ve set up a tunnel and route that essentially opens a secret door from the inside. The ~/.ssh/config file gives us special instructions on finding the hidden door. Now we need to get your laptop on the guest list!

Each server has an authorized_keys file located in ~/.ssh. These keys are the public keys from laptop. Connect to the server and copy your ssh public keys on to the server, and then add them to the authorized_keys file:

sudo -u <username> -i
mkdir -p ~/.ssh
chmod 700 ~/.ssh
cat /path/to/<publickey.pub> >> /home/<username>/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
exit

When you connect to the server, the ssh key that gets shared is checked against the authorized_keys, and if you’re on the list, your ssh connection succeeds.

Upon your first successful connection, your laptop will ask to save a host key to ~/.ssh/known_hosts. This helps to ensure you are always connecting to the same host or server. If something is changed on the server, either by you or someone malicious, you will get a big REMOTE HOST IDENTIFICATION HAS CHANGED warning. You can remove the host key from ~/.ssh/known_hosts file by running the following in a terminal window:

ssh-keygen -R <hostname>

Then you can attempt to connect as before and accept the host key when prompted again.

Copy The Configuration

We did a lot of work to set all this up. It’s reasonable to ask if there’s an easy way to replicate this on all the computers you use to remotely access your server. The quick answer is yes!

A big part of this is using a password manager that manages your ssh keys. These have a public and private component, and keeping them in a password manager is another layer of protection. The password manager will sync across devices, so the files you need to duplicate are minimal.

  • Install cloudflared.
  • Confirm your password manager is syncing keys.
  • Copy/recreate the keys/config files in ~/.ssh.
  • Accept new host key when prompted; automatically saved in known_hosts.

It’s simple and easy to duplicate this information between computers. The public keys aren’t an issue if disclosed, and everything else is easily copied.

Where This Leaves You

No open ssh port anywhere. A service that survives reboots without you thinking about it. Your existing keys and login flow, untouched. If you’re using a password manager with a built-in ssh agent like 1Password and Bitwarden, the both support this now. The same keys are available on every machine you own without ever copying a private key file around: new laptop, ten minutes of setup, done.

Cloudflare gets a lot of attention for what it does to a browser request. It’s worth remembering it can do almost as much for the connection you use to actually manage the server underneath.

In the next post in this series, we’re going to explore how to do this on an iPad and leave the laptop at home!

bradley rowley

Bradley Rowley

Bradley is the founder of Ascendant Bits Creative Digital and a 30-year tech veteran dedicated to helping holistic and spiritual entrepreneurs build a digital presence that feels as good as their helping and healing work.

If you're ready to stop wrestling with technology, the best first step is always a simple conversation. You can book a free, no-pressure discovery call to explore your vision.