Back
57/65
Lesson 57
7 min
Tailscale & Network Security
Secure your VPS with mesh VPN, SSH hardening, and firewall lockdown
Ready to level up?
Mark complete to track your learning progress.
Secure your VPS with mesh VPN, SSH hardening, and firewall lockdown
Mark complete to track your learning progress.
Secure your VPS with Tailscale mesh VPN and SSH hardening — access your development server from anywhere without exposing ports to the internet.
Your VPS has a public IP that bots scan constantly. Automated scripts probe SSH ports, try default credentials, and exploit known vulnerabilities around the clock.
Tailscale creates a private mesh network (WireGuard-based) with zero config. No open ports, no port forwarding, no firewall rules to manage.
WireGuard under the hood, automatic key exchange
Access machines by name, not IP
Encrypted tunnel, no exposed SSH port
Fine-grained access control per device
Install and connect in 2 commands:
1# Install Tailscale (ACFS does this automatically)2curl -fsSL https://tailscale.com/install.sh | sh34# Authenticate and join your tailnet5sudo tailscale up67# Check your Tailscale IP8tailscale ip -49# → 100.x.y.z (your private Tailscale IP)1011# See all devices on your tailnet12tailscale status13# → Shows all connected machines with their Tailscale IPs1415# Enable MagicDNS (in Tailscale admin console)16# Then access your VPS by name:17ssh ubuntu@my-vps # Instead of ssh ubuntu@203.0.113.42
Once Tailscale is set up, lock down the public SSH port:
1# Step 1: Verify Tailscale SSH works2ssh ubuntu@100.x.y.z # Use your Tailscale IP34# Step 2: Configure SSH keepalive (prevents disconnects)5# /etc/ssh/sshd_config6ClientAliveInterval 607ClientAliveCountMax 38# → Server pings client every 60s, disconnects after 3 missed910# Step 3: Disable password authentication11# /etc/ssh/sshd_config12PasswordAuthentication no13ChallengeResponseAuthentication no1415# Step 4: Restrict SSH to Tailscale interface only16# /etc/ssh/sshd_config17ListenAddress 100.x.y.z # Only listen on Tailscale IP18# → Public IP SSH is now closed!1920# Step 5: Restart SSH (CAREFUL — test first!)21sudo systemctl restart sshd2223# Step 6: Verify from another terminal24ssh ubuntu@100.x.y.z # Should work25ssh ubuntu@public-ip # Should be refused
Tailscale can handle SSH authentication itself — no SSH keys to manage:
1# Enable Tailscale SSH on your VPS2sudo tailscale up --ssh34# Now connect from any device on your tailnet:5ssh ubuntu@my-vps6# → Authenticated via Tailscale identity, no SSH key needed!78# This works because:9# 1. Tailscale verifies your identity (OAuth/SSO)10# 2. Creates ephemeral SSH certificates11# 3. No permanent SSH keys to rotate or leak1213# Check Tailscale SSH status14tailscale ssh-status1516# ACL control (in admin console):17# "ssh": [{18# "action": "accept",19# "src": ["group:devs"],20# "dst": ["tag:servers"],21# "users": ["ubuntu"]22# }]
Connect multiple VPS instances into a private network:
1# Your tailnet connects all machines:2# Laptop → 100.100.1.13# VPS-1 → 100.100.1.2 (coding server)4# VPS-2 → 100.100.1.3 (build worker / RCH)5# VPS-3 → 100.100.1.4 (database server)67# All machines can reach each other directly8# No port forwarding, no NAT traversal needed910# RCH workers communicate over Tailscale11rch workers probe --all12# → Workers reachable at 100.x.y.z addresses1314# PostgreSQL over Tailscale (no public exposure)15psql -h 100.100.1.4 -U postgres16# → Database only accessible on tailnet1718# Forward a port through Tailscale19# On VPS: Next.js dev server on port 300020# On laptop: access via http://100.100.1.2:300021# → No ngrok, no port forwarding needed
Final hardening: close everything except Tailscale.
1# Allow Tailscale traffic2sudo ufw allow in on tailscale034# Allow SSH only from Tailscale5sudo ufw allow in on tailscale0 to any port 2267# Block public SSH8sudo ufw deny 22/tcp910# Enable firewall11sudo ufw enable12sudo ufw status13# → Only Tailscale interface accepts connections1415# Verify your VPS is locked down16# From external machine:17nmap public-ip # → All ports filtered/closed18# From tailnet:19ssh ubuntu@100.x.y.z # → Works perfectly