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
sshfor remote access. This post explores how to configure tunnels and routes to allowsshtraffic 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
sshvia aProxyCommand. Forwards yoursshdata 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 localsshdaemon.
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
IdentityAgentline tells the system where to look for passwords and ssh keys. Password managers like 1Password and BitWarden can storesshkeys. - The
Userdirective tells ssh which user to log in with. - The
IdentitiesOnlydirective indicates we don’t wantsshto try all identities (this will cause the login to fail) - When using a password manager for
sshkeys, both the public and private keys are stored. TheIdentityFiledirective gives apps that understand~/.ssh/configa hint at which saved key to use. ProxyCommandexecute the cloudflared connect. The--hostname %hdirective 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 likessh-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
ProxyCommandto map to an existing server andUserdirective 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!


