back

Securing SSH with Tailscale

After setting up Coolify on Hetzner, I had a working server. Coolify was running, HTTPS was configured, everything was reachable. I was happy with the result, but I also had this feeling that there was more to learn when it comes to server security.

After publishing the post, I got a helpful hint from nicam.dev, and Daniel Muxel also gave me useful input while I was working through the setup.

The original nudge was essentially this:

Coolify is great. You might want to check out Tailscale. Then you can close the SSH port and access your server via Tailscale. Protects your server even better.

I had never heard of Tailscale before. But the idea of closing the SSH port completely sounded intriguing, so I started looking into it.

I knew it wasn't perfect yet

My setup was functional, but I was aware that having SSH open to the internet wasn't ideal. I didn't fully understand the scope of the problem at the time, but I knew that bots scan for open SSH ports constantly, and that my server was exposed to that. It was one of those things where you know there's room to improve, but you don't yet know what the right solution looks like.

That hint gave me a direction.

Discovering Tailscale

Tailscale turned out to be surprisingly simple to understand. It creates a private network between your devices using WireGuard under the hood. Every device gets an internal IP in the 100.x.x.x range, and traffic between them is encrypted and peer-to-peer.

What I liked most: no port forwarding, no complex VPN configuration. You install it, log in, and your devices can talk to each other privately. That was exactly the kind of simplicity I was looking for.

Step 1: Install Tailscale on the server

On the Hetzner server, the installation was straightforward:

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

After authenticating, the server received its Tailscale IP:

100.127.151.115

Seeing that internal IP appear felt like a small breakthrough. My server now had a private address that only my devices could reach.

Step 2: Install Tailscale on my machine

On macOS, I installed Tailscale via Homebrew:

brew install tailscale

After logging in with the same Tailscale account, my laptop got its own internal IP (100.96.x.x). Both devices were now in the same private network. That was the moment it clicked for me: these two machines can now talk to each other without going through the public internet.

Step 3: Switch SSH to Tailscale

This was the part I was most curious about. Previously, my SSH config pointed to the public IP:

Host therkiller-coolify
    HostName 46.224.144.135
    User root
    IdentityFile ~/.ssh/id_ed25519_therkiller
    IdentitiesOnly yes

I changed it to use the Tailscale IP instead:

Host therkiller-coolify
    HostName 100.127.151.115
    User root
    IdentityFile ~/.ssh/id_ed25519_therkiller
    IdentitiesOnly yes

Same command as before:

ssh therkiller-coolify

It connected instantly. From the outside, nothing changed in my workflow. But under the hood, the connection was now traveling through the private Tailscale network.

Step 4: Lock down the firewall

This was the step that made the biggest difference. In the Hetzner Cloud firewall, I changed the SSH rule:

RuleBeforeAfter
Port 22Any IPv4 (0.0.0.0/0)100.64.0.0/10 (Tailscale only)
Port 80Any IPv4Any IPv4
Port 443Any IPv4Any IPv4

Port 80 and 443 stay open for web traffic (Coolify, websites). But SSH is now restricted to the Tailscale IP range. Only devices in my private network can reach it.

When I understood what this meant, it was a real eye-opener: my server is now invisible for SSH on the public internet. No bot can even see that port 22 exists.

The architecture, simplified

Internet
   |
[80/443 open] --> Website / Coolify
[22 closed]   --> blocked from public

         Tailscale Network
              |
     Laptop <---> Server (SSH)

What I learned

This whole process taught me a few things:

SSH doesn't need to be public. This sounds obvious in hindsight, but before Tailscale, I didn't know how to solve it without a complex VPN setup. Now I do, and it's surprisingly easy.

Security can be simple. I expected hardening a server to be complicated. Tailscale showed me that sometimes the best security improvement is also the simplest one.

Access from anywhere still works. Whether I'm at home, in a cafe, or on a different network entirely, Tailscale just works. The private network follows my device.

This scales well. If I add more servers later, I can connect them all through Tailscale. Internal services, databases, admin panels, all private by default. That's something I want to explore further.

Conclusion

What started with a friendly hint turned into an impactful change I've made to my server setup. I went from having SSH publicly exposed to having it completely invisible on the internet, and the whole process took less than an hour.

If you're in a similar spot, running a server with SSH open to the world, and you have that same feeling that things could be more secure: look into Tailscale. It was exactly the missing piece I didn't know I was looking for.