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.nix745
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/apache-httpd/location-options.nix54
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix275
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/caddy.nix111
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix77
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix74
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/hitch/default.nix111
-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.nix82
-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.nix825
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix94
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix104
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix229
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix284
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/shellinabox.nix122
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/tomcat.nix421
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/traefik.nix170
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/ttyd.nix196
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/unit/default.nix150
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/uwsgi.nix190
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/varnish/default.nix113
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/zope2.nix258
30 files changed, 5793 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..8abee7130d7c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -0,0 +1,745 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.httpd;
+
+  runtimeDir = "/run/httpd";
+
+  pkg = cfg.package.out;
+
+  httpdConf = cfg.configFile;
+
+  php = cfg.phpPackage.override { apacheHttpd = pkg; };
+
+  phpMajorVersion = lib.versions.major (lib.getVersion php);
+
+  mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = pkg; };
+
+  vhosts = attrValues cfg.virtualHosts;
+
+  mkListenInfo = hostOpts:
+    if hostOpts.listen != [] then hostOpts.listen
+    else (
+      optional (hostOpts.onlySSL || hostOpts.addSSL || hostOpts.forceSSL) { ip = "*"; port = 443; ssl = true; } ++
+      optional (!hostOpts.onlySSL) { ip = "*"; port = 80; ssl = false; }
+    );
+
+  listenInfo = unique (concatMap mkListenInfo vhosts);
+
+  enableHttp2 = any (vhost: vhost.http2) vhosts;
+  enableSSL = any (listen: listen.ssl) listenInfo;
+  enableUserDir = any (vhost: vhost.enableUserDir) vhosts;
+
+  # NOTE: generally speaking order of modules is very important
+  modules =
+    [ # required apache modules our httpd service cannot run without
+      "authn_core" "authz_core"
+      "log_config"
+      "mime" "autoindex" "negotiation" "dir"
+      "alias" "rewrite"
+      "unixd" "slotmem_shm" "socache_shmcb"
+      "mpm_${cfg.mpm}"
+    ]
+    ++ (if cfg.mpm == "prefork" then [ "cgi" ] else [ "cgid" ])
+    ++ optional enableHttp2 "http2"
+    ++ optional enableSSL "ssl"
+    ++ optional enableUserDir "userdir"
+    ++ optional cfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
+    ++ optional cfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
+    ++ optional cfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
+    ++ cfg.extraModules;
+
+  loggingConf = (if cfg.logFormat != "none" then ''
+    ErrorLog ${cfg.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 ${cfg.logDir}/access.log ${cfg.logFormat}
+  '' else ''
+    ErrorLog /dev/null
+  '');
+
+
+  browserHacks = ''
+    <IfModule mod_setenvif.c>
+        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
+    </IfModule>
+  '';
+
+
+  sslConf = ''
+    <IfModule mod_ssl.c>
+        SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000)
+
+        Mutex posixsem
+
+        SSLRandomSeed startup builtin
+        SSLRandomSeed connect builtin
+
+        SSLProtocol ${cfg.sslProtocols}
+        SSLCipherSuite ${cfg.sslCiphers}
+        SSLHonorCipherOrder on
+    </IfModule>
+  '';
+
+
+  mimeConf = ''
+    TypesConfig ${pkg}/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 ${pkg}/conf/magic
+    </IfModule>
+  '';
+
+  mkVHostConf = hostOpts:
+    let
+      adminAddr = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr;
+      listen = filter (listen: !listen.ssl) (mkListenInfo hostOpts);
+      listenSSL = filter (listen: listen.ssl) (mkListenInfo hostOpts);
+
+      useACME = hostOpts.enableACME || hostOpts.useACMEHost != null;
+      sslCertDir =
+        if hostOpts.enableACME then config.security.acme.certs.${hostOpts.hostName}.directory
+        else if hostOpts.useACMEHost != null then config.security.acme.certs.${hostOpts.useACMEHost}.directory
+        else abort "This case should never happen.";
+
+      sslServerCert = if useACME then "${sslCertDir}/full.pem" else hostOpts.sslServerCert;
+      sslServerKey = if useACME then "${sslCertDir}/key.pem" else hostOpts.sslServerKey;
+      sslServerChain = if useACME then "${sslCertDir}/fullchain.pem" else hostOpts.sslServerChain;
+
+      acmeChallenge = optionalString useACME ''
+        Alias /.well-known/acme-challenge/ "${hostOpts.acmeRoot}/.well-known/acme-challenge/"
+        <Directory "${hostOpts.acmeRoot}">
+            AllowOverride None
+            Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
+            Require method GET POST OPTIONS
+            Require all granted
+        </Directory>
+      '';
+    in
+      optionalString (listen != []) ''
+        <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listen}>
+            ServerName ${hostOpts.hostName}
+            ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases}
+            ServerAdmin ${adminAddr}
+            <IfModule mod_ssl.c>
+                SSLEngine off
+            </IfModule>
+            ${acmeChallenge}
+            ${if hostOpts.forceSSL then ''
+              <IfModule mod_rewrite.c>
+                  RewriteEngine on
+                  RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge [NC]
+                  RewriteCond %{HTTPS} off
+                  RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
+              </IfModule>
+            '' else mkVHostCommonConf hostOpts}
+        </VirtualHost>
+      '' +
+      optionalString (listenSSL != []) ''
+        <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listenSSL}>
+            ServerName ${hostOpts.hostName}
+            ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases}
+            ServerAdmin ${adminAddr}
+            SSLEngine on
+            SSLCertificateFile ${sslServerCert}
+            SSLCertificateKeyFile ${sslServerKey}
+            ${optionalString (sslServerChain != null) "SSLCertificateChainFile ${sslServerChain}"}
+            ${optionalString hostOpts.http2 "Protocols h2 h2c http/1.1"}
+            ${acmeChallenge}
+            ${mkVHostCommonConf hostOpts}
+        </VirtualHost>
+      ''
+  ;
+
+  mkVHostCommonConf = hostOpts:
+    let
+      documentRoot = if hostOpts.documentRoot != null
+        then hostOpts.documentRoot
+        else pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out"
+      ;
+
+      mkLocations = locations: concatStringsSep "\n" (map (config: ''
+        <Location ${config.location}>
+          ${optionalString (config.proxyPass != null) ''
+            <IfModule mod_proxy.c>
+                ProxyPass ${config.proxyPass}
+                ProxyPassReverse ${config.proxyPass}
+            </IfModule>
+          ''}
+          ${optionalString (config.index != null) ''
+            <IfModule mod_dir.c>
+                DirectoryIndex ${config.index}
+            </IfModule>
+          ''}
+          ${optionalString (config.alias != null) ''
+            <IfModule mod_alias.c>
+                Alias "${config.alias}"
+            </IfModule>
+          ''}
+          ${config.extraConfig}
+        </Location>
+      '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)));
+    in
+      ''
+        ${optionalString cfg.logPerVirtualHost ''
+          ErrorLog ${cfg.logDir}/error-${hostOpts.hostName}.log
+          CustomLog ${cfg.logDir}/access-${hostOpts.hostName}.log ${hostOpts.logFormat}
+        ''}
+
+        ${optionalString (hostOpts.robotsEntries != "") ''
+          Alias /robots.txt ${pkgs.writeText "robots.txt" hostOpts.robotsEntries}
+        ''}
+
+        DocumentRoot "${documentRoot}"
+
+        <Directory "${documentRoot}">
+            Options Indexes FollowSymLinks
+            AllowOverride None
+            Require all granted
+        </Directory>
+
+        ${optionalString hostOpts.enableUserDir ''
+          UserDir public_html
+          UserDir disabled root
+          <Directory "/home/*/public_html">
+              AllowOverride FileInfo AuthConfig Limit Indexes
+              Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
+              <Limit GET POST OPTIONS>
+                  Require all granted
+              </Limit>
+              <LimitExcept GET POST OPTIONS>
+                  Require all denied
+              </LimitExcept>
+          </Directory>
+        ''}
+
+        ${optionalString (hostOpts.globalRedirect != null && hostOpts.globalRedirect != "") ''
+          RedirectPermanent / ${hostOpts.globalRedirect}
+        ''}
+
+        ${
+          let makeDirConf = elem: ''
+                Alias ${elem.urlPath} ${elem.dir}/
+                <Directory ${elem.dir}>
+                    Options +Indexes
+                    Require all granted
+                    AllowOverride All
+                </Directory>
+              '';
+          in concatMapStrings makeDirConf hostOpts.servedDirs
+        }
+
+        ${mkLocations hostOpts.locations}
+        ${hostOpts.extraConfig}
+      ''
+  ;
+
+
+  confFile = pkgs.writeText "httpd.conf" ''
+
+    ServerRoot ${pkg}
+    ServerName ${config.networking.hostName}
+    DefaultRuntimeDir ${runtimeDir}/runtime
+
+    PidFile ${runtimeDir}/httpd.pid
+
+    ${optionalString (cfg.mpm != "prefork") ''
+      # mod_cgid requires this.
+      ScriptSock ${runtimeDir}/cgisock
+    ''}
+
+    <IfModule prefork.c>
+        MaxClients           ${toString cfg.maxClients}
+        MaxRequestsPerChild  ${toString cfg.maxRequestsPerChild}
+    </IfModule>
+
+    ${let
+        toStr = listen: "Listen ${listen.ip}:${toString listen.port} ${if listen.ssl then "https" else "http"}";
+        uniqueListen = uniqList {inputList = map toStr listenInfo;};
+      in concatStringsSep "\n" uniqueListen
+    }
+
+    User ${cfg.user}
+    Group ${cfg.group}
+
+    ${let
+        mkModule = module:
+          if isString module then { name = module; path = "${pkg}/modules/mod_${module}.so"; }
+          else if isAttrs module then { inherit (module) name path; }
+          else throw "Expecting either a string or attribute set including a name and path.";
+      in
+        concatMapStringsSep "\n" (module: "LoadModule ${module.name}_module ${module.path}") (unique (map mkModule modules))
+    }
+
+    AddHandler type-map var
+
+    <Files ~ "^\.ht">
+        Require all denied
+    </Files>
+
+    ${mimeConf}
+    ${loggingConf}
+    ${browserHacks}
+
+    Include ${pkg}/conf/extra/httpd-default.conf
+    Include ${pkg}/conf/extra/httpd-autoindex.conf
+    Include ${pkg}/conf/extra/httpd-multilang-errordoc.conf
+    Include ${pkg}/conf/extra/httpd-languages.conf
+
+    TraceEnable off
+
+    ${sslConf}
+
+    # Fascist default - deny access to everything.
+    <Directory />
+        Options FollowSymLinks
+        AllowOverride None
+        Require all denied
+    </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>
+        Require all granted
+    </Directory>
+
+    ${cfg.extraConfig}
+
+    ${concatMapStringsSep "\n" mkVHostConf vhosts}
+  '';
+
+  # Generate the PHP configuration file.  Should probably be factored
+  # out into a separate module.
+  phpIni = pkgs.runCommand "php.ini"
+    { options = cfg.phpOptions;
+      preferLocalBuild = true;
+    }
+    ''
+      cat ${php}/etc/php.ini > $out
+      cat ${php.phpIni} > $out
+      echo "$options" >> $out
+    '';
+
+in
+
+
+{
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "httpd" "extraSubservices" ] "Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.")
+    (mkRemovedOptionModule [ "services" "httpd" "stateDir" ] "The httpd module now uses /run/httpd as a runtime directory.")
+    (mkRenamedOptionModule [ "services" "httpd" "multiProcessingModule" ] [ "services" "httpd" "mpm" ])
+
+    # virtualHosts options
+    (mkRemovedOptionModule [ "services" "httpd" "documentRoot" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "enableSSL" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "enableUserDir" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "globalRedirect" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "hostName" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "listen" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "robotsEntries" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "servedDirs" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "servedFiles" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "serverAliases" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "sslServerCert" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "sslServerChain" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "sslServerKey" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+  ];
+
+  # interface
+
+  options = {
+
+    services.httpd = {
+
+      enable = mkEnableOption "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 = ''
+          Configuration lines appended to the generated Apache
+          configuration file. Note that this mechanism will not work
+          when <option>configFile</option> is overridden.
+        '';
+      };
+
+      extraModules = mkOption {
+        type = types.listOf types.unspecified;
+        default = [];
+        example = literalExample ''
+          [
+            "proxy_connect"
+            { name = "jk"; path = "''${pkgs.tomcat_connectors}/modules/mod_jk.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.
+        '';
+      };
+
+      adminAddr = mkOption {
+        type = types.str;
+        example = "admin@example.org";
+        description = "E-mail address of the server administrator.";
+      };
+
+      logFormat = mkOption {
+        type = types.str;
+        default = "common";
+        example = "combined";
+        description = ''
+          Log format for log files. Possible values are: combined, common, referer, agent.
+          See <link xlink:href="https://httpd.apache.org/docs/2.4/logs.html"/> for more details.
+        '';
+      };
+
+      logPerVirtualHost = mkOption {
+        type = types.bool;
+        default = true;
+        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 children processes run.
+
+          If you require the main httpd process to run as
+          <literal>root</literal> add the following configuration:
+          <programlisting>
+          systemd.services.httpd.serviceConfig.User = lib.mkForce "root";
+          </programlisting>
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "wwwrun";
+        description = ''
+          Group under which httpd children processes run.
+        '';
+      };
+
+      logDir = mkOption {
+        type = types.path;
+        default = "/var/log/httpd";
+        description = ''
+          Directory for Apache's log files. It is created automatically.
+        '';
+      };
+
+      virtualHosts = mkOption {
+        type = with types; attrsOf (submodule (import ./vhost-options.nix));
+        default = {
+          localhost = {
+            documentRoot = "${pkg}/htdocs";
+          };
+        };
+        example = literalExample ''
+          {
+            "foo.example.com" = {
+              forceSSL = true;
+              documentRoot = "/var/www/foo.example.com"
+            };
+            "bar.example.com" = {
+              addSSL = true;
+              documentRoot = "/var/www/bar.example.com";
+            };
+          }
+        '';
+        description = ''
+          Specification of the virtual hosts served by Apache. Each
+          element should be an attribute set specifying the
+          configuration of the virtual 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>.
+        '';
+      };
+
+      mpm = mkOption {
+        type = types.enum [ "event" "prefork" "worker" ];
+        default = "event";
+        example = "worker";
+        description =
+          ''
+            Multi-processing module to be used by Apache. Available
+            modules are <literal>prefork</literal> (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>
+            (the default; 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 -TLSv1.1";
+        example = "All -SSLv2 -SSLv3";
+        description = "Allowed SSL/TLS protocol versions.";
+      };
+    };
+
+  };
+
+  # implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      {
+        assertion = all (hostOpts: !hostOpts.enableSSL) vhosts;
+        message = ''
+          The option `services.httpd.virtualHosts.<name>.enableSSL` no longer has any effect; please remove it.
+          Select one of `services.httpd.virtualHosts.<name>.addSSL`, `services.httpd.virtualHosts.<name>.forceSSL`,
+          or `services.httpd.virtualHosts.<name>.onlySSL`.
+        '';
+      }
+      {
+        assertion = all (hostOpts: with hostOpts; !(addSSL && onlySSL) && !(forceSSL && onlySSL) && !(addSSL && forceSSL)) vhosts;
+        message = ''
+          Options `services.httpd.virtualHosts.<name>.addSSL`,
+          `services.httpd.virtualHosts.<name>.onlySSL` and `services.httpd.virtualHosts.<name>.forceSSL`
+          are mutually exclusive.
+        '';
+      }
+      {
+        assertion = all (hostOpts: !(hostOpts.enableACME && hostOpts.useACMEHost != null)) vhosts;
+        message = ''
+          Options `services.httpd.virtualHosts.<name>.enableACME` and
+          `services.httpd.virtualHosts.<name>.useACMEHost` are mutually exclusive.
+        '';
+      }
+    ];
+
+    warnings =
+      mapAttrsToList (name: hostOpts: ''
+        Using config.services.httpd.virtualHosts."${name}".servedFiles is deprecated and will become unsupported in a future release. Your configuration will continue to work as is but please migrate your configuration to config.services.httpd.virtualHosts."${name}".locations before the 20.09 release of NixOS.
+      '') (filterAttrs (name: hostOpts: hostOpts.servedFiles != []) cfg.virtualHosts);
+
+    users.users = optionalAttrs (cfg.user == "wwwrun") {
+      wwwrun = {
+        group = cfg.group;
+        description = "Apache httpd user";
+        uid = config.ids.uids.wwwrun;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "wwwrun") {
+      wwwrun.gid = config.ids.gids.wwwrun;
+    };
+
+    security.acme.certs = mapAttrs (name: hostOpts: {
+      user = cfg.user;
+      group = mkDefault cfg.group;
+      email = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr;
+      webroot = hostOpts.acmeRoot;
+      extraDomains = genAttrs hostOpts.serverAliases (alias: null);
+      postRun = "systemctl reload httpd.service";
+    }) (filterAttrs (name: hostOpts: hostOpts.enableACME) cfg.virtualHosts);
+
+    environment.systemPackages = [ pkg ];
+
+    # required for "apachectl configtest"
+    environment.etc."httpd/httpd.conf".source = httpdConf;
+
+    services.httpd.phpOptions =
+      ''
+        ; Needed for PHP's mail() function.
+        sendmail_path = ${pkgs.system-sendmail}/bin/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}"
+      '';
+
+    services.httpd.extraModules = mkBefore [
+      # 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"
+
+      # Authorization: is the user allowed access?
+      "authz_user" "authz_groupfile" "authz_host"
+
+      # Other modules.
+      "ext_filter" "include" "env" "mime_magic"
+      "cern_meta" "expires" "headers" "usertrack" "setenvif"
+      "dav" "status" "asis" "info" "dav_fs"
+      "vhost_alias" "imagemap" "actions" "speling"
+      "proxy" "proxy_http"
+      "cache" "cache_disk"
+
+      # For compatibility with old configurations, the new module mod_access_compat is provided.
+      "access_compat"
+    ];
+
+    systemd.tmpfiles.rules =
+      let
+        svc = config.systemd.services.httpd.serviceConfig;
+      in
+        [
+          "d '${cfg.logDir}' 0700 ${svc.User} ${svc.Group}"
+          "Z '${cfg.logDir}' - ${svc.User} ${svc.Group}"
+        ];
+
+    systemd.services.httpd =
+      let
+        vhostsACME = filter (hostOpts: hostOpts.enableACME) vhosts;
+      in
+      { description = "Apache HTTPD";
+
+        wantedBy = [ "multi-user.target" ];
+        wants = concatLists (map (hostOpts: [ "acme-${hostOpts.hostName}.service" "acme-selfsigned-${hostOpts.hostName}.service" ]) vhostsACME);
+        after = [ "network.target" "fs.target" ] ++ map (hostOpts: "acme-selfsigned-${hostOpts.hostName}.service") vhostsACME;
+
+        path = [ pkg pkgs.coreutils pkgs.gnugrep ];
+
+        environment =
+          optionalAttrs cfg.enablePHP { PHPRC = phpIni; }
+          // optionalAttrs cfg.enableMellon { LD_LIBRARY_PATH  = "${pkgs.xmlsec}/lib"; };
+
+        preStart =
+          ''
+            # 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 ' ${cfg.user} ' | cut -f2 -d ' '); do
+                ${pkgs.utillinux}/bin/ipcrm -s $i
+            done
+          '';
+
+        serviceConfig = {
+          ExecStart = "@${pkg}/bin/httpd httpd -f ${httpdConf}";
+          ExecStop = "${pkg}/bin/httpd -f ${httpdConf} -k graceful-stop";
+          ExecReload = "${pkg}/bin/httpd -f ${httpdConf} -k graceful";
+          User = cfg.user;
+          Group = cfg.group;
+          Type = "forking";
+          PIDFile = "${runtimeDir}/httpd.pid";
+          Restart = "always";
+          RestartSec = "5s";
+          RuntimeDirectory = "httpd httpd/runtime";
+          RuntimeDirectoryMode = "0750";
+          AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
+        };
+      };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/location-options.nix b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/location-options.nix
new file mode 100644
index 000000000000..8ea88f94f973
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/location-options.nix
@@ -0,0 +1,54 @@
+{ config, lib, name, ... }:
+let
+  inherit (lib) mkOption types;
+in
+{
+  options = {
+
+    proxyPass = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      example = "http://www.example.org/";
+      description = ''
+        Sets up a simple reverse proxy as described by <link xlink:href="https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html#simple" />.
+      '';
+    };
+
+    index = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      example = "index.php index.html";
+      description = ''
+        Adds DirectoryIndex directive. See <link xlink:href="https://httpd.apache.org/docs/2.4/mod/mod_dir.html#directoryindex" />.
+      '';
+    };
+
+    alias = mkOption {
+      type = with types; nullOr path;
+      default = null;
+      example = "/your/alias/directory";
+      description = ''
+        Alias directory for requests. See <link xlink:href="https://httpd.apache.org/docs/2.4/mod/mod_alias.html#alias" />.
+      '';
+    };
+
+    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/apache-httpd/vhost-options.nix b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix
new file mode 100644
index 000000000000..173c0f8561c0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix
@@ -0,0 +1,275 @@
+{ config, lib, name, ... }:
+let
+  inherit (lib) literalExample mkOption nameValuePair types;
+in
+{
+  options = {
+
+    hostName = mkOption {
+      type = types.str;
+      default = name;
+      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 = with types; listOf (submodule ({
+        options = {
+          port = mkOption {
+            type = types.port;
+            description = "Port to listen on";
+          };
+          ip = mkOption {
+            type = types.str;
+            default = "*";
+            description = "IP to listen on. 0.0.0.0 for IPv4 only, * for all.";
+          };
+          ssl = mkOption {
+            type = types.bool;
+            default = false;
+            description = "Whether to enable SSL (https) support.";
+          };
+        };
+      }));
+      default = [];
+      example = [
+        { ip = "195.154.1.1"; port = 443; ssl = true;}
+        { ip = "192.154.1.1"; port = 80; }
+        { ip = "*"; port = 8080; }
+      ];
+      description = ''
+        Listen addresses and ports for this virtual host.
+        <note><para>
+          This option overrides <literal>addSSL</literal>, <literal>forceSSL</literal> and <literal>onlySSL</literal>.
+        </para></note>
+      '';
+    };
+
+    enableSSL = mkOption {
+      type = types.bool;
+      visible = false;
+      default = false;
+    };
+
+    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.
+      '';
+    };
+
+    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.
+      '';
+    };
+
+    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-challenges";
+      description = "Directory for the acme challenge which is PUBLIC, don't put certs or keys in here";
+    };
+
+    sslServerCert = mkOption {
+      type = types.path;
+      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.";
+    };
+
+    http2 = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to enable HTTP 2. HTTP/2 is supported in all multi-processing modules that come with httpd. <emphasis>However, if you use the prefork mpm, there will
+        be severe restrictions.</emphasis> Refer to <link xlink:href="https://httpd.apache.org/docs/2.4/howto/http2.html#mpm-config"/> for details.
+      '';
+    };
+
+    adminAddr = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "admin@example.org";
+      description = "E-mail address of the server administrator.";
+    };
+
+    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.
+
+        <note><para>
+          This option has been deprecated and will be removed in a future
+          version of NixOS. You can achieve the same result by making use of
+          the <literal>locations.&lt;name&gt;.alias</literal> option.
+        </para></note>
+      '';
+    };
+
+    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.
+      '';
+    };
+
+    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.
+      '';
+    };
+
+    locations = mkOption {
+      type = with types; attrsOf (submodule (import ./location-options.nix));
+      default = {};
+      example = literalExample ''
+        {
+          "/" = {
+            proxyPass = "http://localhost:3000";
+          };
+          "/foo/bar.png" = {
+            alias = "/home/eelco/some-file.png";
+          };
+        };
+      '';
+      description = ''
+        Declarative location config. See <link
+        xlink:href="https://httpd.apache.org/docs/2.4/mod/core.html#location"/> for details.
+      '';
+    };
+
+  };
+
+  config = {
+
+    locations = builtins.listToAttrs (map (elem: nameValuePair elem.urlPath { alias = elem.file; }) config.servedFiles);
+
+  };
+}
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..0e6e10a5f47d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/caddy.nix
@@ -0,0 +1,111 @@
+{ 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.str;
+      description = "Certificate authority ACME server. The default (Let's Encrypt production server) should be fine for most people.";
+    };
+
+    email = mkOption {
+      default = "";
+      type = types.str;
+      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";
+      # upstream unit: https://github.com/caddyserver/caddy/blob/master/dist/init/linux-systemd/caddy.service
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ]; # systemd-networkd-wait-online.service
+      wantedBy = [ "multi-user.target" ];
+      environment = mkIf (versionAtLeast config.system.stateVersion "17.09")
+        { CADDYPATH = cfg.dataDir; };
+      serviceConfig = {
+        ExecStart = ''
+          ${cfg.package}/bin/caddy -log stdout -log-timestamps=false \
+            -root=/var/tmp -conf=${configFile} \
+            -ca=${cfg.ca} -email=${cfg.email} ${optionalString cfg.agree "-agree"}
+        '';
+        ExecReload = "${pkgs.coreutils}/bin/kill -USR1 $MAINPID";
+        Type = "simple";
+        User = "caddy";
+        Group = "caddy";
+        Restart = "on-abnormal";
+        StartLimitIntervalSec = 14400;
+        StartLimitBurst = 10;
+        AmbientCapabilities = "cap_net_bind_service";
+        CapabilityBoundingSet = "cap_net_bind_service";
+        NoNewPrivileges = true;
+        LimitNPROC = 512;
+        LimitNOFILE = 1048576;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHome = true;
+        ProtectSystem = "full";
+        ReadWriteDirectories = cfg.dataDir;
+        KillMode = "mixed";
+        KillSignal = "SIGQUIT";
+        TimeoutStopSec = "5s";
+      };
+    };
+
+    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..d6649fd472d9
--- /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 = "${pkgs.darkhttpd}/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..dccc29e4bb0c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix
@@ -0,0 +1,74 @@
+{ 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 {
+        DynamicUser = true;
+      });
+    };
+
+    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..1812f225b74d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/hitch/default.nix
@@ -0,0 +1,111 @@
+{ 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";
+      isSystemUser = true;
+    };
+    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..ca5b8635fc00
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/jboss/default.nix
@@ -0,0 +1,82 @@
+{ 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 {
+        type = types.bool;
+        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 {
+        type = types.bool;
+        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..04d66ffd25c6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix
@@ -0,0 +1,825 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.nginx;
+  certs = config.security.acme.certs;
+  vhostsConfigs = mapAttrsToList (vhostName: vhostConfig: vhostConfig) virtualHosts;
+  acmeEnabledVhosts = filter (vhostConfig: vhostConfig.enableACME && vhostConfig.useACMEHost == null) vhostsConfigs;
+  virtualHosts = mapAttrs (vhostName: vhostConfig:
+    let
+      serverName = if vhostConfig.serverName != null
+        then vhostConfig.serverName
+        else vhostName;
+    in
+    vhostConfig // {
+      inherit serverName;
+    } // (optionalAttrs vhostConfig.enableACME {
+      sslCertificate = "${certs.${serverName}.directory}/fullchain.pem";
+      sslCertificateKey = "${certs.${serverName}.directory}/key.pem";
+      sslTrustedCertificate = "${certs.${serverName}.directory}/full.pem";
+    }) // (optionalAttrs (vhostConfig.useACMEHost != null) {
+      sslCertificate = "${certs.${vhostConfig.useACMEHost}.directory}/fullchain.pem";
+      sslCertificateKey = "${certs.${vhostConfig.useACMEHost}.directory}/key.pem";
+      sslTrustedCertificate = "${certs.${vhostConfig.useACMEHost}.directory}/fullchain.pem";
+    })
+  ) cfg.virtualHosts;
+  enableIPv6 = config.networking.enableIPv6;
+
+  defaultFastcgiParams = {
+    SCRIPT_FILENAME   = "$document_root$fastcgi_script_name";
+    QUERY_STRING      = "$query_string";
+    REQUEST_METHOD    = "$request_method";
+    CONTENT_TYPE      = "$content_type";
+    CONTENT_LENGTH    = "$content_length";
+
+    SCRIPT_NAME       = "$fastcgi_script_name";
+    REQUEST_URI       = "$request_uri";
+    DOCUMENT_URI      = "$document_uri";
+    DOCUMENT_ROOT     = "$document_root";
+    SERVER_PROTOCOL   = "$server_protocol";
+    REQUEST_SCHEME    = "$scheme";
+    HTTPS             = "$https if_not_empty";
+
+    GATEWAY_INTERFACE = "CGI/1.1";
+    SERVER_SOFTWARE   = "nginx/$nginx_version";
+
+    REMOTE_ADDR       = "$remote_addr";
+    REMOTE_PORT       = "$remote_port";
+    SERVER_ADDR       = "$server_addr";
+    SERVER_PORT       = "$server_port";
+    SERVER_NAME       = "$server_name";
+
+    REDIRECT_STATUS   = "200";
+  };
+
+  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}
+    }
+  ''));
+
+  commonHttpConfig = ''
+      # The mime type definitions included with nginx are very incomplete, so
+      # we use a list of mime types from the mailcap package, which is also
+      # used by most other Linux distributions by default.
+      include ${pkgs.mailcap}/etc/nginx/mime.types;
+      include ${cfg.package}/conf/fastcgi.conf;
+      include ${cfg.package}/conf/uwsgi_params;
+  '';
+
+  configFile = pkgs.writers.writeNginxConfig "nginx.conf" ''
+    pid /run/nginx/nginx.pid;
+    error_log ${cfg.logError};
+    daemon off;
+
+    ${cfg.config}
+
+    ${optionalString (cfg.eventsConfig != "" || cfg.config == "") ''
+    events {
+      ${cfg.eventsConfig}
+    }
+    ''}
+
+    ${optionalString (cfg.httpConfig == "" && cfg.config == "") ''
+    http {
+      ${commonHttpConfig}
+
+      ${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 4096;
+      ''}
+
+      ssl_protocols ${cfg.sslProtocols};
+      ssl_ciphers ${cfg.sslCiphers};
+      ${optionalString (cfg.sslDhparam != null) "ssl_dhparam ${cfg.sslDhparam};"}
+
+      ${optionalString (cfg.recommendedTlsSettings) ''
+        # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate
+
+        ssl_session_timeout 1d;
+        ssl_session_cache shared:SSL:10m;
+        # Breaks forward secrecy: https://github.com/mozilla/server-side-tls/issues/135
+        ssl_session_tickets off;
+        # We don't enable insecure ciphers by default, so this allows
+        # clients to pick the most performant, per https://github.com/mozilla/server-side-tls/issues/260
+        ssl_prefer_server_ciphers off;
+
+        # OCSP stapling
+        ssl_stapling on;
+        ssl_stapling_verify on;
+      ''}
+
+      ${optionalString (cfg.recommendedGzipSettings) ''
+        gzip on;
+        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};
+      ''}
+
+      ${optionalString (cfg.mapHashBucketSize != null) ''
+        map_hash_bucket_size ${toString cfg.mapHashBucketSize};
+      ''}
+
+      ${optionalString (cfg.mapHashMaxSize != null) ''
+        map_hash_max_size ${toString cfg.mapHashMaxSize};
+      ''}
+
+      # $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 {
+      ${commonHttpConfig}
+      ${cfg.httpConfig}
+    }''}
+
+    ${cfg.appendConfig}
+  '';
+
+  configPath = if cfg.enableReload
+    then "/etc/nginx/nginx.conf"
+    else configFile;
+
+  execCommand = "${cfg.package}/bin/nginx -c '${configPath}'";
+
+  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;
+      ''}
+      ${concatStringsSep "\n"
+        (mapAttrsToList (n: v: ''fastcgi_param ${n} "${v}";'')
+          (optionalAttrs (config.fastcgiParams != {})
+            (defaultFastcgiParams // config.fastcgiParams)))}
+      ${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 = "";
+        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>.
+        '';
+      };
+
+      enableSandbox = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Starting Nginx web server with additional sandbox/hardening options.
+        '';
+      };
+
+      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.str;
+        default = "10m";
+        description = "Set nginx global client_max_body_size.";
+      };
+
+      sslCiphers = mkOption {
+        type = types.str;
+        # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate
+        default = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
+        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.
+        '';
+      };
+
+      mapHashBucketSize = mkOption {
+        type = types.nullOr (types.enum [ 32 64 128 ]);
+        default = null;
+        description = ''
+            Sets the bucket size for the map variables hash tables. Default
+            value depends on the processor’s cache line size.
+          '';
+      };
+
+      mapHashMaxSize = mkOption {
+        type = types.nullOr types.ints.positive;
+        default = null;
+        description = ''
+            Sets the maximum size of the map variables hash tables.
+          '';
+      };
+
+      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";
+      };
+    };
+  };
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "nginx" "stateDir" ] ''
+      The Nginx log directory has been moved to /var/log/nginx, the cache directory
+      to /var/cache/nginx. The option services.nginx.stateDir has been removed.
+    '')
+  ];
+
+  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";
+      wantedBy = [ "multi-user.target" ];
+      wants = concatLists (map (vhostConfig: ["acme-${vhostConfig.serverName}.service" "acme-selfsigned-${vhostConfig.serverName}.service"]) acmeEnabledVhosts);
+      after = [ "network.target" ] ++ map (vhostConfig: "acme-selfsigned-${vhostConfig.serverName}.service") acmeEnabledVhosts;
+      stopIfChanged = false;
+      preStart = ''
+        ${cfg.preStart}
+        ${execCommand} -t
+      '';
+      serviceConfig = {
+        ExecStart = execCommand;
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        Restart = "always";
+        RestartSec = "10s";
+        StartLimitInterval = "1min";
+        # User and group
+        User = cfg.user;
+        Group = cfg.group;
+        # Runtime directory and mode
+        RuntimeDirectory = "nginx";
+        RuntimeDirectoryMode = "0750";
+        # Cache directory and mode
+        CacheDirectory = "nginx";
+        CacheDirectoryMode = "0750";
+        # Logs directory and mode
+        LogsDirectory = "nginx";
+        LogsDirectoryMode = "0750";
+        # Capabilities
+        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ];
+        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ];
+        # Security
+        NoNewPrivileges = true;
+      } // optionalAttrs cfg.enableSandbox {
+        # Sandboxing
+        ProtectSystem = "strict";
+        ProtectHome = mkDefault true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHostname = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        LockPersonality = true;
+        MemoryDenyWriteExecute = !(builtins.any (mod: (mod.allowMemoryWriteExecute or false)) pkgs.nginx.modules);
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        PrivateMounts = true;
+        # System Call Filtering
+        SystemCallArchitectures = "native";
+      };
+    };
+
+    environment.etc."nginx/nginx.conf" = mkIf cfg.enableReload {
+      source = configFile;
+    };
+
+    systemd.services.nginx-config-reload = mkIf cfg.enableReload {
+      wants = [ "nginx.service" ];
+      wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ configFile ];
+      # commented, because can cause extra delays during activate for this config:
+      #      services.nginx.virtualHosts."_".locations."/".proxyPass = "http://blabla:3000";
+      # stopIfChanged = false;
+      serviceConfig.Type = "oneshot";
+      serviceConfig.TimeoutSec = 60;
+      script = ''
+        if /run/current-system/systemd/bin/systemctl -q is-active nginx.service ; then
+          ${execCommand} -t && \
+            /run/current-system/systemd/bin/systemctl reload nginx.service
+        fi
+      '';
+      serviceConfig.RemainAfterExit = true;
+    };
+
+    security.acme.certs = filterAttrs (n: v: v != {}) (
+      let
+        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 = ''
+              /run/current-system/systemd/bin/systemctl reload nginx
+            '';
+          }; }) acmeEnabledVhosts;
+      in
+        listToAttrs acmePairs
+    );
+
+    users.users = optionalAttrs (cfg.user == "nginx") {
+      nginx = {
+        group = cfg.group;
+        uid = config.ids.uids.nginx;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "nginx") {
+      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..f7fb07bb7975
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix
@@ -0,0 +1,94 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.nginx.gitweb;
+  gitwebConfig = config.services.gitweb;
+  package = pkgs.gitweb.override (optionalAttrs gitwebConfig.gitwebTheme {
+    gitwebTheme = true;
+  });
+
+in
+{
+
+  options.services.nginx.gitweb = {
+
+    enable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        If true, enable gitweb in nginx.
+      '';
+    };
+
+    location = mkOption {
+      default = "/gitweb";
+      type = types.str;
+      description = ''
+        Location to serve gitweb on.
+      '';
+    };
+
+    user = mkOption {
+      default = "nginx";
+      type = types.str;
+      description = ''
+        Existing user that the CGI process will belong to. (Default almost surely will do.)
+      '';
+    };
+
+    group = mkOption {
+      default = "nginx";
+      type = types.str;
+      description = ''
+        Group that the CGI process will belong to. (Set to <literal>config.services.gitolite.group</literal> if you are using gitolite.)
+      '';
+    };
+
+    virtualHost = mkOption {
+      default = "_";
+      type = types.str;
+      description = ''
+        VirtualHost to serve gitweb on. Default is catch-all.
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.gitweb = {
+      description = "GitWeb service";
+      script = "${package}/gitweb.cgi --fastcgi --nproc=1";
+      environment  = {
+        FCGI_SOCKET_PATH = "/run/gitweb/gitweb.sock";
+      };
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        RuntimeDirectory = [ "gitweb" ];
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    services.nginx = {
+      virtualHosts.${cfg.virtualHost} = {
+        locations."${cfg.location}/static/" = {
+          alias = "${package}/static/";
+        };
+        locations."${cfg.location}/" = {
+          extraConfig = ''
+            include ${pkgs.nginx}/conf/fastcgi_params;
+            fastcgi_param GITWEB_CONFIG ${gitwebConfig.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..a85bacb9e4e2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix
@@ -0,0 +1,104 @@
+# This file defines the options that can be used both for the Nginx
+# 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.
+      '';
+    };
+
+    fastcgiParams = mkOption {
+      type = types.attrsOf types.str;
+      default = {};
+      description = ''
+        FastCGI parameters to override.  Unlike in the Nginx
+        configuration file, overriding only some default parameters
+        won't unset the default values for other parameters.
+      '';
+    };
+
+    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..455854e2a965
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix
@@ -0,0 +1,229 @@
+# This file defines the options that can be used both for the Nginx
+# 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.
+        Can be created via: <command>htpasswd -c &lt;filename&gt; &lt;username&gt;</command>
+      '';
+    };
+
+    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..d090885a8ca5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix
@@ -0,0 +1,284 @@
+{ 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 ${poolOpts.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>
+          '';
+          example = "${runtimeDir}/<name>.sock";
+        };
+
+        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;
+          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;
+        phpOptions = mkBefore cfg.phpOptions;
+
+        settings = mapAttrs (name: mkDefault){
+          listen = poolOpts.socket;
+          user = poolOpts.user;
+          group = poolOpts.group;
+        };
+      };
+    };
+
+in {
+  imports = [
+    (mkRemovedOptionModule [ "services" "phpfpm" "poolConfigs" ] "Use services.phpfpm.pools instead.")
+    (mkRemovedOptionModule [ "services" "phpfpm" "phpIni" ] "")
+  ];
+
+  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;
+          PrivateTmp = 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..6d12925829f7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/tomcat.nix
@@ -0,0 +1,421 @@
+{ 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.tomcat.gid = config.ids.gids.tomcat;
+
+    users.users.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..4ab7307c3b67
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/traefik.nix
@@ -0,0 +1,170 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.traefik;
+  jsonValue = with types;
+    let
+      valueType = nullOr (oneOf [
+        bool
+        int
+        float
+        str
+        (lazyAttrsOf valueType)
+        (listOf valueType)
+      ]) // {
+        description = "JSON value";
+        emptyValue.value = { };
+      };
+    in valueType;
+  dynamicConfigFile = if cfg.dynamicConfigFile == null then
+    pkgs.runCommand "config.toml" {
+      buildInputs = [ pkgs.remarshal ];
+      preferLocalBuild = true;
+    } ''
+      remarshal -if json -of toml \
+        < ${
+          pkgs.writeText "dynamic_config.json"
+          (builtins.toJSON cfg.dynamicConfigOptions)
+        } \
+        > $out
+    ''
+  else
+    cfg.dynamicConfigFile;
+  staticConfigFile = if cfg.staticConfigFile == null then
+    pkgs.runCommand "config.toml" {
+      buildInputs = [ pkgs.yj ];
+      preferLocalBuild = true;
+    } ''
+      yj -jt -i \
+        < ${
+          pkgs.writeText "static_config.json" (builtins.toJSON
+            (recursiveUpdate cfg.staticConfigOptions {
+              providers.file.filename = "${dynamicConfigFile}";
+            }))
+        } \
+        > $out
+    ''
+  else
+    cfg.staticConfigFile;
+in {
+  options.services.traefik = {
+    enable = mkEnableOption "Traefik web server";
+
+    staticConfigFile = mkOption {
+      default = null;
+      example = literalExample "/path/to/static_config.toml";
+      type = types.nullOr types.path;
+      description = ''
+        Path to traefik's static configuration to use.
+        (Using that option has precedence over <literal>staticConfigOptions</literal> and <literal>dynamicConfigOptions</literal>)
+      '';
+    };
+
+    staticConfigOptions = mkOption {
+      description = ''
+        Static configuration for Traefik.
+      '';
+      type = jsonValue;
+      default = { entryPoints.http.address = ":80"; };
+      example = {
+        entryPoints.web.address = ":8080";
+        entryPoints.http.address = ":80";
+
+        api = { };
+      };
+    };
+
+    dynamicConfigFile = mkOption {
+      default = null;
+      example = literalExample "/path/to/dynamic_config.toml";
+      type = types.nullOr types.path;
+      description = ''
+        Path to traefik's dynamic configuration to use.
+        (Using that option has precedence over <literal>dynamicConfigOptions</literal>)
+      '';
+    };
+
+    dynamicConfigOptions = mkOption {
+      description = ''
+        Dynamic configuration for Traefik.
+      '';
+      type = jsonValue;
+      default = { };
+      example = {
+        http.routers.router1 = {
+          rule = "Host(`localhost`)";
+          service = "service1";
+        };
+
+        http.services.service1.loadBalancer.servers =
+          [{ url = "http://localhost:8080"; }];
+      };
+    };
+
+    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.str;
+      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/traefik --configfile=${staticConfigFile}";
+        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;
+      isSystemUser = true;
+    };
+
+    users.groups.traefik = { };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/ttyd.nix b/nixpkgs/nixos/modules/services/web-servers/ttyd.nix
new file mode 100644
index 000000000000..01a01d97a234
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/ttyd.nix
@@ -0,0 +1,196 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.ttyd;
+
+  # Command line arguments for the ttyd daemon
+  args = [ "--port" (toString cfg.port) ]
+         ++ optionals (cfg.socket != null) [ "--interface" cfg.socket ]
+         ++ optionals (cfg.interface != null) [ "--interface" cfg.interface ]
+         ++ [ "--signal" (toString cfg.signal) ]
+         ++ (concatLists (mapAttrsToList (_k: _v: [ "--client-option" "${_k}=${_v}" ]) cfg.clientOptions))
+         ++ [ "--terminal-type" cfg.terminalType ]
+         ++ optionals cfg.checkOrigin [ "--check-origin" ]
+         ++ [ "--max-clients" (toString cfg.maxClients) ]
+         ++ optionals (cfg.indexFile != null) [ "--index" cfg.indexFile ]
+         ++ optionals cfg.enableIPv6 [ "--ipv6" ]
+         ++ optionals cfg.enableSSL [ "--ssl-cert" cfg.certFile
+                                      "--ssl-key" cfg.keyFile
+                                      "--ssl-ca" cfg.caFile ]
+         ++ [ "--debug" (toString cfg.logLevel) ];
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.ttyd = {
+      enable = mkEnableOption "ttyd daemon";
+
+      port = mkOption {
+        type = types.int;
+        default = 7681;
+        description = "Port to listen on (use 0 for random port)";
+      };
+
+      socket = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/run/ttyd.sock";
+        description = "UNIX domain socket path to bind.";
+      };
+
+      interface = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "eth0";
+        description = "Network interface to bind.";
+      };
+
+      username = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Username for basic authentication.";
+      };
+
+      passwordFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        apply = value: if value == null then null else toString value;
+        description = ''
+          File containing the password to use for basic authentication.
+          For insecurely putting the password in the globally readable store use
+          <literal>pkgs.writeText "ttydpw" "MyPassword"</literal>.
+        '';
+      };
+
+      signal = mkOption {
+        type = types.ints.u8;
+        default = 1;
+        description = "Signal to send to the command on session close.";
+      };
+
+      clientOptions = mkOption {
+        type = types.attrsOf types.str;
+        default = {};
+        example = literalExample ''{
+          fontSize = "16";
+          fontFamily = "Fira Code";
+
+        }'';
+        description = ''
+          Attribute set of client options for xtermjs.
+          <link xlink:href="https://xtermjs.org/docs/api/terminal/interfaces/iterminaloptions/"/>
+        '';
+      };
+
+      terminalType = mkOption {
+        type = types.str;
+        default = "xterm-256color";
+        description = "Terminal type to report.";
+      };
+
+      checkOrigin = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to allow a websocket connection from a different origin.";
+      };
+
+      maxClients = mkOption {
+        type = types.int;
+        default = 0;
+        description = "Maximum clients to support (0, no limit)";
+      };
+
+      indexFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "Custom index.html path";
+      };
+
+      enableIPv6 = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether or not to enable IPv6 support.";
+      };
+
+      enableSSL = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether or not to enable SSL (https) support.";
+      };
+
+      certFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "SSL certificate file path.";
+      };
+
+      keyFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        apply = value: if value == null then null else toString value;
+        description = ''
+          SSL key file path.
+          For insecurely putting the keyFile in the globally readable store use
+          <literal>pkgs.writeText "ttydKeyFile" "SSLKEY"</literal>.
+        '';
+      };
+
+      caFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "SSL CA file path for client certificate verification.";
+      };
+
+      logLevel = mkOption {
+        type = types.int;
+        default = 7;
+        description = "Set log level.";
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions =
+      [ { assertion = cfg.enableSSL
+            -> cfg.certFile != null && cfg.keyFile != null && cfg.caFile != null;
+          message = "SSL is enabled for ttyd, but no certFile, keyFile or caFile has been specefied."; }
+        { assertion = ! (cfg.interface != null && cfg.socket != null);
+          message = "Cannot set both interface and socket for ttyd."; }
+        { assertion = (cfg.username != null) == (cfg.passwordFile != null);
+          message = "Need to set both username and passwordFile for ttyd"; }
+      ];
+
+    systemd.services.ttyd = {
+      description = "ttyd Web Server Daemon";
+
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        # Runs login which needs to be run as root
+        # login: Cannot possibly work without effective root
+        User = "root";
+      };
+
+      script = if cfg.passwordFile != null then ''
+        PASSWORD=$(cat ${escapeShellArg cfg.passwordFile})
+        ${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \
+          --credential ${escapeShellArg cfg.username}:"$PASSWORD" \
+          ${pkgs.shadow}/bin/login
+      ''
+      else ''
+        ${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \
+          ${pkgs.shadow}/bin/login
+      '';
+    };
+  };
+}
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..989866144e1e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/unit/default.nix
@@ -0,0 +1,150 @@
+{ 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" ];
+      preStart = ''
+        [ ! -e '${cfg.stateDir}/conf.json' ] || rm -f '${cfg.stateDir}/conf.json'
+      '';
+      postStart = ''
+        ${pkgs.curl}/bin/curl -X PUT --data-binary '@${configFile}' --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config'
+      '';
+      serviceConfig = {
+        Type = "forking";
+        PIDFile = "/run/unit/unit.pid";
+        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}' \
+                                   --user ${cfg.user} --group ${cfg.group}
+        '';
+        ExecStop = ''
+          ${pkgs.curl}/bin/curl -X DELETE --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config'
+        '';
+        # Runtime directory and mode
+        RuntimeDirectory = "unit";
+        RuntimeDirectoryMode = "0750";
+        # Access write directories
+        ReadWritePaths = [ cfg.stateDir cfg.logDir ];
+        # Security
+        NoNewPrivileges = true;
+        # Sandboxing
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHostname = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        PrivateMounts = true;
+        # System Call Filtering
+        SystemCallArchitectures = "native";
+      };
+    };
+
+    users.users = optionalAttrs (cfg.user == "unit") {
+      unit = {
+        group = cfg.group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "unit") {
+      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..7ac40c154730
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix
@@ -0,0 +1,190 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.uwsgi;
+
+  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 cfg.package.python2
+        else if hasPython3 then cfg.package.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) {
+                pyhome = "${pythonEnv}";
+                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.path;
+        default = "/run/uwsgi";
+        description = "Where uWSGI communication sockets can live";
+      };
+
+      package = mkOption {
+        type = types.package;
+        internal = true;
+      };
+
+      instance = mkOption {
+        type =  with lib.types; let
+          valueType = nullOr (oneOf [
+            bool
+            int
+            float
+            str
+            (lazyAttrsOf valueType)
+            (listOf valueType)
+            (mkOptionType {
+              name = "function";
+              description = "function";
+              check = x: isFunction x;
+              merge = mergeOneOption;
+            })
+          ]) // {
+            description = "Json value or lambda";
+            emptyValue.value = {};
+          };
+        in valueType;
+        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.";
+      };
+
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "--chmod-socket=664" ];
+        description = "Extra command line arguments for uwsgi.";
+      };
+    };
+  };
+
+  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 = "${cfg.package}/bin/uwsgi --uid ${cfg.user} --gid ${cfg.group} ${escapeShellArgs cfg.extraArgs} --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") {
+      uwsgi = {
+        group = cfg.group;
+        uid = config.ids.uids.uwsgi;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "uwsgi") {
+      uwsgi.gid = config.ids.gids.uwsgi;
+    };
+
+    services.uwsgi.package = pkgs.uwsgi.override {
+      inherit (cfg) plugins;
+    };
+  };
+}
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..01fe3d12917a
--- /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.varnish;
+        defaultText = "pkgs.varnish";
+        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.varnishPackages.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..3abd506827c0
--- /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.str;
+        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.str;
+        description = "Give a port and address for the HTTP server.";
+      };
+
+      user = mkOption {
+        default = "zope2";
+        type = types.str;
+        description = "The name of the effective user for the Zope process.";
+      };
+
+      clientHome = mkOption {
+        default = "/var/lib/zope2/${name}";
+        type = types.path;
+        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.lines;
+        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));
+
+  };
+
+}