The trap
A Tailscale exit node or subnet router needs IP forwarding. The usual verification is:
sysctl -n net.ipv4.ip_forward # 1 -> looks fineBut the Docker daemon sets net.ipv4.ip_forward=1 at runtime when it configures its bridge. On any host that also runs Docker, that reads 1 whether or not anything persists it. The exit node forwards traffic correctly, indefinitely, for the wrong reason — and stops the moment Docker is disabled, removed, or fails to start before the network is used. On a reboot where Docker starts late, you get a window with no forwarding.
On one host this had been true for months: exit node advertised and working, ip_forward=1, and nothing in /etc/sysctl.conf or /etc/sysctl.d/ setting it.
Check the config, not the running value
sysctl -n net.ipv4.ip_forward net.ipv6.conf.all.forwarding
grep -rniE '^\s*net\.(ipv4\.ip_forward|ipv6\.conf\.all\.forwarding)' /etc/sysctl.conf /etc/sysctl.d/A 1 from the first with no hit from the second means you are relying on something else's side effect.
Fix
sudo tee /etc/sysctl.d/99-tailscale.conf >/dev/null <<'EOF'
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF
sudo sysctl -p /etc/sysctl.d/99-tailscale.conf
sudo sysctl --system # prove it survives a full reloadTwo things worth knowing
Older clients may not warn. Tailscale 1.88.3 reported only a (stale, unrelated) DNS warning. Upgrading to 1.102.2 immediately surfaced Subnet routing is enabled, but IP forwarding is disabled — the defect predated the upgrade by a long way; the newer client just detects it. IPv6 forwarding was 0 outright, so IPv6 exit traffic had never worked while ::/0 was advertised.
Before enabling IPv6 forwarding, check you will not strand the host. With forwarding=1 the kernel stops honouring Router Advertisements unless accept_ra=2, so a host holding a SLAAC address can lose it. Verify first:
ip -6 addr show dev <iface> scope global
ip -6 route show default
sysctl -n net.ipv6.conf.<iface>.accept_raNo global address, no default route, and accept_ra already 0 means there is nothing to lose. If the host does hold a SLAAC address and a v6 default route, set accept_ra=2 on that interface in the same drop-in.
Related: upgrade the client over a path it does not own
Upgrading the tailscale package restarts tailscaled, which tears down the tunnel. If you are SSH'd in over the tailnet, that kills your session mid-dpkg. Drive it over a LAN address or console instead.