summary refs log tree commit diff
path: root/nixos/modules/services/networking/nsd.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services/networking/nsd.nix')
-rw-r--r--nixos/modules/services/networking/nsd.nix687
1 files changed, 338 insertions, 349 deletions
diff --git a/nixos/modules/services/networking/nsd.nix b/nixos/modules/services/networking/nsd.nix
index db8cb122871..36d9f5d2f16 100644
--- a/nixos/modules/services/networking/nsd.nix
+++ b/nixos/modules/services/networking/nsd.nix
@@ -9,17 +9,25 @@ let
   stateDir = "/var/lib/nsd";
   pidFile  = stateDir + "/var/nsd.pid";
 
+  nsdPkg = pkgs.nsd.override {
+    bind8Stats = cfg.bind8Stats;
+    ipv6       = cfg.ipv6;
+    ratelimit  = cfg.ratelimit.enable;
+    rootServer = cfg.rootServer;
+    zoneStats  = length (collect (x: (x.zoneStats or null) != null) cfg.zones) > 0;
+  };
+
   zoneFiles = pkgs.stdenv.mkDerivation {
-      preferLocalBuild = true;
-      name = "nsd-env";
-      buildCommand = concatStringsSep "\n"
-        [ "mkdir -p $out"
-          (concatStrings (mapAttrsToList (zoneName: zoneOptions: ''
-            cat > "$out/${zoneName}" <<_EOF_
-            ${zoneOptions.data}
-            _EOF_
-          '') zoneConfigs))
-        ];
+    preferLocalBuild = true;
+    name = "nsd-env";
+    buildCommand = concatStringsSep "\n"
+      [ "mkdir -p $out"
+        (concatStrings (mapAttrsToList (zoneName: zoneOptions: ''
+          cat > "$out/${zoneName}" <<_EOF_
+          ${zoneOptions.data}
+          _EOF_
+        '') zoneConfigs))
+      ];
   };
 
   configFile = pkgs.writeText "nsd.conf" ''
@@ -33,7 +41,6 @@ let
       # the list of dynamically added zones.
       zonelistfile: "${stateDir}/var/zone.list"
       database:     "${stateDir}/var/nsd.db"
-      logfile:      "${stateDir}/var/nsd.log"
       pidfile:      "${pidFile}"
       xfrdfile:     "${stateDir}/var/xfrd.state"
       xfrdir:       "${stateDir}/tmp"
@@ -105,21 +112,21 @@ let
 
 
   zoneConfigFile = name: zone: ''
-        zone:
-          name:         "${name}"
-          zonefile:     "${stateDir}/zones/${name}"
-          ${maybeString "outgoing-interface: " zone.outgoingInterface}
-        ${forEach     "  rrl-whitelist: "      zone.rrlWhitelist}
-
-        ${forEach     "  allow-notify: "       zone.allowNotify}
-        ${forEach     "  request-xfr: "        zone.requestXFR}
-          allow-axfr-fallback: ${yesOrNo       zone.allowAXFRFallback}
-
-        ${forEach     "  notify: "             zone.notify}
-          notify-retry:                        ${toString zone.notifyRetry}
-        ${forEach     "  provide-xfr: "        zone.provideXFR}
-
-    '';
+    zone:
+      name:         "${name}"
+      zonefile:     "${stateDir}/zones/${name}"
+      ${maybeString "zonestats: "          zone.zoneStats}
+      ${maybeString "outgoing-interface: " zone.outgoingInterface}
+    ${forEach     "  rrl-whitelist: "      zone.rrlWhitelist}
+
+    ${forEach     "  allow-notify: "       zone.allowNotify}
+    ${forEach     "  request-xfr: "        zone.requestXFR}
+      allow-axfr-fallback: ${yesOrNo       zone.allowAXFRFallback}
+
+    ${forEach     "  notify: "             zone.notify}
+      notify-retry:                        ${toString zone.notifyRetry}
+    ${forEach     "  provide-xfr: "        zone.provideXFR}
+  '';
 
   zoneConfigs = zoneConfigs' {} "" { children = cfg.zones; };
 
