← Back to GuidesGUIDESelf-Hosting & Homelab

Put SSH Behind Tailscale and Close Port 22

26 minutes ago
❤️ 0 likes
💬 0 comments
self-hostingcode-securityvpstailscalesshlinux
Put SSH Behind Tailscale and Close Port 22

Once a VPS is hardened the usual way, keys only, firewalled, patched, there is a bigger move you can make: stop exposing SSH to the public internet at all. Instead of trusting that a strong key holds up against constant scanning, you put SSH on a private network the rest of the world cannot even see, and close port 22 to everyone else. This is my favorite upgrade for a small server in 2026, and it is genuinely less fragile than it sounds, as long as you keep one escape hatch.

This guide picks up where Setting Up Your Own VPS leaves off. If you have not done the baseline (non-root user, SSH keys, UFW), start there first.

Tip

Key takeaways Tailscale gives your server a private address that only your own devices can reach.Disable key expiry on the server in the admin console, or Tailscale logs you out in 180 days and you cannot reauth remotely.Allow the tailscale0 interface in UFW, then remove the public port 22 rule.Always test a new connection over Tailscale before you close the old one.Keep your provider's browser console handy as a recovery path.

What "behind Tailscale" actually means

Tailscale builds a private mesh network (a "tailnet") between your machines using WireGuard. Every device you add gets a stable 100.x address that is reachable only by your other devices, never from the open internet. Put your server and your laptop on the same tailnet and you can SSH to the server over that private address. Once that works, public port 22 has no reason to exist, so you close it.

There are two ways to run SSH over the tailnet, and it is worth knowing which you are choosing:

  1. Plain OpenSSH over Tailscale. You keep using normal OpenSSH and simply reach it through the private address. Your existing keys and hardening still apply. This is what I recommend for most people, because nothing about your battle-tested SSH setup changes.
  2. Tailscale SSH. Tailscale's own daemon answers on port 22 of the tailnet address and authenticates with your Tailscale identity and access rules. It is convenient, especially for teams, but it hands authentication to Tailscale instead of OpenSSH. Reasonable people disagree here; pick based on how much you want to lean on one provider.

Either way, the firewall move is the same.

Step 1: Install Tailscale and connect

On the server, install Tailscale and bring it up. The install script supports every mainstream distro:

Bash
1curl -fsSL https://tailscale.com/install.sh | sh2sudo tailscale up

The command prints a URL. Open it, sign in, and the server joins your tailnet. Install Tailscale on your laptop the same way and sign in with the same account. Check the server's private address with:

Bash
1tailscale ip -4

You will get a 100.x.x.x address. From your laptop, confirm plain SSH works over it before changing anything:

Bash
1ssh deploy@100.x.x.x

If you would rather use Tailscale SSH, enable it without dropping your session:

Bash
1sudo tailscale set --ssh

Step 2: Disable key expiry (do not skip this)

Warning

This is the step people forget, and the one that locks them out. By default Tailscale expires a machine's key after 180 days and asks it to log in again. On your laptop that is a minor prompt, but on a headless server whose only door is Tailscale, it is a lockout you cannot fix remotely.

In the Tailscale admin console, open Machines, find the server, open the "..." menu, and choose Disable key expiry. Do this now, while you are thinking about it, for every server you put behind the tailnet.

Step 3: Let the tailnet through UFW, then close port 22

Now tell the firewall to trust the Tailscale interface and remove the public SSH rule. This order matters. Following Tailscale's own ufw lockdown guide:

Bash
1# Allow everything arriving over the private Tailscale interface2sudo ufw allow in on tailscale03 4# Keep the public web ports if you serve a site; otherwise skip this5sudo ufw allow 80,443/tcp6 7# Remove the public SSH opening8sudo ufw delete allow OpenSSH

Watch for a leftover blanket rule. If sudo ufw status still lists something like 22/tcp ALLOW IN Anywhere, delete it too:

Bash
1sudo ufw delete allow 22/tcp

Confirm the rules took effect:

Bash
1sudo ufw status verbose2sudo ss -tlnp | grep :22

ufw status verbose should show Default: deny (incoming), an ALLOW ... on tailscale0 line, and no rule for port 22. One point that trips people up: sshd keeps listening on 0.0.0.0:22, so ss still shows it there, and that is fine. With this approach UFW is what blocks the public side, not sshd, so you verify by behavior (the next step) rather than by the listen address. If you would rather sshd not listen on the public interface at all, you can add ListenAddress lines for your Tailscale and loopback addresses to the SSH drop-in, but leave that as optional hardening: if the tailnet interface is not up when sshd starts, binding fails, so the firewall rule is the safer primary control.

Step 4: Test before you trust it

Warning

Never close your working session until a fresh one succeeds. Keep your current SSH terminal open until you have confirmed a new connection works over the tailnet.

With your current SSH terminal still open, start a new terminal and connect over the tailnet:

Bash
1ssh deploy@100.x.x.x

If that lands you on the server, public SSH is closed and private SSH works. Now you can close the old session. If it fails, you still have the original terminal to undo the firewall change.

Your safety net if it all goes wrong

Two things keep this from ever becoming a real lockout:

  • The provider console. Hostinger and most hosts include a browser based terminal in their control panel that reaches the server directly, not over SSH. If Tailscale is ever down or misconfigured, that is how you get in to fix it. This is the recovery path you were told to find in the baseline guide.
  • The reversal order. If you ever remove Tailscale from the server, re-open public SSH first (sudo ufw allow OpenSSH), or you will delete your only way in.

Is this actually a good idea? The trade-offs

I like this setup, but it is fair to name what you are trading:

  • You are trusting a third party. Tailscale and its coordination servers become part of your access path. Some people would rather depend only on OpenSSH, which is open source and among the most audited software in the world. That is a legitimate position.
  • You can own the control plane. If that worries you, run Headscale, an open source implementation of the Tailscale control server, or use NetBird as an alternative mesh. More work, less reliance on one company.
  • This is remote access, not everything. Public web traffic still needs a plan. You can keep serving 80 and 443 directly, or, to avoid opening even those, put the app behind a tunnel. That is a good topic for its own guide.

If you self-host a handful of services for yourself and value not watching your SSH port get scanned a thousand times a day, this is hard to beat.

Where to go next

Final thoughts

The mental shift is simple: your server should not answer the door for strangers at all. Give it a private address, let only your own machines knock, and keep one emergency key (the provider console) for the bad day. Do that, and the daily reality of running a public VPS gets a lot quieter.

Verified end to end on a real Ubuntu 24.04.4 server: joined a tailnet, allowed `tailscale0` in UFW, and removed the public port 22 rule. From a separate machine, SSH to the public IP then timed out while SSH over the tailnet address still logged in, and the kernel firewall log showed the public SYN dropped on `eth0`. The reversal step re-opened public SSH cleanly. See the linked lab notes.

Join the discussion on Put SSH Behind Tailscale and Close Port 22

Likes, comments, and replies are available for authenticated readers with verified email addresses.

Comments (0)

Loading discussion...

Related guides