Once your cloud firewall restricts web traffic to Cloudflare IPs, SSH is the last door left open to the world. You could restrict it to your home IP and call it done — the cloud firewall guide suggests exactly that. But home IPs rotate. You travel. You need access from a phone on a coffee shop WiFi or a laptop on a hotel network. Opening SSH to every network you might use defeats the point of locking it down.
Tailscale solves this. It gives every device a static private IP on a WireGuard mesh network, accessible from anywhere, encrypted end-to-end, without exposing a public port. Combine it with direct SSH from known home IPs and you get three paths in — two direct for when the mesh is down, one mesh for when you're not home.
What you need
- Tailscale installed on the server and on every device you SSH from — free for personal use
- A cloud firewall at your provider — Linode Cloud Firewall, Vultr Firewall, DigitalOcean Cloud Firewall, or equivalent
- SSH already hardened — key-based authentication, non-default port, root login disabled. The Hardening SSH guide covers the full setup
- Your home IPv4 and IPv6 prefix — the IPv6 /64 your ISP assigned you is typically static; the IPv4 may rotate but you can update the firewall rule when it does
The three paths in
Direct SSH from a known IP. No dependencies — works even if Tailscale is down, WireGuard won't start, or your mesh configuration is botched. If your ISP rotates IPv4 addresses, update the firewall rule when it changes.
Direct SSH over IPv6 from your permanent prefix. ISPs rarely rotate IPv6 assignments — the /64 your provider delegated to you is likely static. Same zero-dependency fallback as IPv4, but without the rotation headache.
SSH through the WireGuard mesh to the server's Tailscale IP. Works from anywhere — laptop, phone, tablet — without exposing a public SSH port to new IPs. Encrypted end-to-end. If the mesh is up, you're in.
Two direct paths for reliability. One mesh path for convenience. If Tailscale is down, you have direct access. If your home IPv4 changes, you have IPv6 and Tailscale. If all three fail simultaneously, something has gone so wrong that you're using your provider's emergency console anyway.
Cloud firewall rules
Add these to your existing cloud firewall — the same one that restricts ports 80 and 443 to Cloudflare IP ranges from the cloud firewall guide. If you haven't set up a cloud firewall yet, start there — the web traffic rules are the foundation. These SSH rules layer on top.
Protocol: TCP
Port:
22 (or your custom SSH port)Sources: your home IPv4, your home IPv6 /64,
100.64.0.0/10Action: Accept
The
100.64.0.0/10 subnet covers every Tailscale mesh IP. Without it, the UDP handshake on port 41641 succeeds — WireGuard establishes the tunnel — but the actual SSH connection over TCP is dropped because the firewall hasn't whitelisted the source IP the connection is coming from. Most providers let you group multiple addresses under a single rule; list all three sources together. If your ISP rotates your IPv4, update that entry when it changes. The IPv6 /64 and Tailscale subnet are static.
Protocol: UDP
Port:
41641Source: All IPv4, All IPv6
Action: Accept
WireGuard's default port. Opening this to the world is safe because WireGuard silently discards any packet that isn't encrypted and authenticated by a known peer key. No response, no handshake, no indication that anything is listening. To a port scanner, the port appears closed.
Action: DROP
Everything else — every port scanner, every botnet brute-forcing SSH on port 22, every probe from an IP you've never seen — is silently discarded at the hypervisor level. Your server's kernel never sees the packet.
Linode's Cloud Firewall groups addresses under a single rule — you add your home IPv4, home IPv6 /64, and optionally the server's own Tailscale IP as comma-separated sources on one Accept rule for the SSH port. Vultr and DigitalOcean work the same way. If your provider forces one address per rule, it takes three rules to cover the same paths — the logic is identical either way.
The cloud firewall operates at the hypervisor level. Dropped packets never reach your VM. The SSH brute-force attempts that fail2ban used to catch — the 77.83.39.x botnet campaign, the random VPS probing port 22 — never touch your server. fail2ban still runs on the host for defense in depth, but the volume of noise it processes drops dramatically. The fail2ban guide covers the host-level configuration; the cloud firewall handles the outer perimeter.
Make SSH listen on Tailscale
Your sshd is likely bound to your public IP and localhost. Tailscale connections arrive on the tailscale0 interface with a 100.x.x.x address, and sshd won't see them unless you tell it to listen there.
Check what sshd is currently listening on:
ss -tln | grep 22
If you don't see the Tailscale IP, add ListenAddress directives to /etc/ssh/sshd_config:
# Public interfaces — IPv4 and IPv6
ListenAddress 0.0.0.0:22
ListenAddress [::]:22
# Tailscale mesh IP — add this line
ListenAddress 100.100.100.100:22
The [::]:22 line ensures sshd accepts connections over IPv6 — if your home IPv4 rotates and you haven't updated the firewall rule yet, you can still SSH in directly over IPv6. Tailscale is IPv4-only for mesh IPs, so the Tailscale ListenAddress only needs an IPv4 entry. Test and reload:
sshd -t && systemctl reload sshd
Now sshd accepts connections on the public interfaces for direct access and on the Tailscale interface for mesh access. Combined with the cloud firewall rules, all three paths — home IPv4, home IPv6, and Tailscale — are open and listening.