@@ -130,8 +137,8 @@ let
 
       # fork -> pattern
       else zipAttrsWith (name: head) (
-          mapAttrsToList (name: child: zoneConfigs' (parent // zone // { children = {}; }) name child)
-                         zone.children
+        mapAttrsToList (name: child: zoneConfigs' (parent // zone // { children = {}; }) name child)
+                       zone.children
       );
 
   # fighting infinite recursion
@@ -145,138 +152,148 @@ let
 
   childConfig = x: v: { options.children = { type = types.attrsOf x; visible = v; }; };
 
-  zoneOptionsRaw = types.submodule (
-    { options, ... }:
-    { options = {
-        children = mkOption {
-            default     = {};
-            description = ''
-              Children zones inherit all options of their parents. Attributes
-              defined in a child will overwrite the ones of its parent. Only
-              leaf zones will be actually served. This way it's possible to
-              define maybe zones which share most attributes without
-              duplicating everything. This mechanism replaces nsd's patterns
-              in a save and functional way.
-            '';
-        };
+  zoneOptionsRaw = types.submodule {
+    options = {
+      children = mkOption {
+        default     = {};
+        description = ''
+          Children zones inherit all options of their parents. Attributes
+          defined in a child will overwrite the ones of its parent. Only
+          leaf zones will be actually served. This way it's possible to
+          define maybe zones which share most attributes without
+          duplicating everything. This mechanism replaces nsd's patterns
+          in a save and functional way.
+        '';
+      };
 
-        allowNotify = mkOption {
-          type        = types.listOf types.str;
-          default     = [ ];
-          example     = [ "192.0.2.0/24 NOKEY" "10.0.0.1-10.0.0.5 my_tsig_key_name"
-                           "10.0.3.4&255.255.0.0 BLOCKED"
-                        ];
-          description = ''
-            Listed primary servers are allowed to notify this secondary server.
-            <screen><![CDATA[
-            Format: <ip> <key-name | NOKEY | BLOCKED>
-
-            <ip> either a plain IPv4/IPv6 address or range. Valid patters for ranges:
-            * 10.0.0.0/24            # via subnet size
-            * 10.0.0.0&255.255.255.0 # via subnet mask
-            * 10.0.0.1-10.0.0.254    # via range
-
-            A optional port number could be added with a '@':
-            * 2001:1234::1@1234
-
-            <key-name | NOKEY | BLOCKED>
-            * <key-name> will use the specified TSIG key
-            * NOKEY      no TSIG signature is required
-            * BLOCKED    notifies from non-listed or blocked IPs will be ignored
-            * ]]></screen>
-          '';
-        };
+      allowNotify = mkOption {
+        type        = types.listOf types.str;
+        default     = [ ];
+        example     = [ "192.0.2.0/24 NOKEY" "10.0.0.1-10.0.0.5 my_tsig_key_name"
+                        "10.0.3.4&255.255.0.0 BLOCKED"
+                      ];
+        description = ''
+          Listed primary servers are allowed to notify this secondary server.
+          <screen><![CDATA[
+          Format: <ip> <key-name | NOKEY | BLOCKED>
+
+          <ip> either a plain IPv4/IPv6 address or range. Valid patters for ranges:
+          * 10.0.0.0/24            # via subnet size
+          * 10.0.0.0&255.255.255.0 # via subnet mask
+          * 10.0.0.1-10.0.0.254    # via range
+
+          A optional port number could be added with a '@':
+          * 2001:1234::1@1234
+
+          <key-name | NOKEY | BLOCKED>
+          * <key-name> will use the specified TSIG key
+          * NOKEY      no TSIG signature is required
+          * BLOCKED    notifies from non-listed or blocked IPs will be ignored
+          * ]]></screen>
+        '';
+      };
 
-        requestXFR = mkOption {
-          type        = types.listOf types.str;
-          default     = [];
-          example     = [];
-          description = ''
-            Format: <code>[AXFR|UDP] &lt;ip-address&gt; &lt;key-name | NOKEY&gt;</code>
-          '';
-        };
+      requestXFR = mkOption {
+        type        = types.listOf types.str;
+        default     = [];
+        example     = [];
+        description = ''
+          Format: <code>[AXFR|UDP] &lt;ip-address&gt; &lt;key-name | NOKEY&gt;</code>
+        '';
+      };
 
-        allowAXFRFallback = mkOption {
-          type        = types.bool;
-          default     = true;
-          description = ''
-            If NSD as secondary server should be allowed to AXFR if the primary
-            server does not allow IXFR.
-          '';
-        };
+      allowAXFRFallback = mkOption {
+        type        = types.bool;
+        default     = true;
+        description = ''
+          If NSD as secondary server should be allowed to AXFR if the primary
+          server does not allow IXFR.
+        '';
+      };
 
-        notify = mkOption {
-          type        = types.listOf types.str;
-          default     = [];
-          example     = [ "10.0.0.1@3721 my_key" "::5 NOKEY" ];
-          description = ''
-            This primary server will notify all given secondary servers about
-            zone changes.
-            <screen><![CDATA[
-            Format: <ip> <key-name | NOKEY>
+      notify = mkOption {
+        type        = types.listOf types.str;
+        default     = [];
+        example     = [ "10.0.0.1@3721 my_key" "::5 NOKEY" ];
+        description = ''
+          This primary server will notify all given secondary servers about
+          zone changes.
+          <screen><![CDATA[
+          Format: <ip> <key-name | NOKEY>
 
-            <ip> a plain IPv4/IPv6 address with on optional port number (ip@port)
+          <ip> a plain IPv4/IPv6 address with on optional port number (ip@port)
 
-            <key-name | NOKEY>
-            * <key-name> sign notifies with the specified key
-            * NOKEY      don't sign notifies
-            ]]></screen>
-          '';
-        };
+          <key-name | NOKEY>
+          * <key-name> sign notifies with the specified key
+          * NOKEY      don't sign notifies
+          ]]></screen>
+        '';
+      };
 
-        notifyRetry = mkOption {
-          type        = types.int;
-          default     = 5;
-          description = ''
-            Specifies the number of retries for failed notifies. Set this along with notify.
-          '';
-        };
+      notifyRetry = mkOption {
+        type        = types.int;
+        default     = 5;
+        description = ''
+          Specifies the number of retries for failed notifies. Set this along with notify.
+        '';
+      };
 
-        provideXFR = mkOption {
-          type        = types.listOf types.str;
-          default     = [];
-          example     = [ "192.0.2.0/24 NOKEY" "192.0.2.0/24 my_tsig_key_name" ];
-          description = ''
-            Allow these IPs and TSIG to transfer zones, addr TSIG|NOKEY|BLOCKED
-            address range 192.0.2.0/24, 1.2.3.4&amp;255.255.0.0, 3.0.2.20-3.0.2.40
-          '';
-        };
+      provideXFR = mkOption {
+        type        = types.listOf types.str;
+        default     = [];
+        example     = [ "192.0.2.0/24 NOKEY" "192.0.2.0/24 my_tsig_key_name" ];
+        description = ''
+          Allow these IPs and TSIG to transfer zones, addr TSIG|NOKEY|BLOCKED
+          address range 192.0.2.0/24, 1.2.3.4&amp;255.255.0.0, 3.0.2.20-3.0.2.40
+        '';
+      };
 
-        outgoingInterface = mkOption {
-          type        = types.nullOr types.str;
-          default     = null;
-          example     = "2000::1@1234";
-          description = ''
-            This address will be used for zone-transfere requests if configured
-            as a secondary server or notifications in case of a primary server.
-            Supply either a plain IPv4 or IPv6 address with an optional port
-            number (ip@port).
-          '';
-        };
+      outgoingInterface = mkOption {
+        type        = types.nullOr types.str;
+        default     = null;
+        example     = "2000::1@1234";
+        description = ''
+          This address will be used for zone-transfere requests if configured
+          as a secondary server or notifications in case of a primary server.
+          Supply either a plain IPv4 or IPv6 address with an optional port
+          number (ip@port).
+        '';
+      };
 
-        rrlWhitelist = mkOption {
-          type        = types.listOf types.str;
-          default     = [];
-          description = ''
-            Whitelists the given rrl-types.
-            The RRL classification types are:  nxdomain,  error, referral, any,
-            rrsig, wildcard, nodata, dnskey, positive, all
-          '';
-        };
+      rrlWhitelist = mkOption {
+        type        = types.listOf types.str;
+        default     = [];
+        description = ''
+          Whitelists the given rrl-types.
+          The RRL classification types are:  nxdomain,  error, referral, any,
+          rrsig, wildcard, nodata, dnskey, positive, all
+        '';
+      };
 
-        data = mkOption {
-          type        = types.str;
-          default     = "";
-          example     = "";
-          description = ''
-            The actual zone data. This is the content of your zone file.
-            Use imports or pkgs.lib.readFile if you don't want this data in your config file.
-          '';
-        };
+      data = mkOption {
+        type        = types.str;
+        default     = "";
+        example     = "";
+        description = ''
+          The actual zone data. This is the content of your zone file.
+          Use imports or pkgs.lib.readFile if you don't want this data in your config file.
+        '';
+      };
 
+      zoneStats = mkOption {
+        type        = types.nullOr types.str;
+        default     = null;
+        example     = "%s";
+        description = ''
+          When set to something distinct to null NSD is able to collect
+          statistics per zone. All statistics of this zone(s) will be added
+          to the group specified by this given name. Use "%s" to use the zones
+          name as the group. The groups are output from nsd-control stats
+          and stats_noreset.
+        '';
       };
-    }
-  );
+    };
+  };
 
 in
 {
@@ -291,6 +308,15 @@ in
         '';
       };
 
+      bind8Stats = mkOption {
+        type        = types.bool;
+        default     = false;
+        example     = true;
+        description = ''
+          Wheter to enable BIND8 like statisics.
+        '';
+      };
+
       rootServer = mkOption {
         type        = types.bool;
         default     = false;
@@ -456,191 +482,162 @@ in
       };
 
 
-      ratelimit = mkOption {
-        type = types.submodule (
-          { options, ... }:
-          { options = {
-
-              enable = mkOption {
-                type        = types.bool;
-                default     = false;
-                description = ''
-                  Enable ratelimit capabilities.
-                '';
-              };
-
-              size = mkOption {
-                type        = types.int;
-                default     = 1000000;
-                description = ''
-                  Size of the hashtable. More buckets use more memory but lower
-                  the chance of hash hash collisions.
-                '';
-              };
+      ratelimit = {
+        enable = mkOption {
+          type        = types.bool;
+          default     = false;
+          description = ''
+            Enable ratelimit capabilities.
+          '';
+        };
 
-              ratelimit = mkOption {
-                type        = types.int;
-                default     = 200;
-                description = ''
-                  Max qps allowed from any query source.
-                  0 means unlimited. With an verbosity of 2 blocked and
-                  unblocked subnets will be logged.
-                '';
-              };
+        size = mkOption {
+          type        = types.int;
+          default     = 1000000;
+          description = ''
+            Size of the hashtable. More buckets use more memory but lower
+            the chance of hash hash collisions.
+          '';
+        };
 
-              whitelistRatelimit = mkOption {
-                type        = types.int;
-                default     = 2000;
-                description = ''
-                  Max qps allowed from whitelisted sources.
-                  0 means unlimited. Set the rrl-whitelist option for specific
-                  queries to apply this limit instead of the default to them.
-                '';
-              };
+        ratelimit = mkOption {
+          type        = types.int;
+          default     = 200;
+          description = ''
+            Max qps allowed from any query source.
+            0 means unlimited. With an verbosity of 2 blocked and
+            unblocked subnets will be logged.
+          '';
+        };
 
-              slip = mkOption {
-                type        = types.nullOr types.int;
-                default     = null;
-                description = ''
-                  Number of packets that get discarded before replying a SLIP response.
-                  0 disables SLIP responses. 1 will make every response a SLIP response.
-                '';
-              };
+        whitelistRatelimit = mkOption {
+          type        = types.int;
+          default     = 2000;
+          description = ''
+            Max qps allowed from whitelisted sources.
+            0 means unlimited. Set the rrl-whitelist option for specific
+            queries to apply this limit instead of the default to them.
+          '';
+        };
 
-              ipv4PrefixLength = mkOption {
-                type        = types.nullOr types.int;
-                default     = null;
-                description = ''
-                  IPv4 prefix length. Addresses are grouped by netblock.
-                '';
-              };
+        slip = mkOption {
+          type        = types.nullOr types.int;
+          default     = null;
+          description = ''
+            Number of packets that get discarded before replying a SLIP response.
+            0 disables SLIP responses. 1 will make every response a SLIP response.
+          '';
+        };
 
-              ipv6PrefixLength = mkOption {
-                type        = types.nullOr types.int;
-                default     = null;
-                description = ''
-                  IPv6 prefix length. Addresses are grouped by netblock.
-                '';
-              };
+        ipv4PrefixLength = mkOption {
+          type        = types.nullOr types.int;
+          default     = null;
+          description = ''
+            IPv4 prefix length. Addresses are grouped by netblock.
+          '';
+        };
 
-            };
-          });
-        default = {
+        ipv6PrefixLength = mkOption {
+          type        = types.nullOr types.int;
+          default     = null;
+          description = ''
+            IPv6 prefix length. Addresses are grouped by netblock.
+          '';
         };
-        example = {};
-        description = ''
-        '';
       };
 
 
-      remoteControl = mkOption {
-        type = types.submodule (
-          { config, options, ... }:
-          { options = {
-
-              enable = mkOption {
-                type        = types.bool;
-                default     = false;
-                description = ''
-                  Wheter to enable remote control via nsd-control(8).
-                '';
-              };
-
-              interfaces = mkOption {
-                type        = types.listOf types.str;
-                default     = [ "127.0.0.1" "::1" ];
-                description = ''
-                  Which interfaces NSD should bind to for remote control.
-                '';
-              };
-
-              port = mkOption {
-                type        = types.int;
-                default     = 8952;
-                description = ''
-                  Port number for remote control operations (uses TLS over TCP).
-                '';
-              };
+      remoteControl = {
+        enable = mkOption {
+          type        = types.bool;
+          default     = false;
+          description = ''
+            Wheter to enable remote control via nsd-control(8).
+          '';
+        };
 
-              serverKeyFile = mkOption {
-                type        = types.path;
-                default     = "/etc/nsd/nsd_server.key";
-                description = ''
-                  Path to the server private key, which is used by the server
-                  but not by nsd-control. This file is generated by nsd-control-setup.
-                '';
-              };
+        interfaces = mkOption {
+          type        = types.listOf types.str;
+          default     = [ "127.0.0.1" "::1" ];
+          description = ''
+            Which interfaces NSD should bind to for remote control.
+          '';
+        };
 
-              serverCertFile = mkOption {
-                type        = types.path;
-                default     = "/etc/nsd/nsd_server.pem";
-                description = ''
-                  Path to the server self signed certificate, which is used by the server
-                  but and by nsd-control. This file is generated by nsd-control-setup.
-                '';
-              };
+        port = mkOption {
+          type        = types.int;
+          default     = 8952;
+          description = ''
+            Port number for remote control operations (uses TLS over TCP).
+          '';
+        };
 
-              controlKeyFile = mkOption {
-                type        = types.path;
-                default     = "/etc/nsd/nsd_control.key";
-                description = ''
-                  Path to the client private key, which is used by nsd-control
-                  but not by the server. This file is generated by nsd-control-setup.
-                '';
-              };
+        serverKeyFile = mkOption {
+          type        = types.path;
+          default     = "/etc/nsd/nsd_server.key";
+          description = ''
+            Path to the server private key, which is used by the server
+            but not by nsd-control. This file is generated by nsd-control-setup.
+          '';
+        };
 
-              controlCertFile = mkOption {
-                type        = types.path;
-                default     = "/etc/nsd/nsd_control.pem";
-                description = ''
-                  Path to the client certificate signed with the server certificate.
-                  This file is used by nsd-control and generated by nsd-control-setup.
-                '';
-              };
+        serverCertFile = mkOption {
+          type        = types.path;
+          default     = "/etc/nsd/nsd_server.pem";
+          description = ''
+            Path to the server self signed certificate, which is used by the server
+            but and by nsd-control. This file is generated by nsd-control-setup.
+          '';
+        };
 
-            };
+        controlKeyFile = mkOption {
+          type        = types.path;
+          default     = "/etc/nsd/nsd_control.key";
+          description = ''
+            Path to the client private key, which is used by nsd-control
+            but not by the server. This file is generated by nsd-control-setup.
+          '';
+        };
 
-          });
-        default = {
+        controlCertFile = mkOption {
+          type        = types.path;
+          default     = "/etc/nsd/nsd_control.pem";
+          description = ''
+            Path to the client certificate signed with the server certificate.
+            This file is used by nsd-control and generated by nsd-control-setup.
+          '';
         };
-        example = {};
-        description = ''
-        '';
       };
 
 
       keys = mkOption {
-        type = types.attrsOf (types.submodule (
-          { options, ... }:
-          { options = {
-
-              algorithm = mkOption {
-                type        = types.str;
-                default     = "hmac-sha256";
-                description = ''
-                  Authentication algorithm for this key.
-                '';
-              };
-
-              keyFile = mkOption {
-                type        = types.path;
-                description = ''
-                  Path to the file which contains the actual base64 encoded
-                  key. The key will be copied into "${stateDir}/private" before
-                  NSD starts. The copied file is only accessibly by the NSD
-                  user.
-                '';
-              };
+        type = types.attrsOf (types.submodule {
+          options = {
+            algorithm = mkOption {
+              type        = types.str;
+              default     = "hmac-sha256";
+              description = ''
+                Authentication algorithm for this key.
+              '';
+            };
 
+            keyFile = mkOption {
+              type        = types.path;
+              description = ''
+                Path to the file which contains the actual base64 encoded
+                key. The key will be copied into "${stateDir}/private" before
+                NSD starts. The copied file is only accessibly by the NSD
+                user.
+              '';
             };
-          }));
-        default = {
-        };
+          };
+        });
+        default = {};
         example = {
-            "tsig.example.org" = {
-              algorithm = "hmac-md5";
-              secret    = "aaaaaabbbbbbccccccdddddd";
-            };
+          "tsig.example.org" = {
+            algorithm = "hmac-md5";
+            secret    = "aaaaaabbbbbbccccccdddddd";
+          };
         };
         description = ''
           Define your TSIG keys here.
@@ -651,32 +648,32 @@ in
         type        = types.attrsOf zoneOptions;
         default     = {};
         example     = {
-            "serverGroup1" = {
-                provideXFR = [ "10.1.2.3 NOKEY" ];
-                children = {
-                    "example.com." = {
-                        data = ''
-                          $ORIGIN example.com.
-                          $TTL    86400
-                          @ IN SOA a.ns.example.com. admin.example.com. (
-                          ...
-                        '';
-                    };
-                    "example.org." = {
-                        data = ''
-                          $ORIGIN example.org.
-                          $TTL    86400
-                          @ IN SOA a.ns.example.com. admin.example.com. (
-                          ...
-                        '';
-                    };
-                };
+          "serverGroup1" = {
+            provideXFR = [ "10.1.2.3 NOKEY" ];
+            children = {
+              "example.com." = {
+                data = ''
+                  $ORIGIN example.com.
+                  $TTL    86400
+                  @ IN SOA a.ns.example.com. admin.example.com. (
+                  ...
+                '';
+              };
+              "example.org." = {
+                data = ''
+                  $ORIGIN example.org.
+                  $TTL    86400
+                  @ IN SOA a.ns.example.com. admin.example.com. (
+                  ...
+                '';
+              };
             };
+          };
 
-            "example.net." = {
-                provideXFR = [ "10.3.2.1 NOKEY" ];
-                data = ''...'';
-            };
+          "example.net." = {
+            provideXFR = [ "10.3.2.1 NOKEY" ];
+            data = ''...'';
+          };
         };
         description = ''
           Define your zones here. Zones can cascade other zones and therefore
@@ -693,25 +690,18 @@ in
 
   config = mkIf cfg.enable {
 
-    # this is not working :(
-    nixpkgs.config.nsd = {
-        ipv6       = cfg.ipv6;
-        ratelimit  = cfg.ratelimit.enable;
-        rootServer = cfg.rootServer;
-    };
-
     users.extraGroups = singleton {
-        name = username;
-        gid  = config.ids.gids.nsd;
+      name = username;
+      gid  = config.ids.gids.nsd;
     };
 
     users.extraUsers = singleton {
-        name        = username;
-        description = "NSD service user";
-        home        = stateDir;
-        createHome  = true;
-        uid         = config.ids.uids.nsd;
-        group       = username;
+      name        = username;
+      description = "NSD service user";
+      home        = stateDir;
+      createHome  = true;
+      uid         = config.ids.uids.nsd;
+      group       = username;
     };
 
     systemd.services.nsd = {
@@ -720,10 +710,9 @@ in
       after       = [ "network.target" ];
 
       serviceConfig = {
-        Type      = "forking";
         PIDFile   = pidFile;
         Restart   = "always";
-        ExecStart = "${pkgs.nsd}/sbin/nsd -c ${configFile}";
+        ExecStart = "${nsdPkg}/sbin/nsd -d -c ${configFile}";
       };
 
       preStart = ''