UFW, tailnet only
A firewall that answers only on the Tailscale interface, with one narrow exception for containers reaching the front door.
Names and addresses are placeholders. The list says what each one stands for.
What it does here
Nothing on the box answers on the home network or the internet. The tailnet is the only way in, so no router ports are ever opened.
Canonical setup
Deny incoming, allow outgoing, add the rules you need, then enable.
sudo ufw default deny incoming
sudo ufw default allow outgoingUpstream: UncomplicatedFirewall on the Ubuntu wiki.
In this build
- Allow in on tailscale0One rule covers SSH and mosh's UDP range for anything arriving over the tailnet.
- WireGuard port 41641/udp openIt helps Tailscale make direct connections through NAT.
- No LAN fallbackDecided before enabling. If you want SSH over the home network for when Tailscale is down, add
sudo ufw allow OpenSSH(and60000:61000/udpfor mosh) first. - Containers may reach Caddy on 443From the Docker address range to the box's tailnet address only. Added once the runner needs it.
sudo ufw allow in on tailscale0
sudo ufw allow 41641/udp
sudo ufw enable && sudo ufw status verbosesudo ufw allow from 172.16.0.0/12 to 100.x.y.z port 443 proto tcpSeams
Where this meets the rest of the build. Each one leads with what you will actually see.
Symptomconnections from a container to https://git.dev.tailnet.example time out
A container reaching a service name goes out to the box's tailnet address and back in to Caddy, and the firewall drops that hairpin. The scoped rule above lets it through, only for the Docker range, only to 443. Check it with docker run --rm alpine sh -c 'nc -zv git.dev.tailnet.example 443'.
Symptomssh stops answering after ufw enable
Confirm ssh youruser@devbox works over Tailscale in a second terminal before you walk away. Keep console access to the box until you have.
Symptomdevbox.local stops resolving
UDP 5353 is blocked with everything else. Allow it, or use the MagicDNS name. See the base hardening card.
Verify
sudo ufw status verbose # deny incoming; allow on tailscale0From another tailnet device, ssh youruser@devbox still works. From a device on the home network that isn't on the tailnet, it doesn't.
- 17 Oct 2026First written. Verified on Debian 13.6.