9 cfg = config.services.tailscale.derper;
12 meta.maintainers = with lib.maintainers; [ SuperSandro2000 ];
15 services.tailscale.derper = {
16 enable = lib.mkEnableOption "Tailscale Derper. See upstream doc <https://tailscale.com/kb/1118/custom-derp-servers> how to configure it on clients";
18 domain = lib.mkOption {
20 description = "Domain name under which the derper server is reachable.";
23 openFirewall = lib.mkOption {
24 type = lib.types.bool;
27 Whether to open the firewall for the specified port.
28 Derper requires the used ports to be opened, otherwise it doesn't work as expected.
32 package = lib.mkPackageOption pkgs [
37 stunPort = lib.mkOption {
38 type = lib.types.port;
41 STUN port to listen on.
42 See online docs <https://tailscale.com/kb/1118/custom-derp-servers#prerequisites> on how to configure a different external port.
47 type = lib.types.port;
49 description = "The port the derper process will listen on. This is not the port tailscale will connect to.";
52 verifyClients = lib.mkOption {
53 type = lib.types.bool;
56 Whether to verify clients against a locally running tailscale daemon if they are allowed to connect to this node or not.
62 config = lib.mkIf cfg.enable {
63 networking.firewall = lib.mkIf cfg.openFirewall {
64 # port 80 and 443 are opened by nginx already
65 allowedUDPPorts = [ cfg.stunPort ];
71 upstreams.tailscale-derper = {
72 servers."127.0.0.1:${toString cfg.port}" = { };
77 virtualHosts."${cfg.domain}" = {
78 addSSL = true; # this cannot be forceSSL as derper sends some information over port 80, too.
80 proxyPass = "http://tailscale-derper";
81 proxyWebsockets = true;
90 tailscale.enable = lib.mkIf cfg.verifyClients true;
93 systemd.services.tailscale-derper = {
96 "${lib.getExe' cfg.package "derper"} -a :${toString cfg.port} -c /var/lib/derper/derper.key -hostname=${cfg.domain} -stun-port ${toString cfg.stunPort}"
97 + lib.optionalString cfg.verifyClients " -verify-clients";
100 RestartSec = "5sec"; # don't crash loop immediately
101 StateDirectory = "derper";
104 CapabilityBoundingSet = [ "" ];
106 LockPersonality = true;
107 NoNewPrivileges = true;
108 MemoryDenyWriteExecute = true;
109 PrivateDevices = true;
113 ProtectControlGroups = true;
114 ProtectHostname = true;
115 ProtectKernelLogs = true;
116 ProtectKernelModules = true;
117 ProtectKernelTunables = true;
118 ProtectProc = "invisible";
119 RestrictAddressFamilies = [
124 RestrictNamespaces = true;
125 RestrictRealtime = true;
126 SystemCallArchitectures = "native";
127 SystemCallFilter = [ "@system-service" ];
129 wantedBy = [ "multi-user.target" ];