diff --git a/services/vpn/headscale.nix b/services/vpn/headscale.nix index 7bafe0a..b307a0d 100644 --- a/services/vpn/headscale.nix +++ b/services/vpn/headscale.nix @@ -20,6 +20,34 @@ base_domain = "hosts.mgaction.town"; nameservers.global = [ "1.1.1.1" "9.9.9.9" ]; }; + + # Run our own DERP relay instead of pulling Tailscale's map. + # + # With the default (urls = [controlplane.tailscale.com/derpmap/default], + # auto_update_enabled = true) headscale fetches that map at startup and + # treats failure as FATAL — so a DNS blip or a Tailscale outage stops the + # control server from booting at all. A self-hosted control plane that + # can't start without Tailscale's infrastructure rather misses the point. + # + # The relay itself rides Caddy on :443 (hence the flush_interval -1 on + # that vhost); only STUN needs its own UDP port. + derp = { + urls = [ ]; + auto_update_enabled = false; + server = { + enabled = true; + region_id = 999; # 900-999 is the custom range + region_code = "neptun"; + region_name = "neptun"; + stun_listen_addr = "0.0.0.0:3478"; + automatically_add_embedded_derp_region = true; + }; + }; }; }; + + # STUN for the embedded DERP server above. Also needs a matching inbound-UDP + # rule in netcup's edge firewall — it is stateless and defaults to denying + # inbound UDP outright, which silently kills every DNS/NTP reply too. + networking.firewall.allowedUDPPorts = [ 3478 ]; }