Skip to main content

UFW Without SSH Lockout

The target is public TCP 80/443, SSH only on tailscale0, default deny incoming, and allow outgoing. Keep one known-good public session and one verified Tailscale session. An established connection may survive a rule deletion, so only a fresh connection proves the new policy.

Inspect without resetting:

sudo ufw status verbose
sudo ufw status numbered

Then stage the rules:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # temporary fallback
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow in on tailscale0 to any port 22 proto tcp
sudo ufw --force enable
sudo ufw status verbose

Open fresh public and Tailscale SSH sessions. If both pass, inspect rules again and remove the semantic generic rule:

sudo ufw delete allow 22/tcp
sudo ufw status numbered

Do not assume a saved rule number; numbering changes, and a numbered deletion normally removes only that specific IPv4 or IPv6 entry.

[LOCAL: WSL2]
# Must pass
ssh -o ConnectTimeout=10 "$VPS_USER@$TAILSCALE_IPV4"
# Must time out or be rejected
ssh -o ConnectionAttempts=1 -o ConnectTimeout=5 \
"$VPS_USER@$VPS_PUBLIC_IPV4"

Docker warning: published container ports can bypass ordinary UFW rules. Future deployments must review Compose ports, listening addresses, docker ps, and the DOCKER-USER chain. Disabling Docker's iptables management usually breaks container networking.

If Tailscale SSH transport fails after removal, keep the surviving session, restore sudo ufw allow 22/tcp, verify fresh public-key SSH, gather diagnostics, and stop. Recovery means restoring a verified path, not resetting the firewall.