about summary refs log tree commit diff
path: root/nixpkgs/nixos/modules/services/web-servers
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/modules/services/web-servers')
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix719
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix180
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/caddy.nix105
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix77
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix72
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/hitch/default.nix108
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/hydron.nix165
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh72
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/jboss/default.nix80
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/lighttpd/cgit.nix91
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix58
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix256
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/lighttpd/gitweb.nix52
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/meguca.nix174
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix132
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/minio.nix108
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/default.nix709
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix61
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix95
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix228
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix279
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/shellinabox.nix122
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/tomcat.nix425
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/traefik.nix124
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/unit/default.nix125
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/uwsgi.nix160
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/varnish/default.nix113
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/zope2.nix258
28 files changed, 5148 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix
new file mode 100644
index 000000000000..098160ee3692
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -0,0 +1,719 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  mainCfg = config.services.httpd;
+
+  httpd = mainCfg.package.out;
+
+  httpdConf = mainCfg.configFile;
+
+  php = mainCfg.phpPackage.override { apacheHttpd = httpd.dev; /* otherwise it only gets .out */ };
+
+  phpMajorVersion = head (splitString "." php.version);
+
+  mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = httpd; };
+
+  defaultListen = cfg: if cfg.enableSSL
+    then [{ip = "*"; port = 443;}]
+    else [{ip = "*"; port = 80;}];
+
+  getListen = cfg:
+    if cfg.listen == []
+      then defaultListen cfg
+      else cfg.listen;
+
+  listenToString = l: "${l.ip}:${toString l.port}";
+
+  extraModules = attrByPath ["extraModules"] [] mainCfg;
+  extraForeignModules = filter isAttrs extraModules;
+  extraApacheModules = filter isString extraModules;
+
+
+  makeServerInfo = cfg: {
+    # Canonical name must not include a trailing slash.
+    canonicalNames =
+      let defaultPort = (head (defaultListen cfg)).port; in
+      map (port:
+        (if cfg.enableSSL then "https" else "http") + "://" +
+        cfg.hostName +
+        (if port != defaultPort then ":${toString port}" else "")
+        ) (map (x: x.port) (getListen cfg));
+
+    # Admin address: inherit from the main server if not specified for
+    # a virtual host.
+    adminAddr = if cfg.adminAddr != null then cfg.adminAddr else mainCfg.adminAddr;
+
+    vhostConfig = cfg;
+    serverConfig = mainCfg;
+    fullConfig = config; # machine config
+  };
+
+
+  allHosts = [mainCfg] ++ mainCfg.virtualHosts;
+
+
+  callSubservices = serverInfo: defs:
+    let f = svc:
+      let
+        svcFunction =
+          if svc ? function then svc.function
+          # instead of using serviceType="mediawiki"; you can copy mediawiki.nix to any location outside nixpkgs, modify it at will, and use serviceExpression=./mediawiki.nix;
+          else if svc ? serviceExpression then import (toString svc.serviceExpression)
+          else import (toString "${toString ./.}/${if svc ? serviceType then svc.serviceType else svc.serviceName}.nix");
+        config = (evalModules
+          { modules = [ { options = res.options; config = svc.config or svc; } ];
+            check = false;
+          }).config;
+        defaults = {
+          extraConfig = "";
+          extraModules = [];
+          extraModulesPre = [];
+          extraPath = [];
+          extraServerPath = [];
+          globalEnvVars = [];
+          robotsEntries = "";
+          startupScript = "";
+          enablePHP = false;
+          enablePerl = false;
+          phpOptions = "";
+          options = {};
+          documentRoot = null;
+        };
+        res = defaults // svcFunction { inherit config lib pkgs serverInfo php; };
+      in res;
+    in map f defs;
+
+
+  # !!! callSubservices is expensive
+  subservicesFor = cfg: callSubservices (makeServerInfo cfg) cfg.extraSubservices;
+
+  mainSubservices = subservicesFor mainCfg;
+
+  allSubservices = mainSubservices ++ concatMap subservicesFor mainCfg.virtualHosts;
+
+
+  enableSSL = any (vhost: vhost.enableSSL) allHosts;
+
+
+  # Names of modules from ${httpd}/modules that we want to load.
+  apacheModules =
+    [ # HTTP authentication mechanisms: basic and digest.
+      "auth_basic" "auth_digest"
+
+      # Authentication: is the user who he claims to be?
+      "authn_file" "authn_dbm" "authn_anon" "authn_core"
+
+      # Authorization: is the user allowed access?
+      "authz_user" "authz_groupfile" "authz_host" "authz_core"
+
+      # Other modules.
+      "ext_filter" "include" "log_config" "env" "mime_magic"
+      "cern_meta" "expires" "headers" "usertrack" /* "unique_id" */ "setenvif"
+      "mime" "dav" "status" "autoindex" "asis" "info" "dav_fs"
+      "vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling"
+      "userdir" "alias" "rewrite" "proxy" "proxy_http"
+      "unixd" "cache" "cache_disk" "slotmem_shm" "socache_shmcb"
+      "mpm_${mainCfg.multiProcessingModule}"
+
+      # For compatibility with old configurations, the new module mod_access_compat is provided.
+      "access_compat"
+    ]
+    ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
+    ++ optional enableSSL "ssl"
+    ++ extraApacheModules;
+
+
+  allDenied = "Require all denied";
+  allGranted = "Require all granted";
+
+
+  loggingConf = (if mainCfg.logFormat != "none" then ''
+    ErrorLog ${mainCfg.logDir}/error.log
+
+    LogLevel notice
+
+    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
+    LogFormat "%h %l %u %t \"%r\" %>s %b" common
+    LogFormat "%{Referer}i -> %U" referer
+    LogFormat "%{User-agent}i" agent
+
+    CustomLog ${mainCfg.logDir}/access.log ${mainCfg.logFormat}
+  '' else ''
+    ErrorLog /dev/null
+  '');
+
+
+  browserHacks = ''
+    BrowserMatch "Mozilla/2" nokeepalive
+    BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
+    BrowserMatch "RealPlayer 4\.0" force-response-1.0
+    BrowserMatch "Java/1\.0" force-response-1.0
+    BrowserMatch "JDK/1\.0" force-response-1.0
+    BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
+    BrowserMatch "^WebDrive" redirect-carefully
+    BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
+    BrowserMatch "^gnome-vfs" redirect-carefully
+  '';
+
+
+  sslConf = ''
+    SSLSessionCache shmcb:${mainCfg.stateDir}/ssl_scache(512000)
+
+    Mutex posixsem
+
+    SSLRandomSeed startup builtin
+    SSLRandomSeed connect builtin
+
+    SSLProtocol ${mainCfg.sslProtocols}
+    SSLCipherSuite ${mainCfg.sslCiphers}
+    SSLHonorCipherOrder on
+  '';
+
+
+  mimeConf = ''
+    TypesConfig ${httpd}/conf/mime.types
+
+    AddType application/x-x509-ca-cert .crt
+    AddType application/x-pkcs7-crl    .crl
+    AddType application/x-httpd-php    .php .phtml
+
+    <IfModule mod_mime_magic.c>
+        MIMEMagicFile ${httpd}/conf/magic
+    </IfModule>
+  '';
+
+
+  perServerConf = isMainServer: cfg: let
+
+    serverInfo = makeServerInfo cfg;
+
+    subservices = callSubservices serverInfo cfg.extraSubservices;
+
+    maybeDocumentRoot = fold (svc: acc:
+      if acc == null then svc.documentRoot else assert svc.documentRoot == null; acc
+    ) null ([ cfg ] ++ subservices);
+
+    documentRoot = if maybeDocumentRoot != null then maybeDocumentRoot else
+      pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out";
+
+    documentRootConf = ''
+      DocumentRoot "${documentRoot}"
+
+      <Directory "${documentRoot}">
+          Options Indexes FollowSymLinks
+          AllowOverride None
+          ${allGranted}
+      </Directory>
+    '';
+
+    robotsTxt =
+      concatStringsSep "\n" (filter (x: x != "") (
+        # If this is a vhost, the include the entries for the main server as well.
+        (if isMainServer then [] else [mainCfg.robotsEntries] ++ map (svc: svc.robotsEntries) mainSubservices)
+        ++ [cfg.robotsEntries]
+        ++ (map (svc: svc.robotsEntries) subservices)));
+
+  in ''
+    ${concatStringsSep "\n" (map (n: "ServerName ${n}") serverInfo.canonicalNames)}
+
+    ${concatMapStrings (alias: "ServerAlias ${alias}\n") cfg.serverAliases}
+
+    ${if cfg.sslServerCert != null then ''
+      SSLCertificateFile ${cfg.sslServerCert}
+      SSLCertificateKeyFile ${cfg.sslServerKey}
+      ${if cfg.sslServerChain != null then ''
+        SSLCertificateChainFile ${cfg.sslServerChain}
+      '' else ""}
+    '' else ""}
+
+    ${if cfg.enableSSL then ''
+      SSLEngine on
+    '' else if enableSSL then /* i.e., SSL is enabled for some host, but not this one */
+    ''
+      SSLEngine off
+    '' else ""}
+
+    ${if isMainServer || cfg.adminAddr != null then ''
+      ServerAdmin ${cfg.adminAddr}
+    '' else ""}
+
+    ${if !isMainServer && mainCfg.logPerVirtualHost then ''
+      ErrorLog ${mainCfg.logDir}/error-${cfg.hostName}.log
+      CustomLog ${mainCfg.logDir}/access-${cfg.hostName}.log ${cfg.logFormat}
+    '' else ""}
+
+    ${optionalString (robotsTxt != "") ''
+      Alias /robots.txt ${pkgs.writeText "robots.txt" robotsTxt}
+    ''}
+
+    ${if isMainServer || maybeDocumentRoot != null then documentRootConf else ""}
+
+    ${if cfg.enableUserDir then ''
+
+      UserDir public_html
+      UserDir disabled root
+
+      <Directory "/home/*/public_html">
+          AllowOverride FileInfo AuthConfig Limit Indexes
+          Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
+          <Limit GET POST OPTIONS>
+              ${allGranted}
+          </Limit>
+          <LimitExcept GET POST OPTIONS>
+              ${allDenied}
+          </LimitExcept>
+      </Directory>
+
+    '' else ""}
+
+    ${if cfg.globalRedirect != null && cfg.globalRedirect != "" then ''
+      RedirectPermanent / ${cfg.globalRedirect}
+    '' else ""}
+
+    ${
+      let makeFileConf = elem: ''
+            Alias ${elem.urlPath} ${elem.file}
+          '';
+      in concatMapStrings makeFileConf cfg.servedFiles
+    }
+
+    ${
+      let makeDirConf = elem: ''
+            Alias ${elem.urlPath} ${elem.dir}/
+            <Directory ${elem.dir}>
+                Options +Indexes
+                ${allGranted}
+                AllowOverride All
+            </Directory>
+          '';
+      in concatMapStrings makeDirConf cfg.servedDirs
+    }
+
+    ${concatMapStrings (svc: svc.extraConfig) subservices}
+
+    ${cfg.extraConfig}
+  '';
+
+
+  confFile = pkgs.writeText "httpd.conf" ''
+
+    ServerRoot ${httpd}
+
+    DefaultRuntimeDir ${mainCfg.stateDir}/runtime
+
+    PidFile ${mainCfg.stateDir}/httpd.pid
+
+    ${optionalString (mainCfg.multiProcessingModule != "prefork") ''
+      # mod_cgid requires this.
+      ScriptSock ${mainCfg.stateDir}/cgisock
+    ''}
+
+    <IfModule prefork.c>
+        MaxClients           ${toString mainCfg.maxClients}
+        MaxRequestsPerChild  ${toString mainCfg.maxRequestsPerChild}
+    </IfModule>
+
+    ${let
+        listen = concatMap getListen allHosts;
+        toStr = listen: "Listen ${listenToString listen}\n";
+        uniqueListen = uniqList {inputList = map toStr listen;};
+      in concatStrings uniqueListen
+    }
+
+    User ${mainCfg.user}
+    Group ${mainCfg.group}
+
+    ${let
+        load = {name, path}: "LoadModule ${name}_module ${path}\n";
+        allModules =
+          concatMap (svc: svc.extraModulesPre) allSubservices
+          ++ map (name: {inherit name; path = "${httpd}/modules/mod_${name}.so";}) apacheModules
+          ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
+          ++ optional enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
+          ++ optional enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
+          ++ concatMap (svc: svc.extraModules) allSubservices
+          ++ extraForeignModules;
+      in concatMapStrings load (unique allModules)
+    }
+
+    AddHandler type-map var
+
+    <Files ~ "^\.ht">
+        ${allDenied}
+    </Files>
+
+    ${mimeConf}
+    ${loggingConf}
+    ${browserHacks}
+
+    Include ${httpd}/conf/extra/httpd-default.conf
+    Include ${httpd}/conf/extra/httpd-autoindex.conf
+    Include ${httpd}/conf/extra/httpd-multilang-errordoc.conf
+    Include ${httpd}/conf/extra/httpd-languages.conf
+
+    TraceEnable off
+
+    ${if enableSSL then sslConf else ""}
+
+    # Fascist default - deny access to everything.
+    <Directory />
+        Options FollowSymLinks
+        AllowOverride None
+        ${allDenied}
+    </Directory>
+
+    # But do allow access to files in the store so that we don't have
+    # to generate <Directory> clauses for every generated file that we
+    # want to serve.
+    <Directory /nix/store>
+        ${allGranted}
+    </Directory>
+
+    # Generate directives for the main server.
+    ${perServerConf true mainCfg}
+
+    ${let
+        makeVirtualHost = vhost: ''
+          <VirtualHost ${concatStringsSep " " (map listenToString (getListen vhost))}>
+              ${perServerConf false vhost}
+          </VirtualHost>
+        '';
+      in concatMapStrings makeVirtualHost mainCfg.virtualHosts
+    }
+  '';
+
+
+  enablePHP = mainCfg.enablePHP || any (svc: svc.enablePHP) allSubservices;
+
+  enablePerl = mainCfg.enablePerl || any (svc: svc.enablePerl) allSubservices;
+
+
+  # Generate the PHP configuration file.  Should probably be factored
+  # out into a separate module.
+  phpIni = pkgs.runCommand "php.ini"
+    { options = concatStringsSep "\n"
+        ([ mainCfg.phpOptions ] ++ (map (svc: svc.phpOptions) allSubservices));
+      preferLocalBuild = true;
+    }
+    ''
+      cat ${php}/etc/php.ini > $out
+      echo "$options" >> $out
+    '';
+
+in
+
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.httpd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the Apache HTTP Server.";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.apacheHttpd;
+        defaultText = "pkgs.apacheHttpd";
+        description = ''
+          Overridable attribute of the Apache HTTP Server package to use.
+        '';
+      };
+
+      configFile = mkOption {
+        type = types.path;
+        default = confFile;
+        defaultText = "confFile";
+        example = literalExample ''pkgs.writeText "httpd.conf" "# my custom config file ..."'';
+        description = ''
+          Override the configuration file used by Apache. By default,
+          NixOS generates one automatically.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Cnfiguration lines appended to the generated Apache
+          configuration file. Note that this mechanism may not work
+          when <option>configFile</option> is overridden.
+        '';
+      };
+
+      extraModules = mkOption {
+        type = types.listOf types.unspecified;
+        default = [];
+        example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${pkgs.php}/modules/libphp5.so"; } ]'';
+        description = ''
+          Additional Apache modules to be used.  These can be
+          specified as a string in the case of modules distributed
+          with Apache, or as an attribute set specifying the
+          <varname>name</varname> and <varname>path</varname> of the
+          module.
+        '';
+      };
+
+      logPerVirtualHost = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If enabled, each virtual host gets its own
+          <filename>access.log</filename> and
+          <filename>error.log</filename>, namely suffixed by the
+          <option>hostName</option> of the virtual host.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "wwwrun";
+        description = ''
+          User account under which httpd runs.  The account is created
+          automatically if it doesn't exist.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "wwwrun";
+        description = ''
+          Group under which httpd runs.  The account is created
+          automatically if it doesn't exist.
+        '';
+      };
+
+      logDir = mkOption {
+        type = types.path;
+        default = "/var/log/httpd";
+        description = ''
+          Directory for Apache's log files.  It is created automatically.
+        '';
+      };
+
+      stateDir = mkOption {
+        type = types.path;
+        default = "/run/httpd";
+        description = ''
+          Directory for Apache's transient runtime state (such as PID
+          files).  It is created automatically.  Note that the default,
+          <filename>/run/httpd</filename>, is deleted at boot time.
+        '';
+      };
+
+      virtualHosts = mkOption {
+        type = types.listOf (types.submodule (
+          { options = import ./per-server-options.nix {
+              inherit lib;
+              forMainServer = false;
+            };
+          }));
+        default = [];
+        example = [
+          { hostName = "foo";
+            documentRoot = "/data/webroot-foo";
+          }
+          { hostName = "bar";
+            documentRoot = "/data/webroot-bar";
+          }
+        ];
+        description = ''
+          Specification of the virtual hosts served by Apache.  Each
+          element should be an attribute set specifying the
+          configuration of the virtual host.  The available options
+          are the non-global options permissible for the main host.
+        '';
+      };
+
+      enableMellon = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the mod_auth_mellon module.";
+      };
+
+      enablePHP = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the PHP module.";
+      };
+
+      phpPackage = mkOption {
+        type = types.package;
+        default = pkgs.php;
+        defaultText = "pkgs.php";
+        description = ''
+          Overridable attribute of the PHP package to use.
+        '';
+      };
+
+      enablePerl = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the Perl module (mod_perl).";
+      };
+
+      phpOptions = mkOption {
+        type = types.lines;
+        default = "";
+        example =
+          ''
+            date.timezone = "CET"
+          '';
+        description =
+          "Options appended to the PHP configuration file <filename>php.ini</filename>.";
+      };
+
+      multiProcessingModule = mkOption {
+        type = types.str;
+        default = "prefork";
+        example = "worker";
+        description =
+          ''
+            Multi-processing module to be used by Apache.  Available
+            modules are <literal>prefork</literal> (the default;
+            handles each request in a separate child process),
+            <literal>worker</literal> (hybrid approach that starts a
+            number of child processes each running a number of
+            threads) and <literal>event</literal> (a recent variant of
+            <literal>worker</literal> that handles persistent
+            connections more efficiently).
+          '';
+      };
+
+      maxClients = mkOption {
+        type = types.int;
+        default = 150;
+        example = 8;
+        description = "Maximum number of httpd processes (prefork)";
+      };
+
+      maxRequestsPerChild = mkOption {
+        type = types.int;
+        default = 0;
+        example = 500;
+        description =
+          "Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited";
+      };
+
+      sslCiphers = mkOption {
+        type = types.str;
+        default = "HIGH:!aNULL:!MD5:!EXP";
+        description = "Cipher Suite available for negotiation in SSL proxy handshake.";
+      };
+
+      sslProtocols = mkOption {
+        type = types.str;
+        default = "All -SSLv2 -SSLv3 -TLSv1";
+        example = "All -SSLv2 -SSLv3";
+        description = "Allowed SSL/TLS protocol versions.";
+      };
+    }
+
+    # Include the options shared between the main server and virtual hosts.
+    // (import ./per-server-options.nix {
+      inherit lib;
+      forMainServer = true;
+    });
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.httpd.enable {
+
+    assertions = [ { assertion = mainCfg.enableSSL == true
+                               -> mainCfg.sslServerCert != null
+                                    && mainCfg.sslServerKey != null;
+                     message = "SSL is enabled for httpd, but sslServerCert and/or sslServerKey haven't been specified."; }
+                 ];
+
+    warnings = map (cfg: "apache-httpd's extraSubservices option is deprecated. Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.") (lib.filter (cfg: cfg.extraSubservices != []) allHosts);
+
+    users.users = optionalAttrs (mainCfg.user == "wwwrun") (singleton
+      { name = "wwwrun";
+        group = mainCfg.group;
+        description = "Apache httpd user";
+        uid = config.ids.uids.wwwrun;
+      });
+
+    users.groups = optionalAttrs (mainCfg.group == "wwwrun") (singleton
+      { name = "wwwrun";
+        gid = config.ids.gids.wwwrun;
+      });
+
+    environment.systemPackages = [httpd] ++ concatMap (svc: svc.extraPath) allSubservices;
+
+    services.httpd.phpOptions =
+      ''
+        ; Needed for PHP's mail() function.
+        sendmail_path = sendmail -t -i
+
+        ; Don't advertise PHP
+        expose_php = off
+      '' + optionalString (config.time.timeZone != null) ''
+
+        ; Apparently PHP doesn't use $TZ.
+        date.timezone = "${config.time.timeZone}"
+      '';
+
+    systemd.services.httpd =
+      { description = "Apache HTTPD";
+
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" "fs.target" ];
+
+        path =
+          [ httpd pkgs.coreutils pkgs.gnugrep ]
+          ++ optional enablePHP pkgs.system-sendmail # Needed for PHP's mail() function.
+          ++ concatMap (svc: svc.extraServerPath) allSubservices;
+
+        environment =
+          optionalAttrs enablePHP { PHPRC = phpIni; }
+          // optionalAttrs mainCfg.enableMellon { LD_LIBRARY_PATH  = "${pkgs.xmlsec}/lib"; }
+          // (listToAttrs (concatMap (svc: svc.globalEnvVars) allSubservices));
+
+        preStart =
+          ''
+            mkdir -m 0750 -p ${mainCfg.stateDir}
+            [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir}
+
+            mkdir -m 0750 -p "${mainCfg.stateDir}/runtime"
+            [ $(id -u) != 0 ] || chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime"
+
+            mkdir -m 0700 -p ${mainCfg.logDir}
+
+            # Get rid of old semaphores.  These tend to accumulate across
+            # server restarts, eventually preventing it from restarting
+            # successfully.
+            for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${mainCfg.user} ' | cut -f2 -d ' '); do
+                ${pkgs.utillinux}/bin/ipcrm -s $i
+            done
+
+            # Run the startup hooks for the subservices.
+            for i in ${toString (map (svn: svn.startupScript) allSubservices)}; do
+                echo Running Apache startup hook $i...
+                $i
+            done
+          '';
+
+        serviceConfig.ExecStart = "@${httpd}/bin/httpd httpd -f ${httpdConf}";
+        serviceConfig.ExecStop = "${httpd}/bin/httpd -f ${httpdConf} -k graceful-stop";
+        serviceConfig.ExecReload = "${httpd}/bin/httpd -f ${httpdConf} -k graceful";
+        serviceConfig.Type = "forking";
+        serviceConfig.PIDFile = "${mainCfg.stateDir}/httpd.pid";
+        serviceConfig.Restart = "always";
+        serviceConfig.RestartSec = "5s";
+      };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix
new file mode 100644
index 000000000000..536e707137c6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix
@@ -0,0 +1,180 @@
+# This file defines the options that can be used both for the Apache
+# main server configuration, and for the virtual hosts.  (The latter
+# has additional options that affect the web server as a whole, like
+# the user/group to run under.)
+
+{ forMainServer, lib }:
+
+with lib;
+
+{
+
+  hostName = mkOption {
+    type = types.str;
+    default = "localhost";
+    description = "Canonical hostname for the server.";
+  };
+
+  serverAliases = mkOption {
+    type = types.listOf types.str;
+    default = [];
+    example = ["www.example.org" "www.example.org:8080" "example.org"];
+    description = ''
+      Additional names of virtual hosts served by this virtual host configuration.
+    '';
+  };
+
+  listen = mkOption {
+     type = types.listOf (types.submodule (
+          {
+            options = {
+              port = mkOption {
+                type = types.int;
+                description = "port to listen on";
+              };
+              ip = mkOption {
+                type = types.string;
+                default = "*";
+                description = "Ip to listen on. 0.0.0.0 for ipv4 only, * for all.";
+              };
+            };
+          } ));
+    description = ''
+      List of { /* ip: "*"; */ port = 80;} to listen on
+    '';
+
+    default = [];
+  };
+
+  enableSSL = mkOption {
+    type = types.bool;
+    default = false;
+    description = "Whether to enable SSL (https) support.";
+  };
+
+  # Note: sslServerCert and sslServerKey can be left empty, but this
+  # only makes sense for virtual hosts (they will inherit from the
+  # main server).
+
+  sslServerCert = mkOption {
+    type = types.nullOr types.path;
+    default = null;
+    example = "/var/host.cert";
+    description = "Path to server SSL certificate.";
+  };
+
+  sslServerKey = mkOption {
+    type = types.path;
+    example = "/var/host.key";
+    description = "Path to server SSL certificate key.";
+  };
+
+  sslServerChain = mkOption {
+    type = types.nullOr types.path;
+    default = null;
+    example = "/var/ca.pem";
+    description = "Path to server SSL chain file.";
+  };
+
+  adminAddr = mkOption ({
+    type = types.nullOr types.str;
+    example = "admin@example.org";
+    description = "E-mail address of the server administrator.";
+  } // (if forMainServer then {} else {default = null;}));
+
+  documentRoot = mkOption {
+    type = types.nullOr types.path;
+    default = null;
+    example = "/data/webserver/docs";
+    description = ''
+      The path of Apache's document root directory.  If left undefined,
+      an empty directory in the Nix store will be used as root.
+    '';
+  };
+
+  servedDirs = mkOption {
+    type = types.listOf types.attrs;
+    default = [];
+    example = [
+      { urlPath = "/nix";
+        dir = "/home/eelco/Dev/nix-homepage";
+      }
+    ];
+    description = ''
+      This option provides a simple way to serve static directories.
+    '';
+  };
+
+  servedFiles = mkOption {
+    type = types.listOf types.attrs;
+    default = [];
+    example = [
+      { urlPath = "/foo/bar.png";
+        file = "/home/eelco/some-file.png";
+      }
+    ];
+    description = ''
+      This option provides a simple way to serve individual, static files.
+    '';
+  };
+
+  extraConfig = mkOption {
+    type = types.lines;
+    default = "";
+    example = ''
+      <Directory /home>
+        Options FollowSymlinks
+        AllowOverride All
+      </Directory>
+    '';
+    description = ''
+      These lines go to httpd.conf verbatim. They will go after
+      directories and directory aliases defined by default.
+    '';
+  };
+
+  extraSubservices = mkOption {
+    type = types.listOf types.unspecified;
+    default = [];
+    description = "Extra subservices to enable in the webserver.";
+  };
+
+  enableUserDir = mkOption {
+    type = types.bool;
+    default = false;
+    description = ''
+      Whether to enable serving <filename>~/public_html</filename> as
+      <literal>/~<replaceable>username</replaceable></literal>.
+    '';
+  };
+
+  globalRedirect = mkOption {
+    type = types.nullOr types.str;
+    default = null;
+    example = http://newserver.example.org/;
+    description = ''
+      If set, all requests for this host are redirected permanently to
+      the given URL.
+    '';
+  };
+
+  logFormat = mkOption {
+    type = types.str;
+    default = "common";
+    example = "combined";
+    description = ''
+      Log format for Apache's log files. Possible values are: combined, common, referer, agent.
+    '';
+  };
+
+  robotsEntries = mkOption {
+    type = types.lines;
+    default = "";
+    example = "Disallow: /foo/";
+    description = ''
+      Specification of pages to be ignored by web crawlers. See <link
+      xlink:href='http://www.robotstxt.org/'/> for details.
+    '';
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/caddy.nix b/nixpkgs/nixos/modules/services/web-servers/caddy.nix
new file mode 100644
index 000000000000..6a1db6087840
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/caddy.nix
@@ -0,0 +1,105 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.caddy;
+  configFile = pkgs.writeText "Caddyfile" cfg.config;
+in {
+  options.services.caddy = {
+    enable = mkEnableOption "Caddy web server";
+
+    config = mkOption {
+      default = "";
+      example = ''
+        example.com {
+        gzip
+        minify
+        log syslog
+
+        root /srv/http
+        }
+      '';
+      type = types.lines;
+      description = "Verbatim Caddyfile to use";
+    };
+
+    ca = mkOption {
+      default = "https://acme-v02.api.letsencrypt.org/directory";
+      example = "https://acme-staging-v02.api.letsencrypt.org/directory";
+      type = types.string;
+      description = "Certificate authority ACME server. The default (Let's Encrypt production server) should be fine for most people.";
+    };
+
+    email = mkOption {
+      default = "";
+      type = types.string;
+      description = "Email address (for Let's Encrypt certificate)";
+    };
+
+    agree = mkOption {
+      default = false;
+      type = types.bool;
+      description = "Agree to Let's Encrypt Subscriber Agreement";
+    };
+
+    dataDir = mkOption {
+      default = "/var/lib/caddy";
+      type = types.path;
+      description = ''
+        The data directory, for storing certificates. Before 17.09, this
+        would create a .caddy directory. With 17.09 the contents of the
+        .caddy directory are in the specified data directory instead.
+      '';
+    };
+
+    package = mkOption {
+      default = pkgs.caddy;
+      defaultText = "pkgs.caddy";
+      type = types.package;
+      description = "Caddy package to use.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.caddy = {
+      description = "Caddy web server";
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment = mkIf (versionAtLeast config.system.stateVersion "17.09")
+        { CADDYPATH = cfg.dataDir; };
+      serviceConfig = {
+        ExecStart = ''
+          ${cfg.package}/bin/caddy -root=/var/tmp -conf=${configFile} \
+            -ca=${cfg.ca} -email=${cfg.email} ${optionalString cfg.agree "-agree"}
+        '';
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        Type = "simple";
+        User = "caddy";
+        Group = "caddy";
+        Restart = "on-failure";
+        StartLimitInterval = 86400;
+        StartLimitBurst = 5;
+        AmbientCapabilities = "cap_net_bind_service";
+        CapabilityBoundingSet = "cap_net_bind_service";
+        NoNewPrivileges = true;
+        LimitNPROC = 64;
+        LimitNOFILE = 1048576;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHome = true;
+        ProtectSystem = "full";
+        ReadWriteDirectories = cfg.dataDir;
+      };
+    };
+
+    users.users.caddy = {
+      group = "caddy";
+      uid = config.ids.uids.caddy;
+      home = cfg.dataDir;
+      createHome = true;
+    };
+
+    users.groups.caddy.gid = config.ids.uids.caddy;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix b/nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix
new file mode 100644
index 000000000000..80870118c334
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.darkhttpd;
+
+  args = concatStringsSep " " ([
+    cfg.rootDir
+    "--port ${toString cfg.port}"
+    "--addr ${cfg.address}"
+  ] ++ cfg.extraArgs
+    ++ optional cfg.hideServerId             "--no-server-id"
+    ++ optional config.networking.enableIPv6 "--ipv6");
+
+in {
+  options.services.darkhttpd = with types; {
+    enable = mkEnableOption "DarkHTTPd web server";
+
+    port = mkOption {
+      default = 80;
+      type = ints.u16;
+      description = ''
+        Port to listen on.
+        Pass 0 to let the system choose any free port for you.
+      '';
+    };
+
+    address = mkOption {
+      default = "127.0.0.1";
+      type = str;
+      description = ''
+        Address to listen on.
+        Pass `all` to listen on all interfaces.
+      '';
+    };
+
+    rootDir = mkOption {
+      type = path;
+      description = ''
+        Path from which to serve files.
+      '';
+    };
+
+    hideServerId = mkOption {
+      type = bool;
+      default = true;
+      description = ''
+        Don't identify the server type in headers or directory listings.
+      '';
+    };
+
+    extraArgs = mkOption {
+      type = listOf str;
+      default = [];
+      description = ''
+        Additional configuration passed to the executable.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.darkhttpd = {
+      description = "Dark HTTPd";
+      wants = [ "network.target" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${cfg.package}/bin/darkhttpd ${args}";
+        AmbientCapabilities = lib.mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
+        Restart = "on-failure";
+        RestartSec = "2s";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix b/nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix
new file mode 100644
index 000000000000..a64a187255a4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.fcgiwrap;
+in {
+
+  options = {
+    services.fcgiwrap = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable fcgiwrap, a server for running CGI applications over FastCGI.";
+      };
+
+      preforkProcesses = mkOption {
+        type = types.int;
+        default = 1;
+        description = "Number of processes to prefork.";
+      };
+
+      socketType = mkOption {
+        type = types.enum [ "unix" "tcp" "tcp6" ];
+        default = "unix";
+        description = "Socket type: 'unix', 'tcp' or 'tcp6'.";
+      };
+
+      socketAddress = mkOption {
+        type = types.str;
+        default = "/run/fcgiwrap.sock";
+        example = "1.2.3.4:5678";
+        description = "Socket address. In case of a UNIX socket, this should be its filesystem path.";
+      };
+
+      user = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "User permissions for the socket.";
+      };
+
+      group = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Group permissions for the socket.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.fcgiwrap = {
+      after = [ "nss-user-lookup.target" ];
+      wantedBy = optional (cfg.socketType != "unix") "multi-user.target";
+
+      serviceConfig = {
+        ExecStart = "${pkgs.fcgiwrap}/sbin/fcgiwrap -c ${builtins.toString cfg.preforkProcesses} ${
+          if (cfg.socketType != "unix") then "-s ${cfg.socketType}:${cfg.socketAddress}" else ""
+        }";
+      } // (if cfg.user != null && cfg.group != null then {
+        User = cfg.user;
+        Group = cfg.group;
+      } else { } );
+    };
+
+    systemd.sockets = if (cfg.socketType == "unix") then {
+      fcgiwrap = {
+        wantedBy = [ "sockets.target" ];
+        socketConfig.ListenStream = cfg.socketAddress;
+      };
+    } else { };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/hitch/default.nix b/nixpkgs/nixos/modules/services/web-servers/hitch/default.nix
new file mode 100644
index 000000000000..a6c4cbea1225
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/hitch/default.nix
@@ -0,0 +1,108 @@
+{ config, lib, pkgs, ...}:
+let
+  cfg = config.services.hitch;
+  ocspDir = lib.optionalString cfg.ocsp-stapling.enabled "/var/cache/hitch/ocsp";
+  hitchConfig = with lib; pkgs.writeText "hitch.conf" (concatStringsSep "\n" [
+    ("backend = \"${cfg.backend}\"")
+    (concatMapStrings (s: "frontend = \"${s}\"\n") cfg.frontend)
+    (concatMapStrings (s: "pem-file = \"${s}\"\n") cfg.pem-files)
+    ("ciphers = \"${cfg.ciphers}\"")
+    ("ocsp-dir = \"${ocspDir}\"")
+    "user = \"${cfg.user}\""
+    "group = \"${cfg.group}\""
+    cfg.extraConfig
+  ]);
+in
+with lib;
+{
+  options = {
+    services.hitch = {
+      enable = mkEnableOption "Hitch Server";
+
+      backend = mkOption {
+        type = types.str;
+        description = ''
+          The host and port Hitch connects to when receiving
+          a connection in the form [HOST]:PORT
+        '';
+      };
+
+      ciphers = mkOption {
+        type = types.str;
+        default = "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
+        description = "The list of ciphers to use";
+      };
+
+      frontend = mkOption {
+        type = types.either types.str (types.listOf types.str);
+        default = "[127.0.0.1]:443";
+        description = ''
+          The port and interface of the listen endpoint in the
++         form [HOST]:PORT[+CERT].
+        '';
+        apply = toList;
+      };
+
+      pem-files = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = "PEM files to use";
+      };
+
+      ocsp-stapling = {
+        enabled = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Whether to enable OCSP Stapling";
+        };
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "hitch";
+        description = "The user to run as";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "hitch";
+        description = "The group to run as";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Additional configuration lines";
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.hitch = {
+      description = "Hitch";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      preStart = ''
+        ${pkgs.hitch}/sbin/hitch -t --config ${hitchConfig}
+      '' + (optionalString cfg.ocsp-stapling.enabled ''
+        mkdir -p ${ocspDir}
+        chown -R hitch:hitch ${ocspDir}
+      '');
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${pkgs.hitch}/sbin/hitch --daemon --config ${hitchConfig}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        Restart = "always";
+        RestartSec = "5s";
+        LimitNOFILE = 131072;
+      };
+    };
+
+    environment.systemPackages = [ pkgs.hitch ];
+
+    users.users.hitch.group = "hitch";
+    users.groups.hitch = {};
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/hydron.nix b/nixpkgs/nixos/modules/services/web-servers/hydron.nix
new file mode 100644
index 000000000000..a4a5a435b2e6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/hydron.nix
@@ -0,0 +1,165 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.hydron;
+in with lib; {
+  options.services.hydron = {
+    enable = mkEnableOption "hydron";
+
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/hydron";
+      example = "/home/okina/hydron";
+      description = "Location where hydron runs and stores data.";
+    };
+
+    interval = mkOption {
+      type = types.str;
+      default = "weekly";
+      example = "06:00";
+      description = ''
+        How often we run hydron import and possibly fetch tags. Runs by default every week.
+
+        The format is described in
+        <citerefentry><refentrytitle>systemd.time</refentrytitle>
+        <manvolnum>7</manvolnum></citerefentry>.
+      '';
+    };
+
+    password = mkOption {
+      type = types.str;
+      default = "hydron";
+      example = "dumbpass";
+      description = "Password for the hydron database.";
+    };
+
+    passwordFile = mkOption {
+      type = types.path;
+      default = "/run/keys/hydron-password-file";
+      example = "/home/okina/hydron/keys/pass";
+      description = "Password file for the hydron database.";
+    };
+
+    postgresArgs = mkOption {
+      type = types.str;
+      description = "Postgresql connection arguments.";
+      example = ''
+        {
+          "driver": "postgres",
+          "connection": "user=hydron password=dumbpass dbname=hydron sslmode=disable"
+        }
+      '';
+    };
+
+    postgresArgsFile = mkOption {
+      type = types.path;
+      default = "/run/keys/hydron-postgres-args";
+      example = "/home/okina/hydron/keys/postgres";
+      description = "Postgresql connection arguments file.";
+    };
+
+    listenAddress = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "127.0.0.1:8010";
+      description = "Listen on a specific IP address and port.";
+    };
+
+    importPaths = mkOption {
+      type = types.listOf types.path;
+      default = [];
+      example = [ "/home/okina/Pictures" ];
+      description = "Paths that hydron will recursively import.";
+    };
+
+    fetchTags = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Fetch tags for imported images and webm from gelbooru.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.hydron.passwordFile = mkDefault (pkgs.writeText "hydron-password-file" cfg.password);
+    services.hydron.postgresArgsFile = mkDefault (pkgs.writeText "hydron-postgres-args" cfg.postgresArgs);
+    services.hydron.postgresArgs = mkDefault ''
+      {
+        "driver": "postgres",
+        "connection": "user=hydron password=${cfg.password} host=/run/postgresql dbname=hydron sslmode=disable"
+      }
+    '';
+
+    services.postgresql = {
+      enable = true;
+      ensureDatabases = [ "hydron" ];
+      ensureUsers = [
+        { name = "hydron";
+          ensurePermissions = { "DATABASE hydron" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0750 hydron hydron - -"
+      "d '${cfg.dataDir}/.hydron' - hydron hydron - -"
+      "d '${cfg.dataDir}/images' - hydron hydron - -"
+      "Z '${cfg.dataDir}' - hydron hydron - -"
+
+      "L+ '${cfg.dataDir}/.hydron/db_conf.json' - - - - ${cfg.postgresArgsFile}"
+    ];
+
+    systemd.services.hydron = {
+      description = "hydron";
+      after = [ "network.target" "postgresql.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        User = "hydron";
+        Group = "hydron";
+        ExecStart = "${pkgs.hydron}/bin/hydron serve"
+        + optionalString (cfg.listenAddress != null) " -a ${cfg.listenAddress}";
+      };
+    };
+
+    systemd.services.hydron-fetch = {
+      description = "Import paths into hydron and possibly fetch tags";
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = "hydron";
+        Group = "hydron";
+        ExecStart = "${pkgs.hydron}/bin/hydron import "
+        + optionalString cfg.fetchTags "-f "
+        + (escapeShellArg cfg.dataDir) + "/images " + (escapeShellArgs cfg.importPaths);
+      };
+    };
+
+    systemd.timers.hydron-fetch = {
+      description = "Automatically import paths into hydron and possibly fetch tags";
+      after = [ "network.target" "hydron.service" ];
+      wantedBy = [ "timers.target" ];
+
+      timerConfig = {
+        Persistent = true;
+        OnCalendar = cfg.interval;
+      };
+    };
+
+    users = {
+      groups.hydron.gid = config.ids.gids.hydron;
+
+      users.hydron = {
+        description = "hydron server service user";
+        home = cfg.dataDir;
+        group = "hydron";
+        uid = config.ids.uids.hydron;
+      };
+    };
+  };
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "hydron" "baseDir" ] [ "services" "hydron" "dataDir" ])
+  ];
+
+  meta.maintainers = with maintainers; [ chiiruno ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh b/nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh
new file mode 100644
index 000000000000..2eb89a90f67d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh
@@ -0,0 +1,72 @@
+set -e
+
+source $stdenv/setup
+
+mkdir -p $out/bin
+
+cat > $out/bin/control <<EOF
+mkdir -p $logDir
+chown -R $user $logDir
+export PATH=$PATH:$su/bin
+
+start()
+{
+  su $user -s /bin/sh -c "$jboss/bin/run.sh \
+      -Djboss.server.base.dir=$serverDir \
+      -Djboss.server.base.url=file://$serverDir \
+      -Djboss.server.temp.dir=$tempDir \
+      -Djboss.server.log.dir=$logDir \
+      -Djboss.server.lib.url=$libUrl \
+      -c default"
+}
+
+stop()
+{
+  su $user -s /bin/sh -c "$jboss/bin/shutdown.sh -S"
+}
+
+if test "\$1" = start
+then
+  trap stop 15
+  
+  start
+elif test "\$1" = stop
+then
+  stop  
+elif test "\$1" = init
+then
+  echo "Are you sure you want to create a new server instance (old server instance will be lost!)?"
+  read answer
+
+  if ! test \$answer = "yes"
+  then
+    exit 1
+  fi
+  
+  rm -rf $serverDir
+  mkdir -p $serverDir
+  cd $serverDir
+  cp -av $jboss/server/default .
+  sed -i -e "s|deploy/|$deployDir|" default/conf/jboss-service.xml
+  
+  if ! test "$useJK" = ""
+  then
+    sed -i -e 's|<attribute name="UseJK">false</attribute>|<attribute name="UseJK">true</attribute>|' default/deploy/jboss-web.deployer/META-INF/jboss-service.xml
+    sed -i -e 's|<Engine name="jboss.web" defaultHost="localhost">|<Engine name="jboss.web" defaultHost="localhost" jvmRoute="node1">|' default/deploy/jboss-web.deployer/server.xml
+  fi
+  
+  # Make files accessible for the server user
+  
+  chown -R $user $serverDir
+  for i in \`find $serverDir -type d\`
+  do
+    chmod 755 \$i
+  done
+  for i in \`find $serverDir -type f\`
+  do
+    chmod 644 \$i
+  done
+fi
+EOF
+
+chmod +x $out/bin/*
diff --git a/nixpkgs/nixos/modules/services/web-servers/jboss/default.nix b/nixpkgs/nixos/modules/services/web-servers/jboss/default.nix
new file mode 100644
index 000000000000..d28724281a83
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/jboss/default.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.jboss;
+
+  jbossService = pkgs.stdenv.mkDerivation {
+    name = "jboss-server";
+    builder = ./builder.sh;
+    inherit (pkgs) jboss su;
+    inherit (cfg) tempDir logDir libUrl deployDir serverDir user useJK;
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.jboss = {
+
+      enable = mkOption {
+        default = false;
+        description = "Whether to enable JBoss. WARNING : this package is outdated and is known to have vulnerabilities.";
+      };
+
+      tempDir = mkOption {
+        default = "/tmp";
+        description = "Location where JBoss stores its temp files";
+      };
+
+      logDir = mkOption {
+        default = "/var/log/jboss";
+        description = "Location of the logfile directory of JBoss";
+      };
+
+      serverDir = mkOption {
+        description = "Location of the server instance files";
+        default = "/var/jboss/server";
+      };
+
+      deployDir = mkOption {
+        description = "Location of the deployment files";
+        default = "/nix/var/nix/profiles/default/server/default/deploy/";
+      };
+
+      libUrl = mkOption {
+        default = "file:///nix/var/nix/profiles/default/server/default/lib";
+        description = "Location where the shared library JARs are stored";
+      };
+
+      user = mkOption {
+        default = "nobody";
+        description = "User account under which jboss runs.";
+      };
+
+      useJK = mkOption {
+        default = false;
+        description = "Whether to use to connector to the Apache HTTP server";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.jboss.enable {
+    systemd.services.jboss = {
+      description = "JBoss server";
+      script = "${jbossService}/bin/control start";
+      wantedBy = [ "multi-user.target" ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/cgit.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/cgit.nix
new file mode 100644
index 000000000000..9f25dc34f3f0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/cgit.nix
@@ -0,0 +1,91 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.lighttpd.cgit;
+  pathPrefix = if stringLength cfg.subdir == 0 then "" else "/" + cfg.subdir;
+  configFile = pkgs.writeText "cgitrc"
+    ''
+      # default paths to static assets
+      css=${pathPrefix}/cgit.css
+      logo=${pathPrefix}/cgit.png
+      favicon=${pathPrefix}/favicon.ico
+
+      # user configuration
+      ${cfg.configText}
+    '';
+in
+{
+
+  options.services.lighttpd.cgit = {
+
+    enable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        If true, enable cgit (fast web interface for git repositories) as a
+        sub-service in lighttpd.
+      '';
+    };
+
+    subdir = mkOption {
+      default = "cgit";
+      example = "";
+      type = types.str;
+      description = ''
+        The subdirectory in which to serve cgit. The web application will be
+        accessible at http://yourserver/''${subdir}
+      '';
+    };
+
+    configText = mkOption {
+      default = "";
+      example = ''
+        source-filter=''${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py
+        about-filter=''${pkgs.cgit}/lib/cgit/filters/about-formatting.sh
+        cache-size=1000
+        scan-path=/srv/git
+      '';
+      type = types.lines;
+      description = ''
+        Verbatim contents of the cgit runtime configuration file. Documentation
+        (with cgitrc example file) is available in "man cgitrc". Or online:
+        http://git.zx2c4.com/cgit/tree/cgitrc.5.txt
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    # make the cgitrc manpage available
+    environment.systemPackages = [ pkgs.cgit ];
+
+    # declare module dependencies
+    services.lighttpd.enableModules = [ "mod_cgi" "mod_alias" "mod_setenv" ];
+
+    services.lighttpd.extraConfig = ''
+      $HTTP["url"] =~ "^/${cfg.subdir}" {
+          cgi.assign = (
+              "cgit.cgi" => "${pkgs.cgit}/cgit/cgit.cgi"
+          )
+          alias.url = (
+              "${pathPrefix}/cgit.css" => "${pkgs.cgit}/cgit/cgit.css",
+              "${pathPrefix}/cgit.png" => "${pkgs.cgit}/cgit/cgit.png",
+              "${pathPrefix}"          => "${pkgs.cgit}/cgit/cgit.cgi"
+          )
+          setenv.add-environment = (
+              "CGIT_CONFIG" => "${configFile}"
+          )
+      }
+    '';
+
+    systemd.services.lighttpd.preStart = ''
+      mkdir -p /var/cache/cgit
+      chown lighttpd:lighttpd /var/cache/cgit
+    '';
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix
new file mode 100644
index 000000000000..3f262451c2cb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.lighttpd.collectd;
+
+  collectionConf = pkgs.writeText "collection.conf" ''
+    datadir: "${config.services.collectd.dataDir}"
+    libdir: "${config.services.collectd.package}/lib/collectd"
+  '';
+
+  defaultCollectionCgi = config.services.collectd.package.overrideDerivation(old: {
+    name = "collection.cgi";
+    dontConfigure = true;
+    buildPhase = "true";
+    installPhase = ''
+      substituteInPlace contrib/collection.cgi --replace '"/etc/collection.conf"' '$ENV{COLLECTION_CONF}'
+      cp contrib/collection.cgi $out
+    '';
+  });
+in
+{
+
+  options.services.lighttpd.collectd = {
+
+    enable = mkEnableOption "collectd subservice accessible at http://yourserver/collectd";
+
+    collectionCgi = mkOption {
+      type = types.path;
+      default = defaultCollectionCgi;
+      description = ''
+        Path to collection.cgi script from (collectd sources)/contrib/collection.cgi
+        This option allows to use a customized version
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.lighttpd.enableModules = [ "mod_cgi" "mod_alias" "mod_setenv" ];
+
+    services.lighttpd.extraConfig = ''
+      $HTTP["url"] =~ "^/collectd" {
+        cgi.assign = (
+          ".cgi" => "${pkgs.perl}/bin/perl"
+        )
+        alias.url = (
+          "/collectd" => "${cfg.collectionCgi}"
+        )
+        setenv.add-environment = (
+          "PERL5LIB" => "${with pkgs.perlPackages; makePerlPath [ CGI HTMLParser URI pkgs.rrdtool ]}",
+          "COLLECTION_CONF" => "${collectionConf}"
+        )
+      }
+    '';
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix
new file mode 100644
index 000000000000..7a3df26e47a6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix
@@ -0,0 +1,256 @@
+# NixOS module for lighttpd web server
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.lighttpd;
+
+  # List of known lighttpd modules, ordered by how the lighttpd documentation
+  # recommends them being imported:
+  # http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
+  #
+  # Some modules are always imported and should not appear in the config:
+  # disallowedModules = [ "mod_indexfile" "mod_dirlisting" "mod_staticfile" ];
+  #
+  # For full module list, see the output of running ./configure in the lighttpd
+  # source.
+  allKnownModules = [
+    "mod_rewrite"
+    "mod_redirect"
+    "mod_alias"
+    "mod_access"
+    "mod_auth"
+    "mod_status"
+    "mod_simple_vhost"
+    "mod_evhost"
+    "mod_userdir"
+    "mod_secdownload"
+    "mod_fastcgi"
+    "mod_proxy"
+    "mod_cgi"
+    "mod_ssi"
+    "mod_compress"
+    "mod_usertrack"
+    "mod_expire"
+    "mod_rrdtool"
+    "mod_accesslog"
+    # Remaining list of modules, order assumed to be unimportant.
+    "mod_authn_file"
+    "mod_authn_gssapi"
+    "mod_authn_ldap"
+    "mod_authn_mysql"
+    "mod_cml"
+    "mod_deflate"
+    "mod_evasive"
+    "mod_extforward"
+    "mod_flv_streaming"
+    "mod_geoip"
+    "mod_magnet"
+    "mod_mysql_vhost"
+    "mod_openssl"  # since v1.4.46
+    "mod_scgi"
+    "mod_setenv"
+    "mod_trigger_b4_dl"
+    "mod_uploadprogress"
+    "mod_vhostdb"  # since v1.4.46
+    "mod_webdav"
+    "mod_wstunnel"  # since v1.4.46
+  ];
+
+  maybeModuleString = moduleName:
+    if elem moduleName cfg.enableModules then ''"${moduleName}"'' else "";
+
+  modulesIncludeString = concatStringsSep ",\n"
+    (filter (x: x != "") (map maybeModuleString allKnownModules));
+
+  configFile = if cfg.configText != "" then
+    pkgs.writeText "lighttpd.conf" ''
+      ${cfg.configText}
+    ''
+    else
+    pkgs.writeText "lighttpd.conf" ''
+      server.document-root = "${cfg.document-root}"
+      server.port = ${toString cfg.port}
+      server.username = "lighttpd"
+      server.groupname = "lighttpd"
+
+      # As for why all modules are loaded here, instead of having small
+      # server.modules += () entries in each sub-service extraConfig snippet,
+      # read this:
+      #
+      #   http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
+      #   http://redmine.lighttpd.net/issues/2337
+      #
+      # Basically, lighttpd doesn't want to load (or even silently ignore) a
+      # module for a second time, and there is no way to check if a module has
+      # been loaded already. So if two services were to put the same module in
+      # server.modules += (), that would break the lighttpd configuration.
+      server.modules = (
+          ${modulesIncludeString}
+      )
+
+      # Logging (logs end up in systemd journal)
+      accesslog.use-syslog = "enable"
+      server.errorlog-use-syslog = "enable"
+
+      ${lib.optionalString cfg.enableUpstreamMimeTypes ''
+      include "${pkgs.lighttpd}/share/lighttpd/doc/config/conf.d/mime.conf"
+      ''}
+
+      static-file.exclude-extensions = ( ".fcgi", ".php", ".rb", "~", ".inc" )
+      index-file.names = ( "index.html" )
+
+      ${if cfg.mod_userdir then ''
+        userdir.path = "public_html"
+      '' else ""}
+
+      ${if cfg.mod_status then ''
+        status.status-url = "/server-status"
+        status.statistics-url = "/server-statistics"
+        status.config-url = "/server-config"
+      '' else ""}
+
+      ${cfg.extraConfig}
+    '';
+
+in
+
+{
+
+  options = {
+
+    services.lighttpd = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Enable the lighttpd web server.
+        '';
+      };
+
+      port = mkOption {
+        default = 80;
+        type = types.int;
+        description = ''
+          TCP port number for lighttpd to bind to.
+        '';
+      };
+
+      document-root = mkOption {
+        default = "/srv/www";
+        type = types.path;
+        description = ''
+          Document-root of the web server. Must be readable by the "lighttpd" user.
+        '';
+      };
+
+      mod_userdir = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          If true, requests in the form /~user/page.html are rewritten to take
+          the file public_html/page.html from the home directory of the user.
+        '';
+      };
+
+      enableModules = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "mod_cgi" "mod_status" ];
+        description = ''
+          List of lighttpd modules to enable. Sub-services take care of
+          enabling modules as needed, so this option is mainly for when you
+          want to add custom stuff to
+          <option>services.lighttpd.extraConfig</option> that depends on a
+          certain module.
+        '';
+      };
+
+      enableUpstreamMimeTypes = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to include the list of mime types bundled with lighttpd
+          (upstream). If you disable this, no mime types will be added by
+          NixOS and you will have to add your own mime types in
+          <option>services.lighttpd.extraConfig</option>.
+        '';
+      };
+
+      mod_status = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Show server status overview at /server-status, statistics at
+          /server-statistics and list of loaded modules at /server-config.
+        '';
+      };
+
+      configText = mkOption {
+        default = "";
+        type = types.lines;
+        example = ''...verbatim config file contents...'';
+        description = ''
+          Overridable config file contents to use for lighttpd. By default, use
+          the contents automatically generated by NixOS.
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          These configuration lines will be appended to the generated lighttpd
+          config file. Note that this mechanism does not work when the manual
+          <option>configText</option> option is used.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = all (x: elem x allKnownModules) cfg.enableModules;
+        message = ''
+          One (or more) modules in services.lighttpd.enableModules are
+          unrecognized.
+
+          Known modules: ${toString allKnownModules}
+
+          services.lighttpd.enableModules: ${toString cfg.enableModules}
+        '';
+      }
+    ];
+
+    services.lighttpd.enableModules = mkMerge
+      [ (mkIf cfg.mod_status [ "mod_status" ])
+        (mkIf cfg.mod_userdir [ "mod_userdir" ])
+        # always load mod_accesslog so that we can log to the journal
+        [ "mod_accesslog" ]
+      ];
+
+    systemd.services.lighttpd = {
+      description = "Lighttpd Web Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.ExecStart = "${pkgs.lighttpd}/sbin/lighttpd -D -f ${configFile}";
+      # SIGINT => graceful shutdown
+      serviceConfig.KillSignal = "SIGINT";
+    };
+
+    users.users.lighttpd = {
+      group = "lighttpd";
+      description = "lighttpd web server privilege separation user";
+      uid = config.ids.uids.lighttpd;
+    };
+
+    users.groups.lighttpd.gid = config.ids.gids.lighttpd;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/gitweb.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/gitweb.nix
new file mode 100644
index 000000000000..c494d6966a7f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/gitweb.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gitweb;
+  package = pkgs.gitweb.override (optionalAttrs cfg.gitwebTheme {
+    gitwebTheme = true;
+  });
+
+in
+{
+
+  options.services.lighttpd.gitweb = {
+
+    enable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        If true, enable gitweb in lighttpd. Access it at http://yourserver/gitweb
+      '';
+    };
+
+  };
+
+  config = mkIf config.services.lighttpd.gitweb.enable {
+
+    # declare module dependencies
+    services.lighttpd.enableModules = [ "mod_cgi" "mod_redirect" "mod_alias" "mod_setenv" ];
+
+    services.lighttpd.extraConfig = ''
+      $HTTP["url"] =~ "^/gitweb" {
+          cgi.assign = (
+              ".cgi" => "${pkgs.perl}/bin/perl"
+          )
+          url.redirect = (
+              "^/gitweb$" => "/gitweb/"
+          )
+          alias.url = (
+              "/gitweb/static/" => "${package}/static/",
+              "/gitweb/"        => "${package}/gitweb.cgi"
+          )
+          setenv.add-environment = (
+              "GITWEB_CONFIG" => "${cfg.gitwebConfigFile}",
+              "HOME" => "${cfg.projectroot}"
+          )
+      }
+    '';
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/meguca.nix b/nixpkgs/nixos/modules/services/web-servers/meguca.nix
new file mode 100644
index 000000000000..5a00070dc941
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/meguca.nix
@@ -0,0 +1,174 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.meguca;
+  postgres = config.services.postgresql;
+in with lib; {
+  options.services.meguca = {
+    enable = mkEnableOption "meguca";
+
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/meguca";
+      example = "/home/okina/meguca";
+      description = "Location where meguca stores it's database and links.";
+    };
+
+    password = mkOption {
+      type = types.str;
+      default = "meguca";
+      example = "dumbpass";
+      description = "Password for the meguca database.";
+    };
+
+    passwordFile = mkOption {
+      type = types.path;
+      default = "/run/keys/meguca-password-file";
+      example = "/home/okina/meguca/keys/pass";
+      description = "Password file for the meguca database.";
+    };
+
+    reverseProxy = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "192.168.1.5";
+      description = "Reverse proxy IP.";
+    };
+
+    sslCertificate = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "/home/okina/meguca/ssl.cert";
+      description = "Path to the SSL certificate.";
+    };
+
+    listenAddress = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "127.0.0.1:8000";
+      description = "Listen on a specific IP address and port.";
+    };
+
+    cacheSize = mkOption {
+      type = types.nullOr types.int;
+      default = null;
+      example = 256;
+      description = "Cache size in MB.";
+    };
+
+    postgresArgs = mkOption {
+      type = types.str;
+      example = "user=meguca password=dumbpass dbname=meguca sslmode=disable";
+      description = "Postgresql connection arguments.";
+    };
+
+    postgresArgsFile = mkOption {
+      type = types.path;
+      default = "/run/keys/meguca-postgres-args";
+      example = "/home/okina/meguca/keys/postgres";
+      description = "Postgresql connection arguments file.";
+    };
+
+    compressTraffic = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Compress all traffic with gzip.";
+    };
+
+    assumeReverseProxy = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Assume the server is behind a reverse proxy, when resolving client IPs.";
+    };
+
+    httpsOnly = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Serve and listen only through HTTPS.";
+    };
+
+    videoPaths = mkOption {
+      type = types.listOf types.path;
+      default = [];
+      example = [ "/home/okina/Videos/tehe_pero.webm" ];
+      description = "Videos that will be symlinked into www/videos.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    security.sudo.enable = cfg.enable;
+    services.postgresql.enable = cfg.enable;
+    services.postgresql.package = pkgs.postgresql_11;
+    services.meguca.passwordFile = mkDefault (pkgs.writeText "meguca-password-file" cfg.password);
+    services.meguca.postgresArgsFile = mkDefault (pkgs.writeText "meguca-postgres-args" cfg.postgresArgs);
+    services.meguca.postgresArgs = mkDefault "user=meguca password=${cfg.password} dbname=meguca sslmode=disable";
+
+    systemd.services.meguca = {
+      description = "meguca";
+      after = [ "network.target" "postgresql.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        # Ensure folder exists or create it and links and permissions are correct
+        mkdir -p ${escapeShellArg cfg.dataDir}/www
+        rm -rf ${escapeShellArg cfg.dataDir}/www/videos
+        ln -sf ${pkgs.meguca}/share/meguca/www/* ${escapeShellArg cfg.dataDir}/www
+        unlink ${escapeShellArg cfg.dataDir}/www/videos
+        mkdir -p ${escapeShellArg cfg.dataDir}/www/videos
+
+        for vid in ${escapeShellArg cfg.videoPaths}; do
+          ln -sf $vid ${escapeShellArg cfg.dataDir}/www/videos
+        done
+
+        chmod 750 ${escapeShellArg cfg.dataDir}
+        chown -R meguca:meguca ${escapeShellArg cfg.dataDir}
+
+        # Ensure the database is correct or create it
+        ${pkgs.sudo}/bin/sudo -u ${postgres.superUser} ${postgres.package}/bin/createuser \
+          -SDR meguca || true
+        ${pkgs.sudo}/bin/sudo -u ${postgres.superUser} ${postgres.package}/bin/createdb \
+          -T template0 -E UTF8 -O meguca meguca || true
+        ${pkgs.sudo}/bin/sudo -u meguca ${postgres.package}/bin/psql \
+          -c "ALTER ROLE meguca WITH PASSWORD '$(cat ${escapeShellArg cfg.passwordFile})';" || true
+      '';
+
+    script = ''
+      cd ${escapeShellArg cfg.dataDir}
+
+      ${pkgs.meguca}/bin/meguca -d "$(cat ${escapeShellArg cfg.postgresArgsFile})"''
+      + optionalString (cfg.reverseProxy != null) " -R ${cfg.reverseProxy}"
+      + optionalString (cfg.sslCertificate != null) " -S ${cfg.sslCertificate}"
+      + optionalString (cfg.listenAddress != null) " -a ${cfg.listenAddress}"
+      + optionalString (cfg.cacheSize != null) " -c ${toString cfg.cacheSize}"
+      + optionalString (cfg.compressTraffic) " -g"
+      + optionalString (cfg.assumeReverseProxy) " -r"
+      + optionalString (cfg.httpsOnly) " -s" + " start";
+
+      serviceConfig = {
+        PermissionsStartOnly = true;
+        Type = "forking";
+        User = "meguca";
+        Group = "meguca";
+        ExecStop = "${pkgs.meguca}/bin/meguca stop";
+      };
+    };
+
+    users = {
+      groups.meguca.gid = config.ids.gids.meguca;
+
+      users.meguca = {
+        description = "meguca server service user";
+        home = cfg.dataDir;
+        createHome = true;
+        group = "meguca";
+        uid = config.ids.uids.meguca;
+      };
+    };
+  };
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "meguca" "baseDir" ] [ "services" "meguca" "dataDir" ])
+  ];
+
+  meta.maintainers = with maintainers; [ chiiruno ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix b/nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix
new file mode 100644
index 000000000000..f9b1a8b6ccce
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix
@@ -0,0 +1,132 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.mighttpd2;
+  configFile = pkgs.writeText "mighty-config" cfg.config;
+  routingFile = pkgs.writeText "mighty-routing" cfg.routing;
+in {
+  options.services.mighttpd2 = {
+    enable = mkEnableOption "Mighttpd2 web server";
+
+    config = mkOption {
+      default = "";
+      example = ''
+        # Example configuration for Mighttpd 2
+        Port: 80
+        # IP address or "*"
+        Host: *
+        Debug_Mode: Yes # Yes or No
+        # If available, "nobody" is much more secure for User:.
+        User: root
+        # If available, "nobody" is much more secure for Group:.
+        Group: root
+        Pid_File: /run/mighty.pid
+        Logging: Yes # Yes or No
+        Log_File: /var/log/mighty # The directory must be writable by User:
+        Log_File_Size: 16777216 # bytes
+        Log_Backup_Number: 10
+        Index_File: index.html
+        Index_Cgi: index.cgi
+        Status_File_Dir: /usr/local/share/mighty/status
+        Connection_Timeout: 30 # seconds
+        Fd_Cache_Duration: 10 # seconds
+        # Server_Name: Mighttpd/3.x.y
+        Tls_Port: 443
+        Tls_Cert_File: cert.pem # should change this with an absolute path
+        # should change this with comma-separated absolute paths
+        Tls_Chain_Files: chain.pem
+        # Currently, Tls_Key_File must not be encrypted.
+        Tls_Key_File: privkey.pem # should change this with an absolute path
+        Service: 0 # 0 is HTTP only, 1 is HTTPS only, 2 is both
+      '';
+      type = types.lines;
+      description = ''
+        Verbatim config file to use
+        (see http://www.mew.org/~kazu/proj/mighttpd/en/config.html)
+      '';
+    };
+
+    routing = mkOption {
+      default = "";
+      example = ''
+        # Example routing for Mighttpd 2
+
+        # Domain lists
+        [localhost www.example.com]
+
+        # Entries are looked up in the specified order
+        # All paths must end with "/"
+
+        # A path to CGI scripts should be specified with "=>"
+        /~alice/cgi-bin/ => /home/alice/public_html/cgi-bin/
+
+        # A path to static files should be specified with "->"
+        /~alice/         -> /home/alice/public_html/
+        /cgi-bin/        => /export/cgi-bin/
+
+        # Reverse proxy rules should be specified with ">>"
+        # /path >> host:port/path2
+        # Either "host" or ":port" can be committed, but not both.
+        /app/cal/        >> example.net/calendar/
+        # Yesod app in the same server
+        /app/wiki/       >> 127.0.0.1:3000/
+
+        /                -> /export/www/
+      '';
+      type = types.lines;
+      description = ''
+        Verbatim routing file to use
+        (see http://www.mew.org/~kazu/proj/mighttpd/en/config.html)
+      '';
+    };
+
+    cores = mkOption {
+      default = null;
+      type = types.nullOr types.int;
+      description = ''
+        How many cores to use.
+        If null it will be determined automatically
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    assertions =
+      [ { assertion = cfg.routing != "";
+          message = "You need at least one rule in mighttpd2.routing";
+        }
+      ];
+    systemd.services.mighttpd2 = {
+      description = "Mighttpd2 web server";
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = ''
+          ${pkgs.haskellPackages.mighttpd2}/bin/mighty \
+            ${configFile} \
+            ${routingFile} \
+            +RTS -N${optionalString (cfg.cores != null) "${cfg.cores}"}
+        '';
+        Type = "simple";
+        User = "mighttpd2";
+        Group = "mighttpd2";
+        Restart = "on-failure";
+        AmbientCapabilities = "cap_net_bind_service";
+        CapabilityBoundingSet = "cap_net_bind_service";
+      };
+    };
+
+    users.users.mighttpd2 = {
+      group = "mighttpd2";
+      uid = config.ids.uids.mighttpd2;
+      isSystemUser = true;
+    };
+
+    users.groups.mighttpd2.gid = config.ids.gids.mighttpd2;
+  };
+
+  meta.maintainers = with lib.maintainers; [ fgaz ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/minio.nix b/nixpkgs/nixos/modules/services/web-servers/minio.nix
new file mode 100644
index 000000000000..cd123000f009
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/minio.nix
@@ -0,0 +1,108 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.minio;
+in
+{
+  meta.maintainers = [ maintainers.bachp ];
+
+  options.services.minio = {
+    enable = mkEnableOption "Minio Object Storage";
+
+    listenAddress = mkOption {
+      default = ":9000";
+      type = types.str;
+      description = "Listen on a specific IP address and port.";
+    };
+
+    dataDir = mkOption {
+      default = "/var/lib/minio/data";
+      type = types.path;
+      description = "The data directory, for storing the objects.";
+    };
+
+    configDir = mkOption {
+      default = "/var/lib/minio/config";
+      type = types.path;
+      description = "The config directory, for the access keys and other settings.";
+    };
+
+    accessKey = mkOption {
+      default = "";
+      type = types.str;
+      description = ''
+        Access key of 5 to 20 characters in length that clients use to access the server.
+        This overrides the access key that is generated by minio on first startup and stored inside the
+        <literal>configDir</literal> directory.
+      '';
+    };
+
+    secretKey = mkOption {
+      default = "";
+      type = types.str;
+      description = ''
+        Specify the Secret key of 8 to 40 characters in length that clients use to access the server.
+        This overrides the secret key that is generated by minio on first startup and stored inside the
+        <literal>configDir</literal> directory.
+      '';
+    };
+
+    region = mkOption {
+      default = "us-east-1";
+      type = types.str;
+      description = ''
+        The physical location of the server. By default it is set to us-east-1, which is same as AWS S3's and Minio's default region.
+      '';
+    };
+
+    browser = mkOption {
+      default = true;
+      type = types.bool;
+      description = "Enable or disable access to web UI.";
+    };
+
+    package = mkOption {
+      default = pkgs.minio;
+      defaultText = "pkgs.minio";
+      type = types.package;
+      description = "Minio package to use.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.configDir}' - minio minio - -"
+      "d '${cfg.dataDir}' - minio minio - -"
+    ];
+
+    systemd.services.minio = {
+      description = "Minio Object Storage";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/minio server --json --address ${cfg.listenAddress} --config-dir=${cfg.configDir} ${cfg.dataDir}";
+        Type = "simple";
+        User = "minio";
+        Group = "minio";
+        LimitNOFILE = 65536;
+      };
+      environment = {
+        MINIO_REGION = "${cfg.region}";
+        MINIO_BROWSER = "${if cfg.browser then "on" else "off"}";
+      } // optionalAttrs (cfg.accessKey != "") {
+        MINIO_ACCESS_KEY = "${cfg.accessKey}";
+      } // optionalAttrs (cfg.secretKey != "") {
+        MINIO_SECRET_KEY = "${cfg.secretKey}";
+      };
+    };
+
+    users.users.minio = {
+      group = "minio";
+      uid = config.ids.uids.minio;
+    };
+
+    users.groups.minio.gid = config.ids.uids.minio;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix
new file mode 100644
index 000000000000..c1a51fbf8b42
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix
@@ -0,0 +1,709 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.nginx;
+  virtualHosts = mapAttrs (vhostName: vhostConfig:
+    let
+      serverName = if vhostConfig.serverName != null
+        then vhostConfig.serverName
+        else vhostName;
+      acmeDirectory = config.security.acme.directory;
+    in
+    vhostConfig // {
+      inherit serverName;
+    } // (optionalAttrs vhostConfig.enableACME {
+      sslCertificate = "${acmeDirectory}/${serverName}/fullchain.pem";
+      sslCertificateKey = "${acmeDirectory}/${serverName}/key.pem";
+      sslTrustedCertificate = "${acmeDirectory}/${serverName}/fullchain.pem";
+    }) // (optionalAttrs (vhostConfig.useACMEHost != null) {
+      sslCertificate = "${acmeDirectory}/${vhostConfig.useACMEHost}/fullchain.pem";
+      sslCertificateKey = "${acmeDirectory}/${vhostConfig.useACMEHost}/key.pem";
+      sslTrustedCertificate = "${acmeDirectory}/${vhostConfig.useACMEHost}/fullchain.pem";
+    })
+  ) cfg.virtualHosts;
+  enableIPv6 = config.networking.enableIPv6;
+
+  recommendedProxyConfig = pkgs.writeText "nginx-recommended-proxy-headers.conf" ''
+    proxy_set_header        Host $host;
+    proxy_set_header        X-Real-IP $remote_addr;
+    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
+    proxy_set_header        X-Forwarded-Proto $scheme;
+    proxy_set_header        X-Forwarded-Host $host;
+    proxy_set_header        X-Forwarded-Server $host;
+    proxy_set_header        Accept-Encoding "";
+  '';
+
+  upstreamConfig = toString (flip mapAttrsToList cfg.upstreams (name: upstream: ''
+    upstream ${name} {
+      ${toString (flip mapAttrsToList upstream.servers (name: server: ''
+        server ${name} ${optionalString server.backup "backup"};
+      ''))}
+      ${upstream.extraConfig}
+    }
+  ''));
+
+  configFile = pkgs.writers.writeNginxConfig "nginx.conf" ''
+    user ${cfg.user} ${cfg.group};
+    error_log ${cfg.logError};
+    daemon off;
+
+    ${cfg.config}
+
+    ${optionalString (cfg.eventsConfig != "" || cfg.config == "") ''
+    events {
+      ${cfg.eventsConfig}
+    }
+    ''}
+
+    ${optionalString (cfg.httpConfig == "" && cfg.config == "") ''
+    http {
+      include ${cfg.package}/conf/mime.types;
+      include ${cfg.package}/conf/fastcgi.conf;
+      include ${cfg.package}/conf/uwsgi_params;
+
+      ${optionalString (cfg.resolver.addresses != []) ''
+        resolver ${toString cfg.resolver.addresses} ${optionalString (cfg.resolver.valid != "") "valid=${cfg.resolver.valid}"} ${optionalString (!cfg.resolver.ipv6) "ipv6=off"};
+      ''}
+      ${upstreamConfig}
+
+      ${optionalString (cfg.recommendedOptimisation) ''
+        # optimisation
+        sendfile on;
+        tcp_nopush on;
+        tcp_nodelay on;
+        keepalive_timeout 65;
+        types_hash_max_size 2048;
+      ''}
+
+      ssl_protocols ${cfg.sslProtocols};
+      ssl_ciphers ${cfg.sslCiphers};
+      ${optionalString (cfg.sslDhparam != null) "ssl_dhparam ${cfg.sslDhparam};"}
+
+      ${optionalString (cfg.recommendedTlsSettings) ''
+        ssl_session_cache shared:SSL:42m;
+        ssl_session_timeout 23m;
+        ssl_ecdh_curve secp384r1;
+        ssl_prefer_server_ciphers on;
+        ssl_stapling on;
+        ssl_stapling_verify on;
+      ''}
+
+      ${optionalString (cfg.recommendedGzipSettings) ''
+        gzip on;
+        gzip_disable "msie6";
+        gzip_proxied any;
+        gzip_comp_level 5;
+        gzip_types
+          application/atom+xml
+          application/javascript
+          application/json
+          application/xml
+          application/xml+rss
+          image/svg+xml
+          text/css
+          text/javascript
+          text/plain
+          text/xml;
+        gzip_vary on;
+      ''}
+
+      ${optionalString (cfg.recommendedProxySettings) ''
+        proxy_redirect          off;
+        proxy_connect_timeout   90;
+        proxy_send_timeout      90;
+        proxy_read_timeout      90;
+        proxy_http_version      1.0;
+        include ${recommendedProxyConfig};
+      ''}
+
+      # $connection_upgrade is used for websocket proxying
+      map $http_upgrade $connection_upgrade {
+          default upgrade;
+          '''      close;
+      }
+      client_max_body_size ${cfg.clientMaxBodySize};
+
+      server_tokens ${if cfg.serverTokens then "on" else "off"};
+
+      ${cfg.commonHttpConfig}
+
+      ${vhosts}
+
+      ${optionalString cfg.statusPage ''
+        server {
+          listen 80;
+          ${optionalString enableIPv6 "listen [::]:80;" }
+
+          server_name localhost;
+
+          location /nginx_status {
+            stub_status on;
+            access_log off;
+            allow 127.0.0.1;
+            ${optionalString enableIPv6 "allow ::1;"}
+            deny all;
+          }
+        }
+      ''}
+
+      ${cfg.appendHttpConfig}
+    }''}
+
+    ${optionalString (cfg.httpConfig != "") ''
+    http {
+      include ${cfg.package}/conf/mime.types;
+      include ${cfg.package}/conf/fastcgi.conf;
+      include ${cfg.package}/conf/uwsgi_params;
+      ${cfg.httpConfig}
+    }''}
+
+    ${cfg.appendConfig}
+  '';
+
+  configPath = if cfg.enableReload
+    then "/etc/nginx/nginx.conf"
+    else configFile;
+
+  vhosts = concatStringsSep "\n" (mapAttrsToList (vhostName: vhost:
+    let
+        onlySSL = vhost.onlySSL || vhost.enableSSL;
+        hasSSL = onlySSL || vhost.addSSL || vhost.forceSSL;
+
+        defaultListen =
+          if vhost.listen != [] then vhost.listen
+          else ((optionals hasSSL (
+            singleton                    { addr = "0.0.0.0"; port = 443; ssl = true; }
+            ++ optional enableIPv6 { addr = "[::]";    port = 443; ssl = true; }
+          )) ++ optionals (!onlySSL) (
+            singleton                    { addr = "0.0.0.0"; port = 80;  ssl = false; }
+            ++ optional enableIPv6 { addr = "[::]";    port = 80;  ssl = false; }
+          ));
+
+        hostListen =
+          if vhost.forceSSL
+            then filter (x: x.ssl) defaultListen
+            else defaultListen;
+
+        listenString = { addr, port, ssl, extraParameters ? [], ... }:
+          "listen ${addr}:${toString port} "
+          + optionalString ssl "ssl "
+          + optionalString (ssl && vhost.http2) "http2 "
+          + optionalString vhost.default "default_server "
+          + optionalString (extraParameters != []) (concatStringsSep " " extraParameters)
+          + ";";
+
+        redirectListen = filter (x: !x.ssl) defaultListen;
+
+        acmeLocation = optionalString (vhost.enableACME || vhost.useACMEHost != null) ''
+          location /.well-known/acme-challenge {
+            ${optionalString (vhost.acmeFallbackHost != null) "try_files $uri @acme-fallback;"}
+            root ${vhost.acmeRoot};
+            auth_basic off;
+          }
+          ${optionalString (vhost.acmeFallbackHost != null) ''
+            location @acme-fallback {
+              auth_basic off;
+              proxy_pass http://${vhost.acmeFallbackHost};
+            }
+          ''}
+        '';
+
+      in ''
+        ${optionalString vhost.forceSSL ''
+          server {
+            ${concatMapStringsSep "\n" listenString redirectListen}
+
+            server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
+            ${acmeLocation}
+            location / {
+              return 301 https://$host$request_uri;
+            }
+          }
+        ''}
+
+        server {
+          ${concatMapStringsSep "\n" listenString hostListen}
+          server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
+          ${acmeLocation}
+          ${optionalString (vhost.root != null) "root ${vhost.root};"}
+          ${optionalString (vhost.globalRedirect != null) ''
+            return 301 http${optionalString hasSSL "s"}://${vhost.globalRedirect}$request_uri;
+          ''}
+          ${optionalString hasSSL ''
+            ssl_certificate ${vhost.sslCertificate};
+            ssl_certificate_key ${vhost.sslCertificateKey};
+          ''}
+          ${optionalString (hasSSL && vhost.sslTrustedCertificate != null) ''
+            ssl_trusted_certificate ${vhost.sslTrustedCertificate};
+          ''}
+
+          ${optionalString (vhost.basicAuthFile != null || vhost.basicAuth != {}) ''
+            auth_basic secured;
+            auth_basic_user_file ${if vhost.basicAuthFile != null then vhost.basicAuthFile else mkHtpasswd vhostName vhost.basicAuth};
+          ''}
+
+          ${mkLocations vhost.locations}
+
+          ${vhost.extraConfig}
+        }
+      ''
+  ) virtualHosts);
+  mkLocations = locations: concatStringsSep "\n" (map (config: ''
+    location ${config.location} {
+      ${optionalString (config.proxyPass != null && !cfg.proxyResolveWhileRunning)
+        "proxy_pass ${config.proxyPass};"
+      }
+      ${optionalString (config.proxyPass != null && cfg.proxyResolveWhileRunning) ''
+        set $nix_proxy_target "${config.proxyPass}";
+        proxy_pass $nix_proxy_target;
+      ''}
+      ${optionalString config.proxyWebsockets ''
+        proxy_http_version 1.1;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection $connection_upgrade;
+      ''}
+      ${optionalString (config.index != null) "index ${config.index};"}
+      ${optionalString (config.tryFiles != null) "try_files ${config.tryFiles};"}
+      ${optionalString (config.root != null) "root ${config.root};"}
+      ${optionalString (config.alias != null) "alias ${config.alias};"}
+      ${optionalString (config.return != null) "return ${config.return};"}
+      ${config.extraConfig}
+      ${optionalString (config.proxyPass != null && cfg.recommendedProxySettings) "include ${recommendedProxyConfig};"}
+    }
+  '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)));
+  mkHtpasswd = vhostName: authDef: pkgs.writeText "${vhostName}.htpasswd" (
+    concatStringsSep "\n" (mapAttrsToList (user: password: ''
+      ${user}:{PLAIN}${password}
+    '') authDef)
+  );
+in
+
+{
+  options = {
+    services.nginx = {
+      enable = mkEnableOption "Nginx Web Server";
+
+      statusPage = mkOption {
+        default = false;
+        type = types.bool;
+        description = "
+          Enable status page reachable from localhost on http://127.0.0.1/nginx_status.
+        ";
+      };
+
+      recommendedTlsSettings = mkOption {
+        default = false;
+        type = types.bool;
+        description = "
+          Enable recommended TLS settings.
+        ";
+      };
+
+      recommendedOptimisation = mkOption {
+        default = false;
+        type = types.bool;
+        description = "
+          Enable recommended optimisation settings.
+        ";
+      };
+
+      recommendedGzipSettings = mkOption {
+        default = false;
+        type = types.bool;
+        description = "
+          Enable recommended gzip settings.
+        ";
+      };
+
+      recommendedProxySettings = mkOption {
+        default = false;
+        type = types.bool;
+        description = "
+          Enable recommended proxy settings.
+        ";
+      };
+
+      package = mkOption {
+        default = pkgs.nginxStable;
+        defaultText = "pkgs.nginxStable";
+        type = types.package;
+        description = "
+          Nginx package to use. This defaults to the stable version. Note
+          that the nginx team recommends to use the mainline version which
+          available in nixpkgs as <literal>nginxMainline</literal>.
+        ";
+      };
+
+      logError = mkOption {
+        default = "stderr";
+        description = "
+          Configures logging.
+          The first parameter defines a file that will store the log. The
+          special value stderr selects the standard error file. Logging to
+          syslog can be configured by specifying the “syslog:” prefix.
+          The second parameter determines the level of logging, and can be
+          one of the following: debug, info, notice, warn, error, crit,
+          alert, or emerg. Log levels above are listed in the order of
+          increasing severity. Setting a certain log level will cause all
+          messages of the specified and more severe log levels to be logged.
+          If this parameter is omitted then error is used.
+        ";
+      };
+
+      preStart =  mkOption {
+        type = types.lines;
+        default = ''
+          test -d ${cfg.stateDir}/logs || mkdir -m 750 -p ${cfg.stateDir}/logs
+          test `stat -c %a ${cfg.stateDir}` = "750" || chmod 750 ${cfg.stateDir}
+          test `stat -c %a ${cfg.stateDir}/logs` = "750" || chmod 750 ${cfg.stateDir}/logs
+          chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir}
+        '';
+        description = "
+          Shell commands executed before the service's nginx is started.
+        ";
+      };
+
+      config = mkOption {
+        default = "";
+        description = "
+          Verbatim nginx.conf configuration.
+          This is mutually exclusive with the structured configuration
+          via virtualHosts and the recommendedXyzSettings configuration
+          options. See appendConfig for appending to the generated http block.
+        ";
+      };
+
+      appendConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Configuration lines appended to the generated Nginx
+          configuration file. Commonly used by different modules
+          providing http snippets. <option>appendConfig</option>
+          can be specified more than once and it's value will be
+          concatenated (contrary to <option>config</option> which
+          can be set only once).
+        '';
+      };
+
+      commonHttpConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          resolver 127.0.0.1 valid=5s;
+
+          log_format myformat '$remote_addr - $remote_user [$time_local] '
+                              '"$request" $status $body_bytes_sent '
+                              '"$http_referer" "$http_user_agent"';
+        '';
+        description = ''
+          With nginx you must provide common http context definitions before
+          they are used, e.g. log_format, resolver, etc. inside of server
+          or location contexts. Use this attribute to set these definitions
+          at the appropriate location.
+        '';
+      };
+
+      httpConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "
+          Configuration lines to be set inside the http block.
+          This is mutually exclusive with the structured configuration
+          via virtualHosts and the recommendedXyzSettings configuration
+          options. See appendHttpConfig for appending to the generated http block.
+        ";
+      };
+
+      eventsConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Configuration lines to be set inside the events block.
+        '';
+      };
+
+      appendHttpConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "
+          Configuration lines to be appended to the generated http block.
+          This is mutually exclusive with using config and httpConfig for
+          specifying the whole http block verbatim.
+        ";
+      };
+
+      enableReload = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Reload nginx when configuration file changes (instead of restart).
+          The configuration file is exposed at <filename>/etc/nginx/nginx.conf</filename>.
+          See also <literal>systemd.services.*.restartIfChanged</literal>.
+        '';
+      };
+
+      stateDir = mkOption {
+        default = "/var/spool/nginx";
+        description = "
+          Directory holding all state for nginx to run.
+        ";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "nginx";
+        description = "User account under which nginx runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "nginx";
+        description = "Group account under which nginx runs.";
+      };
+
+      serverTokens = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Show nginx version in headers and error pages.";
+      };
+
+      clientMaxBodySize = mkOption {
+        type = types.string;
+        default = "10m";
+        description = "Set nginx global client_max_body_size.";
+      };
+
+      sslCiphers = mkOption {
+        type = types.str;
+        default = "EECDH+aRSA+AESGCM:EDH+aRSA:EECDH+aRSA:+AES256:+AES128:+SHA1:!CAMELLIA:!SEED:!3DES:!DES:!RC4:!eNULL";
+        description = "Ciphers to choose from when negotiating tls handshakes.";
+      };
+
+      sslProtocols = mkOption {
+        type = types.str;
+        default = "TLSv1.2 TLSv1.3";
+        example = "TLSv1 TLSv1.1 TLSv1.2 TLSv1.3";
+        description = "Allowed TLS protocol versions.";
+      };
+
+      sslDhparam = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/path/to/dhparams.pem";
+        description = "Path to DH parameters file.";
+      };
+
+      proxyResolveWhileRunning = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Resolves domains of proxyPass targets at runtime
+          and not only at start, you have to set
+          services.nginx.resolver, too.
+        '';
+      };
+
+      resolver = mkOption {
+        type = types.submodule {
+          options = {
+            addresses = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              example = literalExample ''[ "[::1]" "127.0.0.1:5353" ]'';
+              description = "List of resolvers to use";
+            };
+            valid = mkOption {
+              type = types.str;
+              default = "";
+              example = "30s";
+              description = ''
+                By default, nginx caches answers using the TTL value of a response.
+                An optional valid parameter allows overriding it
+              '';
+            };
+            ipv6 = mkOption {
+              type = types.bool;
+              default = true;
+              description = ''
+                By default, nginx will look up both IPv4 and IPv6 addresses while resolving.
+                If looking up of IPv6 addresses is not desired, the ipv6=off parameter can be
+                specified.
+              '';
+            };
+          };
+        };
+        description = ''
+          Configures name servers used to resolve names of upstream servers into addresses
+        '';
+        default = {};
+      };
+
+      upstreams = mkOption {
+        type = types.attrsOf (types.submodule {
+          options = {
+            servers = mkOption {
+              type = types.attrsOf (types.submodule {
+                options = {
+                  backup = mkOption {
+                    type = types.bool;
+                    default = false;
+                    description = ''
+                      Marks the server as a backup server. It will be passed
+                      requests when the primary servers are unavailable.
+                    '';
+                  };
+                };
+              });
+              description = ''
+                Defines the address and other parameters of the upstream servers.
+              '';
+              default = {};
+            };
+            extraConfig = mkOption {
+              type = types.lines;
+              default = "";
+              description = ''
+                These lines go to the end of the upstream verbatim.
+              '';
+            };
+          };
+        });
+        description = ''
+          Defines a group of servers to use as proxy target.
+        '';
+        default = {};
+      };
+
+      virtualHosts = mkOption {
+        type = types.attrsOf (types.submodule (import ./vhost-options.nix {
+          inherit config lib;
+        }));
+        default = {
+          localhost = {};
+        };
+        example = literalExample ''
+          {
+            "hydra.example.com" = {
+              forceSSL = true;
+              enableACME = true;
+              locations."/" = {
+                proxyPass = "http://localhost:3000";
+              };
+            };
+          };
+        '';
+        description = "Declarative vhost config";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    # TODO: test user supplied config file pases syntax test
+
+    warnings =
+    let
+      deprecatedSSL = name: config: optional config.enableSSL
+      ''
+        config.services.nginx.virtualHosts.<name>.enableSSL is deprecated,
+        use config.services.nginx.virtualHosts.<name>.onlySSL instead.
+      '';
+
+    in flatten (mapAttrsToList deprecatedSSL virtualHosts);
+
+    assertions =
+    let
+      hostOrAliasIsNull = l: l.root == null || l.alias == null;
+    in [
+      {
+        assertion = all (host: all hostOrAliasIsNull (attrValues host.locations)) (attrValues virtualHosts);
+        message = "Only one of nginx root or alias can be specified on a location.";
+      }
+
+      {
+        assertion = all (conf: with conf;
+          !(addSSL && (onlySSL || enableSSL)) &&
+          !(forceSSL && (onlySSL || enableSSL)) &&
+          !(addSSL && forceSSL)
+        ) (attrValues virtualHosts);
+        message = ''
+          Options services.nginx.service.virtualHosts.<name>.addSSL,
+          services.nginx.virtualHosts.<name>.onlySSL and services.nginx.virtualHosts.<name>.forceSSL
+          are mutually exclusive.
+        '';
+      }
+
+      {
+        assertion = all (conf: !(conf.enableACME && conf.useACMEHost != null)) (attrValues virtualHosts);
+        message = ''
+          Options services.nginx.service.virtualHosts.<name>.enableACME and
+          services.nginx.virtualHosts.<name>.useACMEHost are mutually exclusive.
+        '';
+      }
+    ];
+
+    systemd.services.nginx = {
+      description = "Nginx Web Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      stopIfChanged = false;
+      preStart =
+        ''
+        ${cfg.preStart}
+        ${cfg.package}/bin/nginx -c ${configPath} -p ${cfg.stateDir} -t
+        '';
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/nginx -c ${configPath} -p ${cfg.stateDir}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        Restart = "always";
+        RestartSec = "10s";
+        StartLimitInterval = "1min";
+      };
+    };
+
+    environment.etc."nginx/nginx.conf" = mkIf cfg.enableReload {
+      source = configFile;
+    };
+
+    systemd.services.nginx-config-reload = mkIf cfg.enableReload {
+      wantedBy = [ "nginx.service" ];
+      restartTriggers = [ configFile ];
+      script = ''
+        if ${pkgs.systemd}/bin/systemctl -q is-active nginx.service ; then
+          ${pkgs.systemd}/bin/systemctl reload nginx.service
+        fi
+      '';
+      serviceConfig.RemainAfterExit = true;
+    };
+
+    security.acme.certs = filterAttrs (n: v: v != {}) (
+      let
+        vhostsConfigs = mapAttrsToList (vhostName: vhostConfig: vhostConfig) virtualHosts;
+        acmeEnabledVhosts = filter (vhostConfig: vhostConfig.enableACME && vhostConfig.useACMEHost == null) vhostsConfigs;
+        acmePairs = map (vhostConfig: { name = vhostConfig.serverName; value = {
+            user = cfg.user;
+            group = lib.mkDefault cfg.group;
+            webroot = vhostConfig.acmeRoot;
+            extraDomains = genAttrs vhostConfig.serverAliases (alias: null);
+            postRun = ''
+              systemctl reload nginx
+            '';
+          }; }) acmeEnabledVhosts;
+      in
+        listToAttrs acmePairs
+    );
+
+    users.users = optionalAttrs (cfg.user == "nginx") (singleton
+      { name = "nginx";
+        group = cfg.group;
+        uid = config.ids.uids.nginx;
+      });
+
+    users.groups = optionalAttrs (cfg.group == "nginx") (singleton
+      { name = "nginx";
+        gid = config.ids.gids.nginx;
+      });
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix
new file mode 100644
index 000000000000..272fd1480185
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gitweb;
+  package = pkgs.gitweb.override (optionalAttrs cfg.gitwebTheme {
+    gitwebTheme = true;
+  });
+
+in
+{
+
+  options.services.nginx.gitweb = {
+
+    enable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        If true, enable gitweb in nginx. Access it at http://yourserver/gitweb
+      '';
+    };
+
+  };
+
+  config = mkIf config.services.nginx.gitweb.enable {
+
+    systemd.services.gitweb = {
+      description = "GitWeb service";
+      script = "${package}/gitweb.cgi --fastcgi --nproc=1";
+      environment  = {
+        FCGI_SOCKET_PATH = "/run/gitweb/gitweb.sock";
+      };
+      serviceConfig = {
+        User = "nginx";
+        Group = "nginx";
+        RuntimeDirectory = [ "gitweb" ];
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    services.nginx = {
+      virtualHosts.default = {
+        locations."/gitweb/static/" = {
+          alias = "${package}/static/";
+        };
+        locations."/gitweb/" = {
+          extraConfig = ''
+            include ${pkgs.nginx}/conf/fastcgi_params;
+            fastcgi_param GITWEB_CONFIG ${cfg.gitwebConfigFile};
+            fastcgi_pass unix:/run/gitweb/gitweb.sock;
+          '';
+        };
+      };
+    };
+
+  };
+
+  meta.maintainers = with maintainers; [ gnidorah ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix
new file mode 100644
index 000000000000..aeb9b1dd79ef
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix
@@ -0,0 +1,95 @@
+# This file defines the options that can be used both for the Apache
+# main server configuration, and for the virtual hosts.  (The latter
+# has additional options that affect the web server as a whole, like
+# the user/group to run under.)
+
+{ lib }:
+
+with lib;
+
+{
+  options = {
+    proxyPass = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "http://www.example.org/";
+      description = ''
+        Adds proxy_pass directive and sets recommended proxy headers if
+        recommendedProxySettings is enabled.
+      '';
+    };
+
+    proxyWebsockets = mkOption {
+      type = types.bool;
+      default = false;
+      example = true;
+      description = ''
+        Whether to supporty proxying websocket connections with HTTP/1.1.
+      '';
+    };
+
+    index = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "index.php index.html";
+      description = ''
+        Adds index directive.
+      '';
+    };
+
+    tryFiles = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "$uri =404";
+      description = ''
+        Adds try_files directive.
+      '';
+    };
+
+    root = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/your/root/directory";
+      description = ''
+        Root directory for requests.
+      '';
+    };
+
+    alias = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/your/alias/directory";
+      description = ''
+        Alias directory for requests.
+      '';
+    };
+
+    return = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "301 http://example.com$request_uri;";
+      description = ''
+        Adds a return directive, for e.g. redirections.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        These lines go to the end of the location verbatim.
+      '';
+    };
+
+    priority = mkOption {
+      type = types.int;
+      default = 1000;
+      description = ''
+        Order of this location block in relation to the others in the vhost.
+        The semantics are the same as with `lib.mkOrder`. Smaller values have
+        a greater priority.
+      '';
+    };
+  };
+}
+
diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix
new file mode 100644
index 000000000000..15b933c984a6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix
@@ -0,0 +1,228 @@
+# This file defines the options that can be used both for the Apache
+# main server configuration, and for the virtual hosts.  (The latter
+# has additional options that affect the web server as a whole, like
+# the user/group to run under.)
+
+{ lib, ... }:
+
+with lib;
+{
+  options = {
+    serverName = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Name of this virtual host. Defaults to attribute name in virtualHosts.
+      '';
+      example = "example.org";
+    };
+
+    serverAliases = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = ["www.example.org" "example.org"];
+      description = ''
+        Additional names of virtual hosts served by this virtual host configuration.
+      '';
+    };
+
+    listen = mkOption {
+      type = with types; listOf (submodule { options = {
+        addr = mkOption { type = str;  description = "IP address.";  };
+        port = mkOption { type = int;  description = "Port number."; default = 80; };
+        ssl  = mkOption { type = bool; description = "Enable SSL.";  default = false; };
+        extraParameters = mkOption { type = listOf str; description = "Extra parameters of this listen directive."; default = []; example = [ "reuseport" "deferred" ]; };
+      }; });
+      default = [];
+      example = [
+        { addr = "195.154.1.1"; port = 443; ssl = true;}
+        { addr = "192.154.1.1"; port = 80; }
+      ];
+      description = ''
+        Listen addresses and ports for this virtual host.
+        IPv6 addresses must be enclosed in square brackets.
+        Note: this option overrides <literal>addSSL</literal>
+        and <literal>onlySSL</literal>.
+      '';
+    };
+
+    enableACME = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to ask Let's Encrypt to sign a certificate for this vhost.
+        Alternately, you can use an existing certificate through <option>useACMEHost</option>.
+      '';
+    };
+
+    useACMEHost = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        A host of an existing Let's Encrypt certificate to use.
+        This is useful if you have many subdomains and want to avoid hitting the
+        <link xlink:href="https://letsencrypt.org/docs/rate-limits/">rate limit</link>.
+        Alternately, you can generate a certificate through <option>enableACME</option>.
+        <emphasis>Note that this option does not create any certificates, nor it does add subdomains to existing ones – you will need to create them manually using  <xref linkend="opt-security.acme.certs"/>.</emphasis>
+      '';
+    };
+
+    acmeRoot = mkOption {
+      type = types.str;
+      default = "/var/lib/acme/acme-challenge";
+      description = "Directory for the acme challenge which is PUBLIC, don't put certs or keys in here";
+    };
+
+    acmeFallbackHost = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Host which to proxy requests to if acme challenge is not found. Useful
+        if you want multiple hosts to be able to verify the same domain name.
+      '';
+    };
+
+    addSSL = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable HTTPS in addition to plain HTTP. This will set defaults for
+        <literal>listen</literal> to listen on all interfaces on the respective default
+        ports (80, 443).
+      '';
+    };
+
+    onlySSL = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable HTTPS and reject plain HTTP connections. This will set
+        defaults for <literal>listen</literal> to listen on all interfaces on port 443.
+      '';
+    };
+
+    enableSSL = mkOption {
+      type = types.bool;
+      visible = false;
+      default = false;
+    };
+
+    forceSSL = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to add a separate nginx server block that permanently redirects (301)
+        all plain HTTP traffic to HTTPS. This will set defaults for
+        <literal>listen</literal> to listen on all interfaces on the respective default
+        ports (80, 443), where the non-SSL listens are used for the redirect vhosts.
+      '';
+    };
+
+    sslCertificate = mkOption {
+      type = types.path;
+      example = "/var/host.cert";
+      description = "Path to server SSL certificate.";
+    };
+
+    sslCertificateKey = mkOption {
+      type = types.path;
+      example = "/var/host.key";
+      description = "Path to server SSL certificate key.";
+    };
+
+    sslTrustedCertificate = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/var/root.cert";
+      description = "Path to root SSL certificate for stapling and client certificates.";
+    };
+
+    http2 = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to enable HTTP 2.
+        Note that (as of writing) due to nginx's implementation, to disable
+        HTTP 2 you have to disable it on all vhosts that use a given
+        IP address / port.
+        If there is one server block configured to enable http2,then it is
+        enabled for all server blocks on this IP.
+        See https://stackoverflow.com/a/39466948/263061.
+      '';
+    };
+
+    root = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/data/webserver/docs";
+      description = ''
+        The path of the web root directory.
+      '';
+    };
+
+    default = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Makes this vhost the default.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        These lines go to the end of the vhost verbatim.
+      '';
+    };
+
+    globalRedirect = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "newserver.example.org";
+      description = ''
+        If set, all requests for this host are redirected permanently to
+        the given hostname.
+      '';
+    };
+
+    basicAuth = mkOption {
+      type = types.attrsOf types.str;
+      default = {};
+      example = literalExample ''
+        {
+          user = "password";
+        };
+      '';
+      description = ''
+        Basic Auth protection for a vhost.
+
+        WARNING: This is implemented to store the password in plain text in the
+        nix store.
+      '';
+    };
+
+    basicAuthFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        Basic Auth password file for a vhost.
+      '';
+    };
+
+    locations = mkOption {
+      type = types.attrsOf (types.submodule (import ./location-options.nix {
+        inherit lib;
+      }));
+      default = {};
+      example = literalExample ''
+        {
+          "/" = {
+            proxyPass = "http://localhost:3000";
+          };
+        };
+      '';
+      description = "Declarative location config";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix b/nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix
new file mode 100644
index 000000000000..e95e71e0d997
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix
@@ -0,0 +1,279 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.phpfpm;
+
+  runtimeDir = "/run/phpfpm";
+
+  toStr = value:
+    if true == value then "yes"
+    else if false == value then "no"
+    else toString value;
+
+  fpmCfgFile = pool: poolOpts: pkgs.writeText "phpfpm-${pool}.conf" ''
+    [global]
+    ${concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") cfg.settings)}
+    ${optionalString (cfg.extraConfig != null) cfg.extraConfig}
+
+    [${pool}]
+    ${concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") poolOpts.settings)}
+    ${concatStringsSep "\n" (mapAttrsToList (n: v: "env[${n}] = ${toStr v}") poolOpts.phpEnv)}
+    ${optionalString (poolOpts.extraConfig != null) poolOpts.extraConfig}
+  '';
+
+  phpIni = poolOpts: pkgs.runCommand "php.ini" {
+    inherit (poolOpts) phpPackage phpOptions;
+    preferLocalBuild = true;
+    nixDefaults = ''
+      sendmail_path = "/run/wrappers/bin/sendmail -t -i"
+    '';
+    passAsFile = [ "nixDefaults" "phpOptions" ];
+  } ''
+    cat $phpPackage/etc/php.ini $nixDefaultsPath $phpOptionsPath > $out
+  '';
+
+  poolOpts = { name, ... }:
+    let
+      poolOpts = cfg.pools."${name}";
+    in
+    {
+      options = {
+        socket = mkOption {
+          type = types.str;
+          readOnly = true;
+          description = ''
+            Path to the unix socket file on which to accept FastCGI requests.
+            <note><para>This option is read-only and managed by NixOS.</para></note>
+          '';
+        };
+
+        listen = mkOption {
+          type = types.str;
+          default = "";
+          example = "/path/to/unix/socket";
+          description = ''
+            The address on which to accept FastCGI requests.
+          '';
+        };
+
+        phpPackage = mkOption {
+          type = types.package;
+          default = cfg.phpPackage;
+          defaultText = "config.services.phpfpm.phpPackage";
+          description = ''
+            The PHP package to use for running this PHP-FPM pool.
+          '';
+        };
+
+        phpOptions = mkOption {
+          type = types.lines;
+          default = cfg.phpOptions;
+          defaultText = "config.services.phpfpm.phpOptions";
+          description = ''
+            "Options appended to the PHP configuration file <filename>php.ini</filename> used for this PHP-FPM pool."
+          '';
+        };
+
+        phpEnv = lib.mkOption {
+          type = with types; attrsOf str;
+          default = {};
+          description = ''
+            Environment variables used for this PHP-FPM pool.
+          '';
+          example = literalExample ''
+            {
+              HOSTNAME = "$HOSTNAME";
+              TMP = "/tmp";
+              TMPDIR = "/tmp";
+              TEMP = "/tmp";
+            }
+          '';
+        };
+
+        user = mkOption {
+          type = types.str;
+          description = "User account under which this pool runs.";
+        };
+
+        group = mkOption {
+          type = types.str;
+          description = "Group account under which this pool runs.";
+        };
+
+        settings = mkOption {
+          type = with types; attrsOf (oneOf [ str int bool ]);
+          default = {};
+          description = ''
+            PHP-FPM pool directives. Refer to the "List of pool directives" section of
+            <link xlink:href="https://www.php.net/manual/en/install.fpm.configuration.php"/>
+            for details. Note that settings names must be enclosed in quotes (e.g.
+            <literal>"pm.max_children"</literal> instead of <literal>pm.max_children</literal>).
+          '';
+          example = literalExample ''
+            {
+              "pm" = "dynamic";
+              "pm.max_children" = 75;
+              "pm.start_servers" = 10;
+              "pm.min_spare_servers" = 5;
+              "pm.max_spare_servers" = 20;
+              "pm.max_requests" = 500;
+            }
+          '';
+        };
+
+        extraConfig = mkOption {
+          type = with types; nullOr lines;
+          default = null;
+          description = ''
+            Extra lines that go into the pool configuration.
+            See the documentation on <literal>php-fpm.conf</literal> for
+            details on configuration directives.
+          '';
+        };
+      };
+
+      config = {
+        socket = if poolOpts.listen == "" then "${runtimeDir}/${name}.sock" else poolOpts.listen;
+        group = mkDefault poolOpts.user;
+
+        settings = mapAttrs (name: mkDefault){
+          listen = poolOpts.socket;
+          user = poolOpts.user;
+          group = poolOpts.group;
+        };
+      };
+    };
+
+in {
+
+  options = {
+    services.phpfpm = {
+      settings = mkOption {
+        type = with types; attrsOf (oneOf [ str int bool ]);
+        default = {};
+        description = ''
+          PHP-FPM global directives. Refer to the "List of global php-fpm.conf directives" section of
+          <link xlink:href="https://www.php.net/manual/en/install.fpm.configuration.php"/>
+          for details. Note that settings names must be enclosed in quotes (e.g.
+          <literal>"pm.max_children"</literal> instead of <literal>pm.max_children</literal>).
+          You need not specify the options <literal>error_log</literal> or
+          <literal>daemonize</literal> here, since they are generated by NixOS.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = with types; nullOr lines;
+        default = null;
+        description = ''
+          Extra configuration that should be put in the global section of
+          the PHP-FPM configuration file. Do not specify the options
+          <literal>error_log</literal> or
+          <literal>daemonize</literal> here, since they are generated by
+          NixOS.
+        '';
+      };
+
+      phpPackage = mkOption {
+        type = types.package;
+        default = pkgs.php;
+        defaultText = "pkgs.php";
+        description = ''
+          The PHP package to use for running the PHP-FPM service.
+        '';
+      };
+
+      phpOptions = mkOption {
+        type = types.lines;
+        default = "";
+        example =
+          ''
+            date.timezone = "CET"
+          '';
+        description = ''
+          Options appended to the PHP configuration file <filename>php.ini</filename>.
+        '';
+      };
+
+      pools = mkOption {
+        type = types.attrsOf (types.submodule poolOpts);
+        default = {};
+        example = literalExample ''
+         {
+           mypool = {
+             user = "php";
+             group = "php";
+             phpPackage = pkgs.php;
+             settings = '''
+               "pm" = "dynamic";
+               "pm.max_children" = 75;
+               "pm.start_servers" = 10;
+               "pm.min_spare_servers" = 5;
+               "pm.max_spare_servers" = 20;
+               "pm.max_requests" = 500;
+             ''';
+           }
+         }'';
+        description = ''
+          PHP-FPM pools. If no pools are defined, the PHP-FPM
+          service is disabled.
+        '';
+      };
+    };
+  };
+
+  config = mkIf (cfg.pools != {}) {
+
+    warnings =
+      mapAttrsToList (pool: poolOpts: ''
+        Using config.services.phpfpm.pools.${pool}.listen is deprecated and will become unsupported in a future release. Please reference the read-only option config.services.phpfpm.pools.${pool}.socket to access the path of your socket.
+      '') (filterAttrs (pool: poolOpts: poolOpts.listen != "") cfg.pools) ++
+      mapAttrsToList (pool: poolOpts: ''
+        Using config.services.phpfpm.pools.${pool}.extraConfig is deprecated and will become unsupported in a future release. Please migrate your configuration to config.services.phpfpm.pools.${pool}.settings.
+      '') (filterAttrs (pool: poolOpts: poolOpts.extraConfig != null) cfg.pools) ++
+      optional (cfg.extraConfig != null) ''
+        Using config.services.phpfpm.extraConfig is deprecated and will become unsupported in a future release. Please migrate your configuration to config.services.phpfpm.settings.
+      ''
+    ;
+
+    services.phpfpm.settings = {
+      error_log = "syslog";
+      daemonize = false;
+    };
+
+    systemd.slices.phpfpm = {
+      description = "PHP FastCGI Process manager pools slice";
+    };
+
+    systemd.targets.phpfpm = {
+      description = "PHP FastCGI Process manager pools target";
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    systemd.services = mapAttrs' (pool: poolOpts:
+      nameValuePair "phpfpm-${pool}" {
+        description = "PHP FastCGI Process Manager service for pool ${pool}";
+        after = [ "network.target" ];
+        wantedBy = [ "phpfpm.target" ];
+        partOf = [ "phpfpm.target" ];
+        serviceConfig = let
+          cfgFile = fpmCfgFile pool poolOpts;
+          iniFile = phpIni poolOpts;
+        in {
+          Slice = "phpfpm.slice";
+          PrivateDevices = true;
+          ProtectSystem = "full";
+          ProtectHome = true;
+          # XXX: We need AF_NETLINK to make the sendmail SUID binary from postfix work
+          RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
+          Type = "notify";
+          ExecStart = "${poolOpts.phpPackage}/bin/php-fpm -y ${cfgFile} -c ${iniFile}";
+          ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
+          RuntimeDirectory = "phpfpm";
+          RuntimeDirectoryPreserve = true; # Relevant when multiple processes are running
+        };
+      }
+    ) cfg.pools;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/shellinabox.nix b/nixpkgs/nixos/modules/services/web-servers/shellinabox.nix
new file mode 100644
index 000000000000..58a02ac59c35
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/shellinabox.nix
@@ -0,0 +1,122 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.shellinabox;
+
+  # If a certificate file is specified, shellinaboxd requires
+  # a file descriptor to retrieve it
+  fd = "3";
+  createFd = optionalString (cfg.certFile != null) "${fd}<${cfg.certFile}";
+
+  # Command line arguments for the shellinabox daemon
+  args = [ "--background" ]
+   ++ optional (! cfg.enableSSL) "--disable-ssl"
+   ++ optional (cfg.certFile != null) "--cert-fd=${fd}"
+   ++ optional (cfg.certDirectory != null) "--cert=${cfg.certDirectory}"
+   ++ cfg.extraOptions;
+
+  # Command to start shellinaboxd
+  cmd = "${pkgs.shellinabox}/bin/shellinaboxd ${concatStringsSep " " args}";
+
+  # Command to start shellinaboxd if certFile is specified
+  wrappedCmd = "${pkgs.bash}/bin/bash -c 'exec ${createFd} && ${cmd}'";
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.shellinabox = {
+      enable = mkEnableOption "shellinabox daemon";
+
+      user = mkOption {
+        type = types.str;
+        default = "root";
+        description = ''
+          User to run shellinaboxd as. If started as root, the server drops
+          privileges by changing to nobody, unless overridden by the
+          <literal>--user</literal> option.
+        '';
+      };
+
+      enableSSL = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether or not to enable SSL (https) support.
+        '';
+      };
+        
+      certDirectory = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/certs";
+        description = ''
+          The daemon will look in this directory far any certificates.
+          If the browser negotiated a Server Name Identification the daemon
+          will look for a matching certificate-SERVERNAME.pem file. If no SNI
+          handshake takes place, it will fall back on using the certificate in the
+          certificate.pem file.
+
+          If no suitable certificate is installed, shellinaboxd will attempt to
+          create a new self-signed certificate. This will only succeed if, after
+          dropping privileges, shellinaboxd has write permissions for this
+          directory.
+        '';
+      };
+
+      certFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/certificate.pem";
+        description = "Path to server SSL certificate.";
+      };
+
+      extraOptions = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "--port=443" "--service /:LOGIN" ];
+        description = ''
+          A list of strings to be appended to the command line arguments
+          for shellinaboxd. Please see the manual page
+          <link xlink:href="https://code.google.com/p/shellinabox/wiki/shellinaboxd_man"/>
+          for a full list of available arguments.
+        '';
+      };
+
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions =
+      [ { assertion = cfg.enableSSL == true
+            -> cfg.certDirectory != null || cfg.certFile != null;
+          message = "SSL is enabled for shellinabox, but no certDirectory or certFile has been specefied."; }
+        { assertion = ! (cfg.certDirectory != null && cfg.certFile != null);
+          message = "Cannot set both certDirectory and certFile for shellinabox."; }
+      ];
+
+    systemd.services.shellinaboxd = {
+      description = "Shellinabox Web Server Daemon";
+
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "sshd.service" ];
+      after = [ "sshd.service" ];
+
+      serviceConfig = {
+        Type = "forking";
+        User = "${cfg.user}";
+        ExecStart = "${if cfg.certFile == null then "${cmd}" else "${wrappedCmd}"}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/tomcat.nix b/nixpkgs/nixos/modules/services/web-servers/tomcat.nix
new file mode 100644
index 000000000000..68261c50324d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/tomcat.nix
@@ -0,0 +1,425 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.tomcat;
+  tomcat = cfg.package;
+in
+
+{
+
+  meta = {
+    maintainers = with maintainers; [ danbst ];
+  };
+
+  ###### interface
+
+  options = {
+
+    services.tomcat = {
+      enable = mkEnableOption "Apache Tomcat";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.tomcat85;
+        defaultText = "pkgs.tomcat85";
+        example = lib.literalExample "pkgs.tomcat9";
+        description = ''
+          Which tomcat package to use.
+        '';
+      };
+
+      purifyOnStart = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          On startup, the `baseDir` directory is populated with various files,
+          subdirectories and symlinks. If this option is enabled, these items
+          (except for the `logs` and `work` subdirectories) are first removed.
+          This prevents interference from remainders of an old configuration
+          (libraries, webapps, etc.), so it's recommended to enable this option.
+        '';
+      };
+
+      baseDir = mkOption {
+        type = lib.types.path;
+        default = "/var/tomcat";
+        description = ''
+          Location where Tomcat stores configuration files, web applications
+          and logfiles. Note that it is partially cleared on each service startup
+          if `purifyOnStart` is enabled.
+        '';
+      };
+
+      logDirs = mkOption {
+        default = [];
+        type = types.listOf types.path;
+        description = "Directories to create in baseDir/logs/";
+      };
+
+      extraConfigFiles = mkOption {
+        default = [];
+        type = types.listOf types.path;
+        description = "Extra configuration files to pull into the tomcat conf directory";
+      };
+
+      extraEnvironment = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "ENVIRONMENT=production" ];
+        description = "Environment Variables to pass to the tomcat service";
+      };
+
+      extraGroups = mkOption {
+        default = [];
+        example = [ "users" ];
+        description = "Defines extra groups to which the tomcat user belongs.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "tomcat";
+        description = "User account under which Apache Tomcat runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "tomcat";
+        description = "Group account under which Apache Tomcat runs.";
+      };
+
+      javaOpts = mkOption {
+        type = types.either (types.listOf types.str) types.str;
+        default = "";
+        description = "Parameters to pass to the Java Virtual Machine which spawns Apache Tomcat";
+      };
+
+      catalinaOpts = mkOption {
+        type = types.either (types.listOf types.str) types.str;
+        default = "";
+        description = "Parameters to pass to the Java Virtual Machine which spawns the Catalina servlet container";
+      };
+
+      sharedLibs = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "List containing JAR files or directories with JAR files which are libraries shared by the web applications";
+      };
+
+      serverXml = mkOption {
+        type = types.lines;
+        default = "";
+        description = "
+          Verbatim server.xml configuration.
+          This is mutually exclusive with the virtualHosts options.
+        ";
+      };
+
+      commonLibs = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "List containing JAR files or directories with JAR files which are libraries shared by the web applications and the servlet container";
+      };
+
+      webapps = mkOption {
+        type = types.listOf types.path;
+        default = [ tomcat.webapps ];
+        defaultText = "[ pkgs.tomcat85.webapps ]";
+        description = "List containing WAR files or directories with WAR files which are web applications to be deployed on Tomcat";
+      };
+
+      virtualHosts = mkOption {
+        type = types.listOf (types.submodule {
+          options = {
+            name = mkOption {
+              type = types.str;
+              description = "name of the virtualhost";
+            };
+            aliases = mkOption {
+              type = types.listOf types.str;
+              description = "aliases of the virtualhost";
+              default = [];
+            };
+            webapps = mkOption {
+              type = types.listOf types.path;
+              description = ''
+                List containing web application WAR files and/or directories containing
+                web applications and configuration files for the virtual host.
+              '';
+              default = [];
+            };
+          };
+        });
+        default = [];
+        description = "List consisting of a virtual host name and a list of web applications to deploy on each virtual host";
+      };
+
+      logPerVirtualHost = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable logging per virtual host.";
+      };
+
+      jdk = mkOption {
+        type = types.package;
+        default = pkgs.jdk;
+        defaultText = "pkgs.jdk";
+        description = "Which JDK to use.";
+      };
+
+      axis2 = {
+
+        enable = mkOption {
+          default = false;
+          type = types.bool;
+          description = "Whether to enable an Apache Axis2 container";
+        };
+
+        services = mkOption {
+          default = [];
+          type = types.listOf types.str;
+          description = "List containing AAR files or directories with AAR files which are web services to be deployed on Axis2";
+        };
+
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.tomcat.enable {
+
+    users.groups = singleton
+      { name = "tomcat";
+        gid = config.ids.gids.tomcat;
+      };
+
+    users.users = singleton
+      { name = "tomcat";
+        uid = config.ids.uids.tomcat;
+        description = "Tomcat user";
+        home = "/homeless-shelter";
+        extraGroups = cfg.extraGroups;
+      };
+
+    systemd.services.tomcat = {
+      description = "Apache Tomcat server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      preStart = ''
+        ${lib.optionalString cfg.purifyOnStart ''
+          # Delete most directories/symlinks we create from the existing base directory,
+          # to get rid of remainders of an old configuration.
+          # The list of directories to delete is taken from the "mkdir" command below,
+          # excluding "logs" (because logs are valuable) and "work" (because normally
+          # session files are there), and additionally including "bin".
+          rm -rf ${cfg.baseDir}/{conf,virtualhosts,temp,lib,shared/lib,webapps,bin}
+        ''}
+
+        # Create the base directory
+        mkdir -p \
+          ${cfg.baseDir}/{conf,virtualhosts,logs,temp,lib,shared/lib,webapps,work}
+        chown ${cfg.user}:${cfg.group} \
+          ${cfg.baseDir}/{conf,virtualhosts,logs,temp,lib,shared/lib,webapps,work}
+
+        # Create a symlink to the bin directory of the tomcat component
+        ln -sfn ${tomcat}/bin ${cfg.baseDir}/bin
+
+        # Symlink the config files in the conf/ directory (except for catalina.properties and server.xml)
+        for i in $(ls ${tomcat}/conf | grep -v catalina.properties | grep -v server.xml); do
+          ln -sfn ${tomcat}/conf/$i ${cfg.baseDir}/conf/`basename $i`
+        done
+
+        ${if cfg.extraConfigFiles != [] then ''
+          for i in ${toString cfg.extraConfigFiles}; do
+            ln -sfn $i ${cfg.baseDir}/conf/`basename $i`
+          done
+        '' else ""}
+
+        # Create a modified catalina.properties file
+        # Change all references from CATALINA_HOME to CATALINA_BASE and add support for shared libraries
+        sed -e 's|''${catalina.home}|''${catalina.base}|g' \
+          -e 's|shared.loader=|shared.loader=''${catalina.base}/shared/lib/*.jar|' \
+          ${tomcat}/conf/catalina.properties > ${cfg.baseDir}/conf/catalina.properties
+
+        ${if cfg.serverXml != "" then ''
+          cp -f ${pkgs.writeTextDir "server.xml" cfg.serverXml}/* ${cfg.baseDir}/conf/
+        '' else
+          let
+            hostElementForVirtualHost = virtualHost: ''
+              <Host name="${virtualHost.name}" appBase="virtualhosts/${virtualHost.name}/webapps"
+                    unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false">
+            '' + concatStrings (innerElementsForVirtualHost virtualHost) + ''
+              </Host>
+            '';
+            innerElementsForVirtualHost = virtualHost:
+              (map (alias: ''
+                <Alias>${alias}</Alias>
+              '') virtualHost.aliases)
+              ++ (optional cfg.logPerVirtualHost ''
+                <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs/${virtualHost.name}"
+                       prefix="${virtualHost.name}_access_log." pattern="combined" resolveHosts="false"/>
+              '');
+            hostElementsString = concatMapStringsSep "\n" hostElementForVirtualHost cfg.virtualHosts;
+            hostElementsSedString = replaceStrings ["\n"] ["\\\n"] hostElementsString;
+          in ''
+            # Create a modified server.xml which also includes all virtual hosts
+            sed -e "/<Engine name=\"Catalina\" defaultHost=\"localhost\">/a\\"${escapeShellArg hostElementsSedString} \
+                  ${tomcat}/conf/server.xml > ${cfg.baseDir}/conf/server.xml
+          ''
+        }
+        ${optionalString (cfg.logDirs != []) ''
+          for i in ${toString cfg.logDirs}; do
+            mkdir -p ${cfg.baseDir}/logs/$i
+            chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs/$i
+          done
+        ''}
+        ${optionalString cfg.logPerVirtualHost (toString (map (h: ''
+          mkdir -p ${cfg.baseDir}/logs/${h.name}
+          chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs/${h.name}
+        '') cfg.virtualHosts))}
+
+        # Symlink all the given common libs files or paths into the lib/ directory
+        for i in ${tomcat} ${toString cfg.commonLibs}; do
+          if [ -f $i ]; then
+            # If the given web application is a file, symlink it into the common/lib/ directory
+            ln -sfn $i ${cfg.baseDir}/lib/`basename $i`
+          elif [ -d $i ]; then
+            # If the given web application is a directory, then iterate over the files
+            # in the special purpose directories and symlink them into the tomcat tree
+
+            for j in $i/lib/*; do
+              ln -sfn $j ${cfg.baseDir}/lib/`basename $j`
+            done
+          fi
+        done
+
+        # Symlink all the given shared libs files or paths into the shared/lib/ directory
+        for i in ${toString cfg.sharedLibs}; do
+          if [ -f $i ]; then
+            # If the given web application is a file, symlink it into the common/lib/ directory
+            ln -sfn $i ${cfg.baseDir}/shared/lib/`basename $i`
+          elif [ -d $i ]; then
+            # If the given web application is a directory, then iterate over the files
+            # in the special purpose directories and symlink them into the tomcat tree
+
+            for j in $i/shared/lib/*; do
+              ln -sfn $j ${cfg.baseDir}/shared/lib/`basename $j`
+            done
+          fi
+        done
+
+        # Symlink all the given web applications files or paths into the webapps/ directory
+        for i in ${toString cfg.webapps}; do
+          if [ -f $i ]; then
+            # If the given web application is a file, symlink it into the webapps/ directory
+            ln -sfn $i ${cfg.baseDir}/webapps/`basename $i`
+          elif [ -d $i ]; then
+            # If the given web application is a directory, then iterate over the files
+            # in the special purpose directories and symlink them into the tomcat tree
+
+            for j in $i/webapps/*; do
+              ln -sfn $j ${cfg.baseDir}/webapps/`basename $j`
+            done
+
+            # Also symlink the configuration files if they are included
+            if [ -d $i/conf/Catalina ]; then
+              for j in $i/conf/Catalina/*; do
+                mkdir -p ${cfg.baseDir}/conf/Catalina/localhost
+                ln -sfn $j ${cfg.baseDir}/conf/Catalina/localhost/`basename $j`
+              done
+            fi
+          fi
+        done
+
+        ${toString (map (virtualHost: ''
+          # Create webapps directory for the virtual host
+          mkdir -p ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps
+
+          # Modify ownership
+          chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps
+
+          # Symlink all the given web applications files or paths into the webapps/ directory
+          # of this virtual host
+          for i in "${if virtualHost ? webapps then toString virtualHost.webapps else ""}"; do
+            if [ -f $i ]; then
+              # If the given web application is a file, symlink it into the webapps/ directory
+              ln -sfn $i ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $i`
+            elif [ -d $i ]; then
+              # If the given web application is a directory, then iterate over the files
+              # in the special purpose directories and symlink them into the tomcat tree
+
+              for j in $i/webapps/*; do
+                ln -sfn $j ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $j`
+              done
+
+              # Also symlink the configuration files if they are included
+              if [ -d $i/conf/Catalina ]; then
+                for j in $i/conf/Catalina/*; do
+                  mkdir -p ${cfg.baseDir}/conf/Catalina/${virtualHost.name}
+                  ln -sfn $j ${cfg.baseDir}/conf/Catalina/${virtualHost.name}/`basename $j`
+                done
+              fi
+            fi
+          done
+        '') cfg.virtualHosts)}
+
+        ${optionalString cfg.axis2.enable ''
+          # Copy the Axis2 web application
+          cp -av ${pkgs.axis2}/webapps/axis2 ${cfg.baseDir}/webapps
+
+          # Turn off addressing, which causes many errors
+          sed -i -e 's%<module ref="addressing"/>%<!-- <module ref="addressing"/> -->%' ${cfg.baseDir}/webapps/axis2/WEB-INF/conf/axis2.xml
+
+          # Modify permissions on the Axis2 application
+          chown -R ${cfg.user}:${cfg.group} ${cfg.baseDir}/webapps/axis2
+
+          # Symlink all the given web service files or paths into the webapps/axis2/WEB-INF/services directory
+          for i in ${toString cfg.axis2.services}; do
+            if [ -f $i ]; then
+              # If the given web service is a file, symlink it into the webapps/axis2/WEB-INF/services
+              ln -sfn $i ${cfg.baseDir}/webapps/axis2/WEB-INF/services/`basename $i`
+            elif [ -d $i ]; then
+              # If the given web application is a directory, then iterate over the files
+              # in the special purpose directories and symlink them into the tomcat tree
+
+              for j in $i/webapps/axis2/WEB-INF/services/*; do
+                ln -sfn $j ${cfg.baseDir}/webapps/axis2/WEB-INF/services/`basename $j`
+              done
+
+              # Also symlink the configuration files if they are included
+              if [ -d $i/conf/Catalina ]; then
+                for j in $i/conf/Catalina/*; do
+                  ln -sfn $j ${cfg.baseDir}/conf/Catalina/localhost/`basename $j`
+                done
+              fi
+            fi
+          done
+        ''}
+      '';
+
+      serviceConfig = {
+        Type = "forking";
+        PermissionsStartOnly = true;
+        PIDFile="/run/tomcat/tomcat.pid";
+        RuntimeDirectory = "tomcat";
+        User = cfg.user;
+        Environment=[
+          "CATALINA_BASE=${cfg.baseDir}"
+          "CATALINA_PID=/run/tomcat/tomcat.pid"
+          "JAVA_HOME='${cfg.jdk}'"
+          "JAVA_OPTS='${builtins.toString cfg.javaOpts}'"
+          "CATALINA_OPTS='${builtins.toString cfg.catalinaOpts}'"
+        ] ++ cfg.extraEnvironment;
+        ExecStart = "${tomcat}/bin/startup.sh";
+        ExecStop = "${tomcat}/bin/shutdown.sh";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/traefik.nix b/nixpkgs/nixos/modules/services/web-servers/traefik.nix
new file mode 100644
index 000000000000..5bac895d43ac
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/traefik.nix
@@ -0,0 +1,124 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.traefik;
+  configFile =
+    if cfg.configFile == null then
+      pkgs.runCommand "config.toml" {
+        buildInputs = [ pkgs.remarshal ];
+        preferLocalBuild = true;
+      } ''
+        remarshal -if json -of toml \
+          < ${pkgs.writeText "config.json" (builtins.toJSON cfg.configOptions)} \
+          > $out
+      ''
+    else cfg.configFile;
+
+in {
+  options.services.traefik = {
+    enable = mkEnableOption "Traefik web server";
+
+    configFile = mkOption {
+      default = null;
+      example = literalExample "/path/to/config.toml";
+      type = types.nullOr types.path;
+      description = ''
+        Path to verbatim traefik.toml to use.
+        (Using that option has precedence over <literal>configOptions</literal>)
+      '';
+    };
+
+    configOptions = mkOption {
+      description = ''
+        Config for Traefik.
+      '';
+      type = types.attrs;
+      default = {
+        defaultEntryPoints = ["http"];
+        entryPoints.http.address = ":80";
+      };
+      example = {
+        defaultEntrypoints = [ "http" ];
+        web.address = ":8080";
+        entryPoints.http.address = ":80";
+
+        file = {};
+        frontends = {
+          frontend1 = {
+            backend = "backend1";
+            routes.test_1.rule = "Host:localhost";
+          };
+        };
+        backends.backend1 = {
+          servers.server1.url = "http://localhost:8000";
+        };
+      };
+    };
+
+    dataDir = mkOption {
+      default = "/var/lib/traefik";
+      type = types.path;
+      description = ''
+      Location for any persistent data traefik creates, ie. acme
+      '';
+    };
+
+    group = mkOption {
+      default = "traefik";
+      type = types.string;
+      example = "docker";
+      description = ''
+        Set the group that traefik runs under.
+        For the docker backend this needs to be set to <literal>docker</literal> instead.
+      '';
+    };
+
+    package = mkOption {
+      default = pkgs.traefik;
+      defaultText = "pkgs.traefik";
+      type = types.package;
+      description = "Traefik package to use.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 traefik traefik - -"
+    ];
+
+    systemd.services.traefik = {
+      description = "Traefik web server";
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = ''${cfg.package.bin}/bin/traefik --configfile=${configFile}'';
+        Type = "simple";
+        User = "traefik";
+        Group = cfg.group;
+        Restart = "on-failure";
+        StartLimitInterval = 86400;
+        StartLimitBurst = 5;
+        AmbientCapabilities = "cap_net_bind_service";
+        CapabilityBoundingSet = "cap_net_bind_service";
+        NoNewPrivileges = true;
+        LimitNPROC = 64;
+        LimitNOFILE = 1048576;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHome = true;
+        ProtectSystem = "full";
+        ReadWriteDirectories = cfg.dataDir;
+      };
+    };
+
+    users.users.traefik = {
+      group = "traefik";
+      home = cfg.dataDir;
+      createHome = true;
+    };
+
+    users.groups.traefik = {};
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/unit/default.nix b/nixpkgs/nixos/modules/services/web-servers/unit/default.nix
new file mode 100644
index 000000000000..a4a9d370d644
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/unit/default.nix
@@ -0,0 +1,125 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.unit;
+
+  configFile = pkgs.writeText "unit.json" cfg.config;
+
+in {
+  options = {
+    services.unit = {
+      enable = mkEnableOption "Unit App Server";
+      package = mkOption {
+        type = types.package;
+        default = pkgs.unit;
+        defaultText = "pkgs.unit";
+        description = "Unit package to use.";
+      };
+      user = mkOption {
+        type = types.str;
+        default = "unit";
+        description = "User account under which unit runs.";
+      };
+      group = mkOption {
+        type = types.str;
+        default = "unit";
+        description = "Group account under which unit runs.";
+      };
+      stateDir = mkOption {
+        default = "/var/spool/unit";
+        description = "Unit data directory.";
+      };
+      logDir = mkOption {
+        default = "/var/log/unit";
+        description = "Unit log directory.";
+      };
+      config = mkOption {
+        type = types.str;
+        default = ''
+          {
+            "listeners": {},
+            "applications": {}
+          }
+        '';
+        example = literalExample ''
+          {
+            "listeners": {
+              "*:8300": {
+                "application": "example-php-72"
+              }
+            },
+            "applications": {
+              "example-php-72": {
+                "type": "php 7.2",
+                "processes": 4,
+                "user": "nginx",
+                "group": "nginx",
+                "root": "/var/www",
+                "index": "index.php",
+                "options": {
+                  "file": "/etc/php.d/default.ini",
+                  "admin": {
+                    "max_execution_time": "30",
+                    "max_input_time": "30",
+                    "display_errors": "off",
+                    "display_startup_errors": "off",
+                    "open_basedir": "/dev/urandom:/proc/cpuinfo:/proc/meminfo:/etc/ssl/certs:/var/www",
+                    "disable_functions": "exec,passthru,shell_exec,system"
+                  }
+                }
+              }
+            }
+          }
+        '';
+        description = "Unit configuration in JSON format. More details here https://unit.nginx.org/configuration";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.logDir}' 0750 ${cfg.user} ${cfg.group} - -"
+     ];
+
+    systemd.services.unit = {
+      description = "Unit App Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = with pkgs; [ curl ];
+      preStart = ''
+        test -f '/run/unit/control.unit.sock' || rm -f '/run/unit/control.unit.sock'
+      '';
+      postStart = ''
+        curl -X PUT --data-binary '@${configFile}' --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config'
+      '';
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID";
+        CapabilityBoundingSet = "CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID";
+        ExecStart = ''
+          ${cfg.package}/bin/unitd --control 'unix:/run/unit/control.unit.sock' --pid '/run/unit/unit.pid' \
+                                   --log '${cfg.logDir}/unit.log' --state '${cfg.stateDir}' --no-daemon \
+                                   --user ${cfg.user} --group ${cfg.group}
+        '';
+        RuntimeDirectory = "unit";
+        RuntimeDirectoryMode = "0750";
+      };
+    };
+
+    users.users = optionalAttrs (cfg.user == "unit") (singleton {
+      name = "unit";
+      group = cfg.group;
+    });
+
+    users.groups = optionalAttrs (cfg.group == "unit") (singleton {
+      name = "unit";
+    });
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix b/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix
new file mode 100644
index 000000000000..3f858d90fa46
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix
@@ -0,0 +1,160 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.uwsgi;
+
+  uwsgi = pkgs.uwsgi.override {
+    plugins = cfg.plugins;
+  };
+
+  buildCfg = name: c:
+    let
+      plugins =
+        if any (n: !any (m: m == n) cfg.plugins) (c.plugins or [])
+        then throw "`plugins` attribute in UWSGI configuration contains plugins not in config.services.uwsgi.plugins"
+        else c.plugins or cfg.plugins;
+
+      hasPython = v: filter (n: n == "python${v}") plugins != [];
+      hasPython2 = hasPython "2";
+      hasPython3 = hasPython "3";
+
+      python =
+        if hasPython2 && hasPython3 then
+          throw "`plugins` attribute in UWSGI configuration shouldn't contain both python2 and python3"
+        else if hasPython2 then uwsgi.python2
+        else if hasPython3 then uwsgi.python3
+        else null;
+
+      pythonEnv = python.withPackages (c.pythonPackages or (self: []));
+
+      uwsgiCfg = {
+        uwsgi =
+          if c.type == "normal"
+            then {
+              inherit plugins;
+            } // removeAttrs c [ "type" "pythonPackages" ]
+              // optionalAttrs (python != null) {
+                pythonpath = "${pythonEnv}/${python.sitePackages}";
+                env =
+                  # Argh, uwsgi expects list of key-values there instead of a dictionary.
+                  let env' = c.env or [];
+                      getPath =
+                        x: if hasPrefix "PATH=" x
+                           then substring (stringLength "PATH=") (stringLength x) x
+                           else null;
+                      oldPaths = filter (x: x != null) (map getPath env');
+                  in env' ++ [ "PATH=${optionalString (oldPaths != []) "${last oldPaths}:"}${pythonEnv}/bin" ];
+              }
+          else if c.type == "emperor"
+            then {
+              emperor = if builtins.typeOf c.vassals != "set" then c.vassals
+                        else pkgs.buildEnv {
+                          name = "vassals";
+                          paths = mapAttrsToList buildCfg c.vassals;
+                        };
+            } // removeAttrs c [ "type" "vassals" ]
+          else throw "`type` attribute in UWSGI configuration should be either 'normal' or 'emperor'";
+      };
+
+    in pkgs.writeTextDir "${name}.json" (builtins.toJSON uwsgiCfg);
+
+in {
+
+  options = {
+    services.uwsgi = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable uWSGI";
+      };
+
+      runDir = mkOption {
+        type = types.string;
+        default = "/run/uwsgi";
+        description = "Where uWSGI communication sockets can live";
+      };
+
+      instance = mkOption {
+        type = types.attrs;
+        default = {
+          type = "normal";
+        };
+        example = literalExample ''
+          {
+            type = "emperor";
+            vassals = {
+              moin = {
+                type = "normal";
+                pythonPackages = self: with self; [ moinmoin ];
+                socket = "${config.services.uwsgi.runDir}/uwsgi.sock";
+              };
+            };
+          }
+        '';
+        description = ''
+          uWSGI configuration. It awaits an attribute <literal>type</literal> inside which can be either
+          <literal>normal</literal> or <literal>emperor</literal>.
+
+          For <literal>normal</literal> mode you can specify <literal>pythonPackages</literal> as a function
+          from libraries set into a list of libraries. <literal>pythonpath</literal> will be set accordingly.
+
+          For <literal>emperor</literal> mode, you should use <literal>vassals</literal> attribute
+          which should be either a set of names and configurations or a path to a directory.
+
+          Other attributes will be used in configuration file as-is. Notice that you can redefine
+          <literal>plugins</literal> setting here.
+        '';
+      };
+
+      plugins = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "Plugins used with uWSGI";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "uwsgi";
+        description = "User account under which uwsgi runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "uwsgi";
+        description = "Group account under which uwsgi runs.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.uwsgi = {
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        mkdir -p ${cfg.runDir}
+        chown ${cfg.user}:${cfg.group} ${cfg.runDir}
+      '';
+      serviceConfig = {
+        Type = "notify";
+        ExecStart = "${uwsgi}/bin/uwsgi --uid ${cfg.user} --gid ${cfg.group} --json ${buildCfg "server" cfg.instance}/server.json";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
+        NotifyAccess = "main";
+        KillSignal = "SIGQUIT";
+      };
+    };
+
+    users.users = optionalAttrs (cfg.user == "uwsgi") (singleton
+      { name = "uwsgi";
+        group = cfg.group;
+        uid = config.ids.uids.uwsgi;
+      });
+
+    users.groups = optionalAttrs (cfg.group == "uwsgi") (singleton
+      { name = "uwsgi";
+        gid = config.ids.gids.uwsgi;
+      });
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix b/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix
new file mode 100644
index 000000000000..63f967185c2d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix
@@ -0,0 +1,113 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+let
+  cfg = config.services.varnish;
+
+  commandLine = "-f ${pkgs.writeText "default.vcl" cfg.config}" +
+      optionalString (cfg.extraModules != []) " -p vmod_path='${makeSearchPathOutput "lib" "lib/varnish/vmods" ([cfg.package] ++ cfg.extraModules)}' -r vmod_path";
+in
+{
+  options = {
+    services.varnish = {
+      enable = mkEnableOption "Varnish Server";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.varnish5;
+        defaultText = "pkgs.varnish5";
+        description = ''
+          The package to use
+        '';
+      };
+
+      http_address = mkOption {
+        type = types.str;
+        default = "*:6081";
+        description = "
+          HTTP listen address and port.
+        ";
+      };
+
+      config = mkOption {
+        type = types.lines;
+        description = "
+          Verbatim default.vcl configuration.
+        ";
+      };
+
+      stateDir = mkOption {
+        type = types.path;
+        default = "/var/spool/varnish/${config.networking.hostName}";
+        description = "
+          Directory holding all state for Varnish to run.
+        ";
+      };
+
+      extraModules = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        example = literalExample "[ pkgs.varnish5Packages.geoip ]";
+        description = "
+          Varnish modules (except 'std').
+        ";
+      };
+
+      extraCommandLine = mkOption {
+        type = types.str;
+        default = "";
+        example = "-s malloc,256M";
+        description = "
+          Command line switches for varnishd (run 'varnishd -?' to get list of options)
+        ";
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.varnish = {
+      description = "Varnish";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      preStart = ''
+        mkdir -p ${cfg.stateDir}
+        chown -R varnish:varnish ${cfg.stateDir}
+      '';
+      postStop = ''
+        rm -rf ${cfg.stateDir}
+      '';
+      serviceConfig = {
+        Type = "simple";
+        PermissionsStartOnly = true;
+        ExecStart = "${cfg.package}/sbin/varnishd -a ${cfg.http_address} -n ${cfg.stateDir} -F ${cfg.extraCommandLine} ${commandLine}";
+        Restart = "always";
+        RestartSec = "5s";
+        User = "varnish";
+        Group = "varnish";
+        AmbientCapabilities = "cap_net_bind_service";
+        NoNewPrivileges = true;
+        LimitNOFILE = 131072;
+      };
+    };
+
+    environment.systemPackages = [ cfg.package ];
+
+    # check .vcl syntax at compile time (e.g. before nixops deployment)
+    system.extraDependencies = [
+      (pkgs.stdenv.mkDerivation {
+        name = "check-varnish-syntax";
+        buildCommand = "${cfg.package}/sbin/varnishd -C ${commandLine} 2> $out || (cat $out; exit 1)";
+      })
+    ];
+
+    users.users.varnish = {
+      group = "varnish";
+      uid = config.ids.uids.varnish;
+    };
+
+    users.groups.varnish.gid = config.ids.uids.varnish;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/zope2.nix b/nixpkgs/nixos/modules/services/web-servers/zope2.nix
new file mode 100644
index 000000000000..4cad2a2ff777
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/zope2.nix
@@ -0,0 +1,258 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.zope2;
+
+  zope2Opts = { name, ... }: {
+    options = {
+
+      name = mkOption {
+        default = "${name}";
+        type = types.string;
+        description = "The name of the zope2 instance. If undefined, the name of the attribute set will be used.";
+      };
+
+      threads = mkOption {
+        default = 2;
+        type = types.int;
+        description = "Specify the number of threads that Zope's ZServer web server will use to service requests. ";
+      };
+
+      http_address = mkOption {
+        default = "localhost:8080";
+        type = types.string;
+        description = "Give a port and address for the HTTP server.";
+      };
+
+      user = mkOption {
+        default = "zope2";
+        type = types.string;
+        description = "The name of the effective user for the Zope process.";
+      };
+
+      clientHome = mkOption {
+        default = "/var/lib/zope2/${name}";
+        type = types.string;
+        description = "Home directory of zope2 instance.";
+      };
+      extra = mkOption {
+        default =
+          ''
+          <zodb_db main>
+            mount-point /
+            cache-size 30000
+            <blobstorage>
+                blob-dir /var/lib/zope2/${name}/blobstorage
+                <filestorage>
+                path /var/lib/zope2/${name}/filestorage/Data.fs
+                </filestorage>
+            </blobstorage>
+          </zodb_db>
+          '';
+        type = types.string;
+        description = "Extra zope.conf";
+      };
+
+      packages = mkOption {
+        type = types.listOf types.package;
+        description = "The list of packages you want to make available to the zope2 instance.";
+      };
+
+    };
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.zope2.instances = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule zope2Opts);
+      example = literalExample ''
+        {
+          plone01 = {
+            http_address = "127.0.0.1:8080";
+            extra =
+              '''
+              <zodb_db main>
+                mount-point /
+                cache-size 30000
+                <blobstorage>
+                    blob-dir /var/lib/zope2/plone01/blobstorage
+                    <filestorage>
+                    path /var/lib/zope2/plone01/filestorage/Data.fs
+                    </filestorage>
+                </blobstorage>
+              </zodb_db>
+              ''';
+          };
+        }
+      '';
+      description = "zope2 instances to be created automaticaly by the system.";
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf (cfg.instances != {}) {
+
+    users.users.zope2.uid = config.ids.uids.zope2;
+
+    systemd.services =
+      let
+
+        createZope2Instance = opts: name:
+          let
+            interpreter = pkgs.writeScript "interpreter"
+              ''
+              import sys
+
+              _interactive = True
+              if len(sys.argv) > 1:
+                  _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:')
+                  _interactive = False
+                  for (_opt, _val) in _options:
+                      if _opt == '-i':
+                          _interactive = True
+                      elif _opt == '-c':
+                          exec _val
+                      elif _opt == '-m':
+                          sys.argv[1:] = _args
+                          _args = []
+                          __import__("runpy").run_module(
+                              _val, {}, "__main__", alter_sys=True)
+
+                  if _args:
+                      sys.argv[:] = _args
+                      __file__ = _args[0]
+                      del _options, _args
+                      execfile(__file__)
+
+              if _interactive:
+                  del _interactive
+                  __import__("code").interact(banner="", local=globals())
+              '';
+            env = pkgs.buildEnv {
+              name = "zope2-${name}-env";
+              paths = [
+                pkgs.python27
+                pkgs.python27Packages.recursivePthLoader
+                pkgs.python27Packages."plone.recipe.zope2instance"
+              ] ++ attrValues pkgs.python27.modules
+                ++ opts.packages;
+              postBuild =
+                ''
+                echo "#!$out/bin/python" > $out/bin/interpreter
+                cat ${interpreter} >> $out/bin/interpreter
+                '';
+            };
+            conf = pkgs.writeText "zope2-${name}-conf"
+              ''
+              %define INSTANCEHOME ${env}
+              instancehome $INSTANCEHOME
+              %define CLIENTHOME ${opts.clientHome}/${opts.name}
+              clienthome $CLIENTHOME
+
+              debug-mode off
+              security-policy-implementation C
+              verbose-security off
+              default-zpublisher-encoding utf-8
+              zserver-threads ${toString opts.threads}
+              effective-user ${opts.user}
+
+              pid-filename ${opts.clientHome}/${opts.name}/pid
+              lock-filename ${opts.clientHome}/${opts.name}/lock
+              python-check-interval 1000
+              enable-product-installation off
+
+              <environment>
+                zope_i18n_compile_mo_files false
+              </environment>
+
+              <eventlog>
+              level INFO
+              <logfile>
+                  path /var/log/zope2/${name}.log
+                  level INFO
+              </logfile>
+              </eventlog>
+
+              <logger access>
+              level WARN
+              <logfile>
+                  path /var/log/zope2/${name}-Z2.log
+                  format %(message)s
+              </logfile>
+              </logger>
+
+              <http-server>
+              address ${opts.http_address}
+              </http-server>
+
+              <zodb_db temporary>
+              <temporarystorage>
+                  name temporary storage for sessioning
+              </temporarystorage>
+              mount-point /temp_folder
+              container-class Products.TemporaryFolder.TemporaryContainer
+              </zodb_db>
+
+              ${opts.extra}
+              '';
+            ctlScript = pkgs.writeScript "zope2-${name}-ctl-script"
+              ''
+              #!${env}/bin/python
+
+              import sys
+              import plone.recipe.zope2instance.ctl
+
+              if __name__ == '__main__':
+                  sys.exit(plone.recipe.zope2instance.ctl.main(
+                      ["-C", "${conf}"]
+                      + sys.argv[1:]))
+              '';
+
+            ctl = pkgs.writeScript "zope2-${name}-ctl"
+              ''
+              #!${pkgs.bash}/bin/bash -e
+              export PYTHONHOME=${env}
+              exec ${ctlScript} "$@"
+              '';
+          in {
+            #description = "${name} instance";
+            after = [ "network.target" ];  # with RelStorage also add "postgresql.service"
+            wantedBy = [ "multi-user.target" ];
+            path = opts.packages;
+            preStart =
+              ''
+              mkdir -p /var/log/zope2/
+              touch /var/log/zope2/${name}.log
+              touch /var/log/zope2/${name}-Z2.log
+              chown ${opts.user} /var/log/zope2/${name}.log
+              chown ${opts.user} /var/log/zope2/${name}-Z2.log
+
+              mkdir -p ${opts.clientHome}/filestorage ${opts.clientHome}/blobstorage
+              mkdir -p ${opts.clientHome}/${opts.name}
+              chown ${opts.user} ${opts.clientHome} -R
+
+              ${ctl} adduser admin admin
+              '';
+
+            serviceConfig.Type = "forking";
+            serviceConfig.ExecStart = "${ctl} start";
+            serviceConfig.ExecStop = "${ctl} stop";
+            serviceConfig.ExecReload = "${ctl} restart";
+          };
+
+      in listToAttrs (map (name: { name = "zope2-${name}"; value = createZope2Instance (builtins.getAttr name cfg.instances) name; }) (builtins.attrNames cfg.instances));
+
+  };
+
+}