Restrict external SSH access using Tailscale and UFW in Ubuntu

The task is to block all non-SSH connections. My motivation for this was that when I checked /var/log/auth.log, there were thousands of requests from bots trying to crack SSH passwords. I just didn’t want that. And, as we already use Tailscale at HYVOR for SSH, why not just block all other SSH connections.

This blog post is quite the same as Tailscale’s Use UFW to lock down an Ubuntu server, but I’ve included a few nuances I found along the way. Especially, some precautions to prevent you from getting locked out of your server from the firewall. Tailscale’s guide weirdly (as of today) doesn’t have any mention of enabling Tailscale connections via UFW.

Before getting started, make sure you have installed Tailscale on your server and have enabled SSH.

Setting up UFW

UFW, Uncomplicated Firewall, is a tool to configure a firewall. If this is the first time using it, I recommend quickly checking out DigitalOcean’s UFW guide to get the basic idea.

First, log into your server via Tailscale.

1ssh user@tailscale-ip

Reset ufw to its initial state (in case it was changed):

1sudo ufw default deny incoming
2sudo ufw default allow outgoing

Those two are the rules that run when no other rules match.

  • Block all incoming connections

  • Allow any outgoing connection

List all the apps that are registered with UFW. Many applications that depend on network connections will be registered here. For example, OpenSSH, NGINX, Apache, etc.

1sudo ufw app list
2 OpenSSH

These are probably services you have installed (ex: NGINX for web server). I recommend enabling ALL OF THEM, including OpenSSH.

1sudo ufw enable OpenSSH
2sudo ufw enable NGINX

In addition, enable incoming connections to port 22 on Tailscale:

1sudo ufw allow in on tailscale0 to any port 22

Once that’s done, enable UFW.

1sudo ufw enable

Verify everything

Before we block external SSH connections, open two new terminals.

1ssh root@public-ip
2ssh root@tailscale-ip

Make sure you can access your server both ways.

Once you confirm, close those two new terminals and go back to the first terminal, where you are logged in to the server via Tailscale.

Disable Non-Tailscale SSH access

Warning! Before you continue, make sure that your hosting provider gives a way to connect to your SSH server in case you lose access to it due to firewall rules. In our case, Hetzner provides a Console for us to log in from the browser without SSH.

Let’s block non-tailscale incoming SSH connections. List the all rules, numbered.

1sudo ufw status numbered
1Status: active
3 To Action From
4 -- ------ ----
5[ 1] 80 ALLOW IN Anywhere
6[ 2] 443 ALLOW IN Anywhere
7[ 3] 22 on tailscale0 ALLOW IN Anywhere
8[ 4] OpenSSH ALLOW IN Anywhere
9[ 5] 80 (v6) ALLOW IN Anywhere (v6)
10[ 6] 443 (v6) ALLOW IN Anywhere (v6)
11[ 7] 22 (v6) on tailscale0 ALLOW IN Anywhere (v6)
12[ 8] OpenSSH (v6) ALLOW IN Anywhere (v6)

Remove the rules that say OpenSSH (or port 22 without “tailscale”)

1sudo ufw delete 4
2sudo ufw delete 8

Finally, reload UFW and SSH services.

1sudo ufw reload
2sudo service ssh restart

Again, open two terminals. Try to SSH using public and tailscale IP addresses. The public one should fail, and the tailscale one should connect.

Feel free to discuss pros/cons below.