On this page
- Arsenal of the Paranoid: Preparing the Bastion Environment
- The Fortress Topology: Routing Identity, Not Packets
- Sealing the Hatches: From Root Access to Cryptographic Invisibility
- Phase 1: Bootstrapping the Identity
- Phase 2: Laying the Mesh and Preparing the Environment
- Phase 3: The Refined Configuration (Socket Binding)
- Phase 4: The Belt and Braces Firewall
- Kernel-Level Socket Binding: The Anatomy of a Ghost Node
- The Lockout Trap: Mitigating Self-Inflicted Denials of Service
- The Invisible Perimeter: Mastering the Zero-Trust Bastion
The moment you provision a Virtual Private Server (VPS) and bind it to a public IP address, it is under attack. Within minutes, automated botnets will begin relentlessly brute-forcing Port 22, scanning for weak credentials and misconfigurations. Relying on password authentication or simple non-standard port obfuscation is a naive gamble. To build a truly robust, production-ready deployment platform, we must shift our paradigm from perimeter defense to zero-trust architecture.
In this deep dive, we will implement a “Belt and Braces” security methodology using Tailscale (a WireGuard-based mesh VPN). We aren’t just going to secure the SSH daemon; we are going to make it cryptographically invisible to the public internet. By the end of this tutorial, you will have engineered a hardened, production-grade Linux environment where administrative access is strictly tied to cryptographic identity, bounded by kernel-level socket restrictions, and shielded by a ruthless firewall.
Arsenal of the Paranoid: Preparing the Bastion Environment
Before we mutate our standard server into a ghost node, you must ensure your local machine and remote environment are properly staged.
Knowledge Base: To succeed here, you should have a firm grasp of Linux file permissions (the octal system), asymmetric cryptography (public/private keypairs), and basic OSI Model Layer 3/Layer 4 networking (IP addressing and TCP/UDP ports).
Environment & Tooling:
- A freshly provisioned Ubuntu/Debian VPS with a public IPv4 address.
- Root access to the VPS via an established SSH keypair (no passwords).
- A free Tailscale account with the client installed and authenticated on your local workstation.
- Basic Unix utilities:
ufw(Uncomplicated Firewall),systemd,apt, andvim.
The Fortress Topology: Routing Identity, Not Packets
To understand our “Belt and Braces” strategy, think of your VPS as an exclusive VIP underground club. The “Naive Approach” is simply putting a strong lock on the front door (disabling passwords) and hoping the bouncer (the firewall) checks IDs correctly. Our “Refined Approach” removes the front door entirely. The club can now only be accessed through a secret, subterranean tunnel (Tailscale) that only exists for cryptographically verified VIPs.
Here is the precise traffic flow and state machine of our target architecture:
flowchart TD
subgraph Public Internet
A[Attacker / Botnet]
B[Legitimate User]
end
subgraph The Invisible VPS
FW{UFW Firewall}
TS[tailscale0 Interface \n CGNAT: 100.x.x.x]
SSH[SSH Daemon \n Bound strictly to 100.x.x.x]
FW -- Drop Port 22 --> A
FW -- Drop Port 22 --> B
TS -- Cryptographic Tunnel --> SSH
end
subgraph Local Workstation
C[Local Tailscale Client]
end
A -.->|Public IP:22| FW
B -.->|Public IP:22| FW
C ==>|WireGuard UDP Tunnel| TS
Sealing the Hatches: From Root Access to Cryptographic Invisibility
We will implement this in four distinct phases: Identity Bootstrapping, Mesh Integration, Socket Binding, and Firewall Hardening.
Phase 1: Bootstrapping the Identity
Working as the root user is an extreme security liability. We will first create a dedicated administrative user, grant them sudo privileges, and meticulously migrate our SSH keys to ensure we don’t lock ourselves out.
# Connect to your fresh VPS using its public IP
ssh root@<PUBLIC_VPS_IP>
# Create the new administrative user
adduser username
# Append the new user to the sudo group for administrative elevation
usermod -aG sudo username
# Create the SSH directory for the new user, suppressing errors if it exists
mkdir -p /home/username/.ssh
# Securely copy the authorized keys from root to the new user
cp /root/.ssh/authorized_keys /home/username/.ssh/authorized_keys
💡 Pro-Tip: Copying the authorized_keys file directly from root ensures that the exact same cryptographic keypair you just used to log in will work for the new user.
Next, we must repair the permissions. SSH is notoriously strict; if the permissions are too loose, the SSH daemon will silently reject the keys and refuse the connection to protect you from malicious local actors.
# Recursively change ownership of the .ssh directory to the new user
chown -R username:username /home/username/.ssh
# Restrict directory access strictly to the owner (read/write/execute)
chmod 700 /home/username/.ssh
# Restrict key file access strictly to the owner (read/write only)
chmod 600 /home/username/.ssh/authorized_keys
# Switch to the new user environment to verify functionality
su - username
# Verify sudo privileges (you will be prompted for 'username's password)
sudo ls
Phase 2: Laying the Mesh and Preparing the Environment
With our user secured, we update our package indices and install Tailscale. We will also add our user to the docker group, establishing the foundation for our future zero-friction container deployments.
# Synchronize package repositories and upgrade existing packages
sudo apt update && sudo apt upgrade -y
# Add the user to the docker group (assuming Docker is or will be installed)
sudo usermod -aG docker username
# Pull the Tailscale installation script and pipe it to the shell
curl -fsSL https://tailscale.com/install.sh | sh
# Initialize the Tailscale daemon and authenticate the node
sudo tailscale up
Once you run sudo tailscale up, you will be provided with an authentication link. Open this link in your local browser to append the VPS to your Tailscale network (your “tailnet”).
Phase 3: The Refined Configuration (Socket Binding)
Here is where we diverge from standard tutorials. A naive setup leaves the SSH daemon listening on 0.0.0.0 (all available network interfaces, including the public internet) and relies entirely on the firewall to block traffic.
We will use the Belt and Braces approach: We will reconfigure the SSH daemon to only bind to the specific Tailscale IP addresses. If the daemon isn’t listening on the public interface, a firewall failure won’t expose Port 22.
First, retrieve your Tailscale IPv4 and IPv6 addresses from your Tailscale admin console or by running tailscale ip.
# Open the SSH daemon configuration file
sudo vim /etc/ssh/sshd_config
Locate the following directives, uncomment them if necessary, and strictly define them as follows:
# Bind SSH strictly to the Tailscale IPv4 interface (CGNAT space)
ListenAddress <TAILSCALE_IPV4>
# Bind SSH strictly to the Tailscale IPv6 interface
ListenAddress <TAILSCALE_IPV6>
# Ruthlessly disable password authentication
PasswordAuthentication no
# Disable Pluggable Authentication Modules to prevent auth bypasses
UsePAM no
# Completely sever direct root login capabilities
PermitRootLogin no
Apply the changes and bounce the server to ensure all kernel-level bindings take effect smoothly.
# Restart the SSH daemon to parse the new configuration
sudo systemctl restart ssh
# Perform a full system reboot to ensure persistence
sudo reboot now
🔴 Danger: After this reboot, you cannot connect to the server via its public IP address. You must now SSH using your Tailscale IP: ssh username@<TAILSCALE_IPV4>.
Phase 4: The Belt and Braces Firewall
We’ve secured the daemon (the belt). Now, we configure the Uncomplicated Firewall (the braces). Even though SSH is only listening on the Tailscale interface, we want the firewall to aggressively drop any stray packets targeting Port 22 from the outside world.
# Set aggressive default policies: deny all in, allow all out
sudo ufw default deny incoming
sudo ufw default allow outgoing
# (Naive Step - often done initially before hardening)
sudo ufw allow OpenSSH
sudo ufw show added
sudo ufw enable
Now, we evolve to the Refined Configuration. We will explicitly whitelist Tailscale’s RFC 6598 subnet (Carrier-Grade NAT) and delete the generic OpenSSH rules.
# Explicitly whitelist the entire Tailscale IPv4 CGNAT subnet
sudo ufw allow from '100.64.0.0/10' to any port 22
# Explicitly whitelist the Tailscale IPv6 subnet
sudo ufw allow from 'fd7a:115c:a1e0::/48' to any port 22
# Ruthlessly deny all other traffic attempting to hit Port 22
sudo ufw deny 22
sudo ufw deny OpenSSH
# Verify the rule hierarchy
sudo ufw status
# Reload and enable the firewall to lock the perimeter
sudo ufw enable
Kernel-Level Socket Binding: The Anatomy of a Ghost Node
Why go through the trouble of altering the ListenAddress in sshd_config if ufw is already blocking Port 22?
Under the hood, when a Linux service starts, it makes a bind() system call to the kernel. If configured to 0.0.0.0, the kernel attaches the service to every Network Interface Card (NIC) on the machine—including your public eth0 interface. If ufw (which acts as a front-end for iptables or nftables) crashes, is accidentally flushed, or misconfigured during a late-night debugging session, your SSH daemon is instantly exposed to the public internet.
By defining <TAILSCALE_IPV4>, we instruct the kernel to attach the SSH socket exclusively to the tailscale0 virtual network interface. Packets arriving on eth0 with a destination of Port 22 hit the kernel, and the kernel drops them immediately because no socket exists on that interface. It is hardware-level invisibility.
🔵 Deep Dive: Notice the IP 100.64.0.0/10 in our UFW rules? This is defined in RFC 6598 as Shared Address Space, commonly known as Carrier-Grade NAT (CGNAT). Tailscale cleverly utilizes this reserved block to assign static IPs to your devices because these addresses are mathematically guaranteed not to route over the public internet, preventing IP collisions with your local home or office network.
The Lockout Trap: Mitigating Self-Inflicted Denials of Service
When building highly secure, zero-trust architectures, your greatest adversary is often yourself.
The Tailscale Dependency:
By relying entirely on Tailscale for inbound access, you introduce a critical point of failure. If the tailscaled service crashes, or if an aggressive firewall rule inadvertently blocks Tailscale’s outbound UDP communication to its coordination servers, you will be locked out.
Mitigation (Out-of-Band Management): Before implementing this on a production database or critical infrastructure, ensure your VPS hosting provider offers an “Out-of-Band” (OOB) web console (often an emulated VNC or Serial console in your browser). Because this console interacts directly with the virtualized hardware (tty1), it bypasses network-level SSH and UFW rules, allowing you to log in locally and fix the daemon if you sever your own connection.
Concurrency and Boot Ordering:
We disabled UsePAM and PasswordAuthentication. If sshd attempts to start during the boot sequence before tailscaled has fully initialized the tailscale0 network interface, the SSH daemon might fail to bind to the ListenAddress and crash. Systemd usually handles dependency trees well, but if you encounter this edge case, you can modify the sshd.service file to strictly mandate After=tailscaled.service.
The Invisible Perimeter: Mastering the Zero-Trust Bastion
You have successfully transitioned a vulnerable, public-facing server into a cryptographically invisible node.
You now know how to implement a “Belt and Braces” security posture. By combining kernel-level socket binding (sshd_config) with network-layer packet filtering (ufw), and routing identity through a WireGuard mesh network (Tailscale), you have completely removed your infrastructure’s attack surface from the public internet.
This foundation is crucial. With the perimeter locked down, you can now confidently deploy databases, continuous integration runners, and production applications without the lingering anxiety of automated intrusion.