Use UFW to lock down an Ubuntu server

Any server on the public internet is bound to be attacked by bots looking for weak or leaked passwords and unsafely configured services. Even security experts can misconfigure a database, or an unwitting member of the team can accidentally open up a vulnerability, leaving your devices or network open to attack.

If you have an existing server, you can view this bot traffic by running sudo less /var/log/auth.log. If your server is like many on the web, you’ll see lots of “invalid user admin” or “invalid user test”.

Tailscale simplifies network security by letting you keep your servers away from the public web, while keeping it easy to connect.

The best way to secure a server with Tailscale is to accept connections from Tailscale, and ignore any public internet traffic. Since your Tailscale network is invisible, except to those in your network, attackers won’t even be able to find it.

Prerequisites

Before you begin this guide, you’ll need an Ubuntu server to secure. This guide assumes you’re setting up a DigitalOcean Ubuntu 18.04 server, but the steps should be similar for most hosting providers and versions of Ubuntu.

You’ll also need a Tailscale network, known as a tailnet. For information about creating a tailnet, see the Tailscale quickstart.

Next, you’ll need to install the Tailscale client on your local machine and log in.

We’ll follow the same steps on the Ubuntu server next.

Step 1: ssh into your new Ubuntu server

After spinning up a new server, ssh into it with your account details.

ssh <username>@<server host ip>

Step 2: Install Tailscale on your Ubuntu server

  1. Install Tailscale using the one-line script below, or read our detailed install instructions for Ubuntu

    curl -fsSL https://tailscale.com/install.sh | sh
    
  2. Authenticate and connect your machine to your Tailscale network

    sudo tailscale up
    
  3. (Optional) If you signed in with a custom domain (not a @gmail.com address) visit the admin console and authorize your new endpoint.

  4. (Optional) Disable key expiry for this server

As a security feature, Tailscale requires periodic reauthentication. To prevent getting locked out, you may want to disable expiry on certain endpoints, such as this trusted server. Disable key expiry by following these instructions.

If you leave key expiry on, be familiar with how to regain server access. For example, DigitalOcean provides access via a droplet console.

Step 3: ssh over Tailscale

An important step — since we’re about to restrict ssh access to be only over Tailscale, we’ll exit the machine and re-ssh with our Tailscale IP.

First, find and copy your machine’s Tailscale IP. The easiest way to do this is to run

tailscale ip -4

And copy the 100.x.y.z shown.

Once you’ve found it, exit your ssh session, and start a new one with your newly copied Tailscale IP.

ssh <username>@<copied 100.x.y.z address>

Step 4: Enable UFW

For this guide, we’ll use UFW (Uncomplicated Firewall) to restrict non-Tailscale traffic to our server. It comes pre-installed on Ubuntu 18.04, so no installation is needed.

Before we continue editing rules, you’ll need to enable UFW if it isn’t already enabled.

sudo ufw enable

Step 5: Restrict all other traffic

Next, we’ll set up rules to reject all incoming non-Tailscale traffic, and allow all outgoing traffic by default.

sudo ufw default deny incoming
sudo ufw default allow outgoing

Now that we’ve set these defaults check your existing firewall rules you might need to keep.

sudo ufw status

You might see a list of firewall rules, like this:

To                          Action      From
--                          ------      ----
22/tcp                      ALLOW IN    Anywhere
80/tcp                      ALLOW IN    Anywhere
443/tcp                     ALLOW IN    Anywhere
Anywhere on tailscale0      ALLOW IN    Anywhere
22/tcp (v6)                 ALLOW IN    Anywhere (v6)
80/tcp (v6)                 ALLOW IN    Anywhere (v6)
443/tcp (v6)                ALLOW IN    Anywhere (v6)
Anywhere (v6) on tailscale0 ALLOW IN    Anywhere (v6)

All other connections are denied by default and so not listed above. We want to limit this list to the minimum set needed.

To completely lock down your server while retaining ssh access, you could delete every rule except for the “Anywhere on tailscale0” rule.

For the example above, we’ll delete all “22/tcp” rules, which will remove the ability to ssh over regular connections:

sudo ufw delete 22/tcp

Now, only “Anywhere on tailscale0” remains, meaning ssh can only occur over Tailscale.

To                          Action      From
--                          ------      ----
80/tcp                      ALLOW       Anywhere
443/tcp                     ALLOW       Anywhere
Anywhere on tailscale0      ALLOW IN    Anywhere
80/tcp (v6)                 ALLOW       Anywhere (v6)
443/tcp (v6)                ALLOW       Anywhere (v6)
Anywhere (v6) on tailscale0 ALLOW IN    Anywhere (v6)

If you expose a public web service (80/tcp, 443/tcp), you’ll want to keep those rules around. For less public services like FTP (21/tcp) or a database, consider connecting devices that rely on those services over Tailscale too.

This guide assumes ssh is running on the default port, port 22. If you’ve changed your ssh port, you may need to change these instructions as well.

Step 6: Restart ufw and ssh

Once you’ve set up firewall rules to restrict all non-Tailscale connections, restart ufw and ssh

sudo ufw reload
sudo service ssh restart

Done! Now your server will ignore any ssh requests that don’t come from users authenticated to your private Tailscale network.

Step 7: Test and verify

Let’s make sure that everything is working as expected.

First, let’s exit the existing ssh session.

Then, let’s try to connect with the public IP address from earlier.

You should see that we’re not able to connect, and the operation times out.

ssh <username>@<server host ip>
ssh: connect to host <server host ip> port 22: Operation timed out

Now, let’s try to ssh in using the Tailscale IP address (starting with 100.x.y.z) from earlier.

ssh <username>@<copied 100.x.y.z address>

We’re able to connect! Everything is working as expected. exit the ssh connection again.

This time, quit the Tailscale client on your local machine.

If you try to ssh to the Ubuntu server again, you’ll see that the operation now times out and we are no longer able to connect.

ssh <username>@<copied 100.x.y.z address>
ssh: connect to host <copied 100.x.y.z address> port 22: Operation timed out

We’ve now verified that we can only connect when we’re successfully authenticated to the Tailscale client running on our local machine.

Optional: enable multi-factor authentication (MFA) for all ssh connections

Now that your server can only be accessed via Tailscale, you can enforce login rules in using your Tailscale network’s identity provider, knowing they will apply to all your ssh connections too.

For example, you may want to configure your identity provider to require multi-factor authentication (MFA) for every sign-in.

Thanks to /u/mgozmovies whose experimentation and write-up on /r/tailscale inspired this article.