VPS Hardening Guide (Ubuntu + Tailscale SSH)

Hardening steps for a fresh Ubuntu VPS. Replaces public SSH with Tailscale SSH so the server has zero public attack surface beyond ports 80/443.

Prerequisites: Fresh Ubuntu VPS with root SSH access and a Tailscale account.

1. Update system

apt update && apt upgrade -y

2. Install security tools

apt install -y ufw fail2ban unattended-upgrades

Since we’re disabling public SSH, fail2ban won’t have much to guard by default. If you’re running a web server (nginx, Caddy, etc.), configure fail2ban to watch its logs:

sudo tee /etc/fail2ban/jail.local << 'EOF'
[nginx-http-auth]
enabled = true

[nginx-botsearch]
enabled = true
EOF
sudo systemctl restart fail2ban

3. Create a non-root user

adduser deploy
usermod -aG sudo deploy

Verify sudo works:

su - deploy
sudo whoami  # should print "root"
exit

4. Disable root account

passwd -l root

Also prevent root SSH login (belt and suspenders):

sed -i 's/^PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
systemctl restart ssh

5. Enable automatic security updates

dpkg-reconfigure -plow unattended-upgrades
# Select "Yes"

6. Configure firewall

ufw default deny incoming
ufw default allow outgoing
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 22/tcp   # temporary — removed after Tailscale is confirmed
ufw enable

7. Install Tailscale & enable Tailscale SSH

You can verify the install script before running it, or install from Tailscale’s official apt repo instead:

curl -fsSL https://tailscale.com/install.sh -o install-tailscale.sh
less install-tailscale.sh  # review it
sh install-tailscale.sh
tailscale up --ssh

A login URL will appear — open it in your browser and authenticate.

Once connected, lock down who can SSH into this machine via your Tailscale ACLs. In the Tailscale admin console, restrict SSH access to only your user or specific groups rather than leaving it open to the entire tailnet.

8. Verify Tailscale SSH works

From your local machine (must also have Tailscale running):

ssh deploy@<tailscale-hostname>

Do not proceed until this works. You will lock yourself out otherwise.

9. Disable public SSH

sudo ufw delete allow 22/tcp
sudo systemctl disable --now ssh
sudo systemctl disable --now ssh.socket

10. Harden sysctl

sudo tee /etc/sysctl.d/99-hardening.conf << 'EOF'
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0

# IPv6 hardening (or disable entirely if you don't use it)
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0
EOF
sudo sysctl --system

11. Basic log monitoring

Keep an eye on what’s happening on your server:

# Failed login attempts
sudo journalctl -u ssh --no-pager | grep "Failed"

# fail2ban status
sudo fail2ban-client status

# Recent firewall blocks
sudo dmesg | grep -i "UFW BLOCK"

For something more hands-off, consider setting up logwatch for daily email summaries:

sudo apt install -y logwatch
sudo logwatch --output mail --mailto [email protected] --detail high

Result

Connecting

ssh deploy@<tailscale-hostname>