diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index 5058d41bf75..db8b66c9768 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -284,6 +284,7 @@
glance = 266;
couchpotato = 267;
gogs = 268;
+ pdns-recursor = 269;
# When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index e99e344b932..f3996564fd9 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -426,6 +426,7 @@
./services/networking/pdnsd.nix
./services/networking/polipo.nix
./services/networking/powerdns.nix
+ ./services/networking/pdns-recursor.nix
./services/networking/pptpd.nix
./services/networking/prayer.nix
./services/networking/privoxy.nix
diff --git a/nixos/modules/services/networking/pdns-recursor.nix b/nixos/modules/services/networking/pdns-recursor.nix
new file mode 100644
index 00000000000..26be72d2a61
--- /dev/null
+++ b/nixos/modules/services/networking/pdns-recursor.nix
@@ -0,0 +1,168 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ dataDir = "/var/lib/pdns-recursor";
+ username = "pdns-recursor";
+
+ cfg = config.services.pdns-recursor;
+ zones = mapAttrsToList (zone: uri: "${zone}.=${uri}") cfg.forwardZones;
+
+ configFile = pkgs.writeText "recursor.conf" ''
+ local-address=${cfg.dns.address}
+ local-port=${toString cfg.dns.port}
+ allow-from=${concatStringsSep "," cfg.dns.allowFrom}
+
+ webserver-address=${cfg.api.address}
+ webserver-port=${toString cfg.api.port}
+ webserver-allow-from=${concatStringsSep "," cfg.api.allowFrom}
+
+ forward-zones=${concatStringsSep "," zones}
+ export-etc-hosts=${if cfg.exportHosts then "yes" else "no"}
+ dnssec=${cfg.dnssecValidation}
+ serve-rfc1918=${if cfg.serveRFC1918 then "yes" else "no"}
+
+ ${cfg.extraConfig}
+ '';
+
+in {
+ options.services.pdns-recursor = {
+ enable = mkEnableOption "PowerDNS Recursor, a recursive DNS server";
+
+ dns.address = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = ''
+ IP address Recursor DNS server will bind to.
+ '';
+ };
+
+ dns.port = mkOption {
+ type = types.int;
+ default = 53;
+ description = ''
+ Port number Recursor DNS server will bind to.
+ '';
+ };
+
+ dns.allowFrom = mkOption {
+ type = types.listOf types.str;
+ default = [ "10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/16" ];
+ example = [ "0.0.0.0/0" ];
+ description = ''
+ IP address ranges of clients allowed to make DNS queries.
+ '';
+ };
+
+ api.address = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = ''
+ IP address Recursor REST API server will bind to.
+ '';
+ };
+
+ api.port = mkOption {
+ type = types.int;
+ default = 8082;
+ description = ''
+ Port number Recursor REST API server will bind to.
+ '';
+ };
+
+ api.allowFrom = mkOption {
+ type = types.listOf types.str;
+ default = [ "0.0.0.0/0" ];
+ description = ''
+ IP address ranges of clients allowed to make API requests.
+ '';
+ };
+
+ exportHosts = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to export names and IP addresses defined in /etc/hosts.
+ '';
+ };
+
+ forwardZones = mkOption {
+ type = types.attrs;
+ example = { eth = "127.0.0.1:5353"; };
+ default = {};
+ description = ''
+ DNS zones to be forwarded to other servers.
+ '';
+ };
+
+ dnssecValidation = mkOption {
+ type = types.enum ["off" "process-no-validate" "process" "log-fail" "validate"];
+ default = "validate";
+ description = ''
+ Controls the level of DNSSEC processing done by the PowerDNS Recursor.
+ See https://doc.powerdns.com/md/recursor/dnssec/ for a detailed explanation.
+ '';
+ };
+
+ serveRFC1918 = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to directly resolve the RFC1918 reverse-mapping domains:
+ 10.in-addr.arpa,
+ 168.192.in-addr.arpa,
+ 16-31.172.in-addr.arpa
+ This saves load on the AS112 servers.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra options to be appended to the configuration file.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ users.extraUsers."${username}" = {
+ home = dataDir;
+ createHome = true;
+ uid = config.ids.uids.pdns-recursor;
+ description = "PowerDNS Recursor daemon user";
+ };
+
+ systemd.services.pdns-recursor = {
+ unitConfig.Documentation = "man:pdns_recursor(1) man:rec_control(1)";
+ description = "PowerDNS recursive server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ User = username;
+ Restart ="on-failure";
+ RestartSec = "5";
+ PrivateTmp = true;
+ PrivateDevices = true;
+ AmbientCapabilities = "cap_net_bind_service";
+ ExecStart = ''${pkgs.pdns-recursor}/bin/pdns_recursor \
+ --config-dir=${dataDir} \
+ --socket-dir=${dataDir} \
+ --disable-syslog
+ '';
+ };
+
+ preStart = ''
+ # Link configuration file into recursor home directory
+ configPath=${dataDir}/recursor.conf
+ if [ "$(realpath $configPath)" != "${configFile}" ]; then
+ rm -f $configPath
+ ln -s ${configFile} $configPath
+ fi
+ '';
+ };
+ };
+}