toxic: 0.15.1 -> 0.16.0
[NixPkgs.git] / nixos / modules / services / web-servers / apache-httpd / default.nix
blob1ac86c1a5c1df98bb2750cfc4b31029d78794a44
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
7   cfg = config.services.httpd;
9   certs = config.security.acme.certs;
11   runtimeDir = "/run/httpd";
13   pkg = cfg.package.out;
15   apachectl = pkgs.runCommand "apachectl" { meta.priority = -1; } ''
16     mkdir -p $out/bin
17     cp ${pkg}/bin/apachectl $out/bin/apachectl
18     sed -i $out/bin/apachectl -e 's|$HTTPD -t|$HTTPD -t -f /etc/httpd/httpd.conf|'
19   '';
21   php = cfg.phpPackage.override { apxs2Support = true; apacheHttpd = pkg; };
23   phpModuleName = let
24     majorVersion = lib.versions.major (lib.getVersion php);
25   in (if majorVersion == "8" then "php" else "php${majorVersion}");
27   mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = pkg; };
29   vhosts = attrValues cfg.virtualHosts;
31   # certName is used later on to determine systemd service names.
32   acmeEnabledVhosts = map (hostOpts: hostOpts // {
33     certName = if hostOpts.useACMEHost != null then hostOpts.useACMEHost else hostOpts.hostName;
34   }) (filter (hostOpts: hostOpts.enableACME || hostOpts.useACMEHost != null) vhosts);
36   vhostCertNames = unique (map (hostOpts: hostOpts.certName) acmeEnabledVhosts);
37   dependentCertNames = filter (cert: certs.${cert}.dnsProvider == null) vhostCertNames; # those that might depend on the HTTP server
38   independentCertNames = filter (cert: certs.${cert}.dnsProvider != null) vhostCertNames; # those that don't depend on the HTTP server
40   mkListenInfo = hostOpts:
41     if hostOpts.listen != [] then
42       hostOpts.listen
43     else
44       optionals (hostOpts.onlySSL || hostOpts.addSSL || hostOpts.forceSSL) (map (addr: { ip = addr; port = 443; ssl = true; }) hostOpts.listenAddresses) ++
45       optionals (!hostOpts.onlySSL) (map (addr: { ip = addr; port = 80; ssl = false; }) hostOpts.listenAddresses)
46     ;
48   listenInfo = unique (concatMap mkListenInfo vhosts);
50   enableHttp2 = any (vhost: vhost.http2) vhosts;
51   enableSSL = any (listen: listen.ssl) listenInfo;
52   enableUserDir = any (vhost: vhost.enableUserDir) vhosts;
54   # NOTE: generally speaking order of modules is very important
55   modules =
56     [ # required apache modules our httpd service cannot run without
57       "authn_core" "authz_core"
58       "log_config"
59       "mime" "autoindex" "negotiation" "dir"
60       "alias" "rewrite"
61       "unixd" "slotmem_shm" "socache_shmcb"
62       "mpm_${cfg.mpm}"
63     ]
64     ++ (if cfg.mpm == "prefork" then [ "cgi" ] else [ "cgid" ])
65     ++ optional enableHttp2 "http2"
66     ++ optional enableSSL "ssl"
67     ++ optional enableUserDir "userdir"
68     ++ optional cfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
69     ++ optional cfg.enablePHP { name = phpModuleName; path = "${php}/modules/lib${phpModuleName}.so"; }
70     ++ optional cfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
71     ++ cfg.extraModules;
73   loggingConf = (if cfg.logFormat != "none" then ''
74     ErrorLog ${cfg.logDir}/error.log
76     LogLevel notice
78     LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
79     LogFormat "%h %l %u %t \"%r\" %>s %b" common
80     LogFormat "%{Referer}i -> %U" referer
81     LogFormat "%{User-agent}i" agent
83     CustomLog ${cfg.logDir}/access.log ${cfg.logFormat}
84   '' else ''
85     ErrorLog /dev/null
86   '');
89   browserHacks = ''
90     <IfModule mod_setenvif.c>
91         BrowserMatch "Mozilla/2" nokeepalive
92         BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
93         BrowserMatch "RealPlayer 4\.0" force-response-1.0
94         BrowserMatch "Java/1\.0" force-response-1.0
95         BrowserMatch "JDK/1\.0" force-response-1.0
96         BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
97         BrowserMatch "^WebDrive" redirect-carefully
98         BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
99         BrowserMatch "^gnome-vfs" redirect-carefully
100     </IfModule>
101   '';
104   sslConf = ''
105     <IfModule mod_ssl.c>
106         SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000)
108         Mutex posixsem
110         SSLRandomSeed startup builtin
111         SSLRandomSeed connect builtin
113         SSLProtocol ${cfg.sslProtocols}
114         SSLCipherSuite ${cfg.sslCiphers}
115         SSLHonorCipherOrder on
116     </IfModule>
117   '';
120   mimeConf = ''
121     TypesConfig ${pkg}/conf/mime.types
123     AddType application/x-x509-ca-cert .crt
124     AddType application/x-pkcs7-crl    .crl
125     AddType application/x-httpd-php    .php .phtml
127     <IfModule mod_mime_magic.c>
128         MIMEMagicFile ${pkg}/conf/magic
129     </IfModule>
130   '';
132   luaSetPaths = let
133     # support both lua and lua.withPackages derivations
134     luaversion = cfg.package.lua5.lua.luaversion or cfg.package.lua5.luaversion;
135     in
136   ''
137     <IfModule mod_lua.c>
138       LuaPackageCPath ${cfg.package.lua5}/lib/lua/${luaversion}/?.so
139       LuaPackagePath  ${cfg.package.lua5}/share/lua/${luaversion}/?.lua
140     </IfModule>
141   '';
143   mkVHostConf = hostOpts:
144     let
145       adminAddr = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr;
146       listen = filter (listen: !listen.ssl) (mkListenInfo hostOpts);
147       listenSSL = filter (listen: listen.ssl) (mkListenInfo hostOpts);
149       useACME = hostOpts.enableACME || hostOpts.useACMEHost != null;
150       sslCertDir =
151         if hostOpts.enableACME then certs.${hostOpts.hostName}.directory
152         else if hostOpts.useACMEHost != null then certs.${hostOpts.useACMEHost}.directory
153         else abort "This case should never happen.";
155       sslServerCert = if useACME then "${sslCertDir}/fullchain.pem" else hostOpts.sslServerCert;
156       sslServerKey = if useACME then "${sslCertDir}/key.pem" else hostOpts.sslServerKey;
157       sslServerChain = if useACME then "${sslCertDir}/chain.pem" else hostOpts.sslServerChain;
159       acmeChallenge = optionalString (useACME && hostOpts.acmeRoot != null) ''
160         Alias /.well-known/acme-challenge/ "${hostOpts.acmeRoot}/.well-known/acme-challenge/"
161         <Directory "${hostOpts.acmeRoot}">
162             AllowOverride None
163             Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
164             Require method GET POST OPTIONS
165             Require all granted
166         </Directory>
167       '';
168     in
169       optionalString (listen != []) ''
170         <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listen}>
171             ServerName ${hostOpts.hostName}
172             ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases}
173             ${optionalString (adminAddr != null) "ServerAdmin ${adminAddr}"}
174             <IfModule mod_ssl.c>
175                 SSLEngine off
176             </IfModule>
177             ${acmeChallenge}
178             ${if hostOpts.forceSSL then ''
179               <IfModule mod_rewrite.c>
180                   RewriteEngine on
181                   RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge [NC]
182                   RewriteCond %{HTTPS} off
183                   RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
184               </IfModule>
185             '' else mkVHostCommonConf hostOpts}
186         </VirtualHost>
187       '' +
188       optionalString (listenSSL != []) ''
189         <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listenSSL}>
190             ServerName ${hostOpts.hostName}
191             ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases}
192             ${optionalString (adminAddr != null) "ServerAdmin ${adminAddr}"}
193             SSLEngine on
194             SSLCertificateFile ${sslServerCert}
195             SSLCertificateKeyFile ${sslServerKey}
196             ${optionalString (sslServerChain != null) "SSLCertificateChainFile ${sslServerChain}"}
197             ${optionalString hostOpts.http2 "Protocols h2 h2c http/1.1"}
198             ${acmeChallenge}
199             ${mkVHostCommonConf hostOpts}
200         </VirtualHost>
201       ''
202   ;
204   mkVHostCommonConf = hostOpts:
205     let
206       documentRoot = if hostOpts.documentRoot != null
207         then hostOpts.documentRoot
208         else pkgs.emptyDirectory
209       ;
211       mkLocations = locations: concatStringsSep "\n" (map (config: ''
212         <Location ${config.location}>
213           ${optionalString (config.proxyPass != null) ''
214             <IfModule mod_proxy.c>
215                 ProxyPass ${config.proxyPass}
216                 ProxyPassReverse ${config.proxyPass}
217             </IfModule>
218           ''}
219           ${optionalString (config.index != null) ''
220             <IfModule mod_dir.c>
221                 DirectoryIndex ${config.index}
222             </IfModule>
223           ''}
224           ${optionalString (config.alias != null) ''
225             <IfModule mod_alias.c>
226                 Alias "${config.alias}"
227             </IfModule>
228           ''}
229           ${config.extraConfig}
230         </Location>
231       '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)));
232     in
233       ''
234         ${optionalString cfg.logPerVirtualHost ''
235           ErrorLog ${cfg.logDir}/error-${hostOpts.hostName}.log
236           CustomLog ${cfg.logDir}/access-${hostOpts.hostName}.log ${hostOpts.logFormat}
237         ''}
239         ${optionalString (hostOpts.robotsEntries != "") ''
240           Alias /robots.txt ${pkgs.writeText "robots.txt" hostOpts.robotsEntries}
241         ''}
243         DocumentRoot "${documentRoot}"
245         <Directory "${documentRoot}">
246             Options Indexes FollowSymLinks
247             AllowOverride None
248             Require all granted
249         </Directory>
251         ${optionalString hostOpts.enableUserDir ''
252           UserDir public_html
253           UserDir disabled root
254           <Directory "/home/*/public_html">
255               AllowOverride FileInfo AuthConfig Limit Indexes
256               Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
257               <Limit GET POST OPTIONS>
258                   Require all granted
259               </Limit>
260               <LimitExcept GET POST OPTIONS>
261                   Require all denied
262               </LimitExcept>
263           </Directory>
264         ''}
266         ${optionalString (hostOpts.globalRedirect != null && hostOpts.globalRedirect != "") ''
267           RedirectPermanent / ${hostOpts.globalRedirect}
268         ''}
270         ${
271           let makeDirConf = elem: ''
272                 Alias ${elem.urlPath} ${elem.dir}/
273                 <Directory ${elem.dir}>
274                     Options +Indexes
275                     Require all granted
276                     AllowOverride All
277                 </Directory>
278               '';
279           in concatMapStrings makeDirConf hostOpts.servedDirs
280         }
282         ${mkLocations hostOpts.locations}
283         ${hostOpts.extraConfig}
284       ''
285   ;
288   confFile = pkgs.writeText "httpd.conf" ''
290     ServerRoot ${pkg}
291     ServerName ${config.networking.hostName}
292     DefaultRuntimeDir ${runtimeDir}/runtime
294     PidFile ${runtimeDir}/httpd.pid
296     ${optionalString (cfg.mpm != "prefork") ''
297       # mod_cgid requires this.
298       ScriptSock ${runtimeDir}/cgisock
299     ''}
301     <IfModule prefork.c>
302         MaxClients           ${toString cfg.maxClients}
303         MaxRequestsPerChild  ${toString cfg.maxRequestsPerChild}
304     </IfModule>
306     ${let
307         toStr = listen: "Listen ${listen.ip}:${toString listen.port} ${if listen.ssl then "https" else "http"}";
308         uniqueListen = uniqList {inputList = map toStr listenInfo;};
309       in concatStringsSep "\n" uniqueListen
310     }
312     User ${cfg.user}
313     Group ${cfg.group}
315     ${let
316         mkModule = module:
317           if isString module then { name = module; path = "${pkg}/modules/mod_${module}.so"; }
318           else if isAttrs module then { inherit (module) name path; }
319           else throw "Expecting either a string or attribute set including a name and path.";
320       in
321         concatMapStringsSep "\n" (module: "LoadModule ${module.name}_module ${module.path}") (unique (map mkModule modules))
322     }
324     AddHandler type-map var
326     <Files ~ "^\.ht">
327         Require all denied
328     </Files>
330     ${mimeConf}
331     ${loggingConf}
332     ${browserHacks}
334     Include ${pkg}/conf/extra/httpd-default.conf
335     Include ${pkg}/conf/extra/httpd-autoindex.conf
336     Include ${pkg}/conf/extra/httpd-multilang-errordoc.conf
337     Include ${pkg}/conf/extra/httpd-languages.conf
339     TraceEnable off
341     ${sslConf}
343     ${optionalString cfg.package.luaSupport luaSetPaths}
345     # Fascist default - deny access to everything.
346     <Directory />
347         Options FollowSymLinks
348         AllowOverride None
349         Require all denied
350     </Directory>
352     # But do allow access to files in the store so that we don't have
353     # to generate <Directory> clauses for every generated file that we
354     # want to serve.
355     <Directory /nix/store>
356         Require all granted
357     </Directory>
359     ${cfg.extraConfig}
361     ${concatMapStringsSep "\n" mkVHostConf vhosts}
362   '';
364   # Generate the PHP configuration file.  Should probably be factored
365   # out into a separate module.
366   phpIni = pkgs.runCommand "php.ini"
367     { options = cfg.phpOptions;
368       preferLocalBuild = true;
369     }
370     ''
371       cat ${php}/etc/php.ini > $out
372       cat ${php.phpIni} > $out
373       echo "$options" >> $out
374     '';
376   mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix lib;
382   imports = [
383     (mkRemovedOptionModule [ "services" "httpd" "extraSubservices" ] "Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.")
384     (mkRemovedOptionModule [ "services" "httpd" "stateDir" ] "The httpd module now uses /run/httpd as a runtime directory.")
385     (mkRenamedOptionModule [ "services" "httpd" "multiProcessingModule" ] [ "services" "httpd" "mpm" ])
387     # virtualHosts options
388     (mkRemovedOptionModule [ "services" "httpd" "documentRoot" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
389     (mkRemovedOptionModule [ "services" "httpd" "enableSSL" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
390     (mkRemovedOptionModule [ "services" "httpd" "enableUserDir" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
391     (mkRemovedOptionModule [ "services" "httpd" "globalRedirect" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
392     (mkRemovedOptionModule [ "services" "httpd" "hostName" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
393     (mkRemovedOptionModule [ "services" "httpd" "listen" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
394     (mkRemovedOptionModule [ "services" "httpd" "robotsEntries" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
395     (mkRemovedOptionModule [ "services" "httpd" "servedDirs" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
396     (mkRemovedOptionModule [ "services" "httpd" "servedFiles" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
397     (mkRemovedOptionModule [ "services" "httpd" "serverAliases" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
398     (mkRemovedOptionModule [ "services" "httpd" "sslServerCert" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
399     (mkRemovedOptionModule [ "services" "httpd" "sslServerChain" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
400     (mkRemovedOptionModule [ "services" "httpd" "sslServerKey" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
401   ];
403   # interface
405   options = {
407     services.httpd = {
409       enable = mkEnableOption "the Apache HTTP Server";
411       package = mkPackageOption pkgs "apacheHttpd" { };
413       configFile = mkOption {
414         type = types.path;
415         default = confFile;
416         defaultText = literalExpression "confFile";
417         example = literalExpression ''pkgs.writeText "httpd.conf" "# my custom config file ..."'';
418         description = ''
419           Override the configuration file used by Apache. By default,
420           NixOS generates one automatically.
421         '';
422       };
424       extraConfig = mkOption {
425         type = types.lines;
426         default = "";
427         description = ''
428           Configuration lines appended to the generated Apache
429           configuration file. Note that this mechanism will not work
430           when {option}`configFile` is overridden.
431         '';
432       };
434       extraModules = mkOption {
435         type = types.listOf types.unspecified;
436         default = [];
437         example = literalExpression ''
438           [
439             "proxy_connect"
440             { name = "jk"; path = "''${pkgs.apacheHttpdPackages.mod_jk}/modules/mod_jk.so"; }
441           ]
442         '';
443         description = ''
444           Additional Apache modules to be used. These can be
445           specified as a string in the case of modules distributed
446           with Apache, or as an attribute set specifying the
447           {var}`name` and {var}`path` of the
448           module.
449         '';
450       };
452       adminAddr = mkOption {
453         type = types.nullOr types.str;
454         example = "admin@example.org";
455         default = null;
456         description = "E-mail address of the server administrator.";
457       };
459       logFormat = mkOption {
460         type = types.str;
461         default = "common";
462         example = "combined";
463         description = ''
464           Log format for log files. Possible values are: combined, common, referer, agent, none.
465           See <https://httpd.apache.org/docs/2.4/logs.html> for more details.
466         '';
467       };
469       logPerVirtualHost = mkOption {
470         type = types.bool;
471         default = true;
472         description = ''
473           If enabled, each virtual host gets its own
474           {file}`access.log` and
475           {file}`error.log`, namely suffixed by the
476           {option}`hostName` of the virtual host.
477         '';
478       };
480       user = mkOption {
481         type = types.str;
482         default = "wwwrun";
483         description = ''
484           User account under which httpd children processes run.
486           If you require the main httpd process to run as
487           `root` add the following configuration:
488           ```
489           systemd.services.httpd.serviceConfig.User = lib.mkForce "root";
490           ```
491         '';
492       };
494       group = mkOption {
495         type = types.str;
496         default = "wwwrun";
497         description = ''
498           Group under which httpd children processes run.
499         '';
500       };
502       logDir = mkOption {
503         type = types.path;
504         default = "/var/log/httpd";
505         description = ''
506           Directory for Apache's log files. It is created automatically.
507         '';
508       };
510       virtualHosts = mkOption {
511         type = with types; attrsOf (submodule (import ./vhost-options.nix));
512         default = {
513           localhost = {
514             documentRoot = "${pkg}/htdocs";
515           };
516         };
517         defaultText = literalExpression ''
518           {
519             localhost = {
520               documentRoot = "''${package.out}/htdocs";
521             };
522           }
523         '';
524         example = literalExpression ''
525           {
526             "foo.example.com" = {
527               forceSSL = true;
528               documentRoot = "/var/www/foo.example.com"
529             };
530             "bar.example.com" = {
531               addSSL = true;
532               documentRoot = "/var/www/bar.example.com";
533             };
534           }
535         '';
536         description = ''
537           Specification of the virtual hosts served by Apache. Each
538           element should be an attribute set specifying the
539           configuration of the virtual host.
540         '';
541       };
543       enableMellon = mkEnableOption "the mod_auth_mellon module";
545       enablePHP = mkEnableOption "the PHP module";
547       phpPackage = mkPackageOption pkgs "php" { };
549       enablePerl = mkEnableOption "the Perl module (mod_perl)";
551       phpOptions = mkOption {
552         type = types.lines;
553         default = "";
554         example =
555           ''
556             date.timezone = "CET"
557           '';
558         description = ''
559           Options appended to the PHP configuration file {file}`php.ini`.
560         '';
561       };
563       mpm = mkOption {
564         type = types.enum [ "event" "prefork" "worker" ];
565         default = "event";
566         example = "worker";
567         description = ''
568             Multi-processing module to be used by Apache. Available
569             modules are `prefork` (handles each
570             request in a separate child process), `worker`
571             (hybrid approach that starts a number of child processes
572             each running a number of threads) and `event`
573             (the default; a recent variant of `worker`
574             that handles persistent connections more efficiently).
575           '';
576       };
578       maxClients = mkOption {
579         type = types.int;
580         default = 150;
581         example = 8;
582         description = "Maximum number of httpd processes (prefork)";
583       };
585       maxRequestsPerChild = mkOption {
586         type = types.int;
587         default = 0;
588         example = 500;
589         description = ''
590           Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited.
591         '';
592       };
594       sslCiphers = mkOption {
595         type = types.str;
596         default = "HIGH:!aNULL:!MD5:!EXP";
597         description = "Cipher Suite available for negotiation in SSL proxy handshake.";
598       };
600       sslProtocols = mkOption {
601         type = types.str;
602         default = "All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1";
603         example = "All -SSLv2 -SSLv3";
604         description = "Allowed SSL/TLS protocol versions.";
605       };
606     };
608   };
610   # implementation
612   config = mkIf cfg.enable {
614     assertions = [
615       {
616         assertion = all (hostOpts: !hostOpts.enableSSL) vhosts;
617         message = ''
618           The option `services.httpd.virtualHosts.<name>.enableSSL` no longer has any effect; please remove it.
619           Select one of `services.httpd.virtualHosts.<name>.addSSL`, `services.httpd.virtualHosts.<name>.forceSSL`,
620           or `services.httpd.virtualHosts.<name>.onlySSL`.
621         '';
622       }
623       {
624         assertion = all (hostOpts: with hostOpts; !(addSSL && onlySSL) && !(forceSSL && onlySSL) && !(addSSL && forceSSL)) vhosts;
625         message = ''
626           Options `services.httpd.virtualHosts.<name>.addSSL`,
627           `services.httpd.virtualHosts.<name>.onlySSL` and `services.httpd.virtualHosts.<name>.forceSSL`
628           are mutually exclusive.
629         '';
630       }
631       {
632         assertion = all (hostOpts: !(hostOpts.enableACME && hostOpts.useACMEHost != null)) vhosts;
633         message = ''
634           Options `services.httpd.virtualHosts.<name>.enableACME` and
635           `services.httpd.virtualHosts.<name>.useACMEHost` are mutually exclusive.
636         '';
637       }
638       {
639         assertion = cfg.enablePHP -> php.ztsSupport;
640         message = ''
641           The php package provided by `services.httpd.phpPackage` is not built with zts support. Please
642           ensure the php has zts support by settings `services.httpd.phpPackage = php.override { ztsSupport = true; }`
643         '';
644       }
645     ] ++ map (name: mkCertOwnershipAssertion {
646       cert = config.security.acme.certs.${name};
647       groups = config.users.groups;
648       services = [ config.systemd.services.httpd ] ++ lib.optional (vhostCertNames != []) config.systemd.services.httpd-config-reload;
649     }) vhostCertNames;
651     warnings =
652       mapAttrsToList (name: hostOpts: ''
653         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.
654       '') (filterAttrs (name: hostOpts: hostOpts.servedFiles != []) cfg.virtualHosts);
656     users.users = optionalAttrs (cfg.user == "wwwrun") {
657       wwwrun = {
658         group = cfg.group;
659         description = "Apache httpd user";
660         uid = config.ids.uids.wwwrun;
661       };
662     };
664     users.groups = optionalAttrs (cfg.group == "wwwrun") {
665       wwwrun.gid = config.ids.gids.wwwrun;
666     };
668     security.acme.certs = let
669       acmePairs = map (hostOpts: let
670         hasRoot = hostOpts.acmeRoot != null;
671       in nameValuePair hostOpts.hostName {
672         group = mkDefault cfg.group;
673         # if acmeRoot is null inherit config.security.acme
674         # Since config.security.acme.certs.<cert>.webroot's own default value
675         # should take precedence set priority higher than mkOptionDefault
676         webroot = mkOverride (if hasRoot then 1000 else 2000) hostOpts.acmeRoot;
677         # Also nudge dnsProvider to null in case it is inherited
678         dnsProvider = mkOverride (if hasRoot then 1000 else 2000) null;
679         extraDomainNames = hostOpts.serverAliases;
680         # Use the vhost-specific email address if provided, otherwise let
681         # security.acme.email or security.acme.certs.<cert>.email be used.
682         email = mkOverride 2000 (if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr);
683       # Filter for enableACME-only vhosts. Don't want to create dud certs
684       }) (filter (hostOpts: hostOpts.useACMEHost == null) acmeEnabledVhosts);
685     in listToAttrs acmePairs;
687     # httpd requires a stable path to the configuration file for reloads
688     environment.etc."httpd/httpd.conf".source = cfg.configFile;
689     environment.systemPackages = [
690       apachectl
691       pkg
692     ];
694     services.logrotate = optionalAttrs (cfg.logFormat != "none") {
695       enable = mkDefault true;
696       settings.httpd = {
697         files = "${cfg.logDir}/*.log";
698         su = "${cfg.user} ${cfg.group}";
699         frequency = "daily";
700         rotate = 28;
701         sharedscripts = true;
702         compress = true;
703         delaycompress = true;
704         postrotate = "systemctl reload httpd.service > /dev/null 2>/dev/null || true";
705       };
706     };
708     services.httpd.phpOptions =
709       ''
710         ; Don't advertise PHP
711         expose_php = off
712       '' + optionalString (config.time.timeZone != null) ''
714         ; Apparently PHP doesn't use $TZ.
715         date.timezone = "${config.time.timeZone}"
716       '';
718     services.httpd.extraModules = mkBefore [
719       # HTTP authentication mechanisms: basic and digest.
720       "auth_basic" "auth_digest"
722       # Authentication: is the user who he claims to be?
723       "authn_file" "authn_dbm" "authn_anon"
725       # Authorization: is the user allowed access?
726       "authz_user" "authz_groupfile" "authz_host"
728       # Other modules.
729       "ext_filter" "include" "env" "mime_magic"
730       "cern_meta" "expires" "headers" "usertrack" "setenvif"
731       "dav" "status" "asis" "info" "dav_fs"
732       "vhost_alias" "imagemap" "actions" "speling"
733       "proxy" "proxy_http"
734       "cache" "cache_disk"
736       # For compatibility with old configurations, the new module mod_access_compat is provided.
737       "access_compat"
738     ];
740     systemd.tmpfiles.rules =
741       let
742         svc = config.systemd.services.httpd.serviceConfig;
743       in
744         [
745           "d '${cfg.logDir}' 0700 ${svc.User} ${svc.Group}"
746           "Z '${cfg.logDir}' - ${svc.User} ${svc.Group}"
747         ];
749     systemd.services.httpd = {
750         description = "Apache HTTPD";
751         wantedBy = [ "multi-user.target" ];
752         wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) vhostCertNames);
753         after = [ "network.target" ]
754           ++ map (certName: "acme-selfsigned-${certName}.service") vhostCertNames
755           ++ map (certName: "acme-${certName}.service") independentCertNames; # avoid loading self-signed key w/ real cert, or vice-versa
756         before = map (certName: "acme-${certName}.service") dependentCertNames;
757         restartTriggers = [ cfg.configFile ];
759         path = [ pkg pkgs.coreutils pkgs.gnugrep ];
761         environment =
762           optionalAttrs cfg.enablePHP { PHPRC = phpIni; }
763           // optionalAttrs cfg.enableMellon { LD_LIBRARY_PATH  = "${pkgs.xmlsec}/lib"; };
765         preStart =
766           ''
767             # Get rid of old semaphores.  These tend to accumulate across
768             # server restarts, eventually preventing it from restarting
769             # successfully.
770             for i in $(${pkgs.util-linux}/bin/ipcs -s | grep ' ${cfg.user} ' | cut -f2 -d ' '); do
771                 ${pkgs.util-linux}/bin/ipcrm -s $i
772             done
773           '';
775         serviceConfig = {
776           ExecStart = "@${pkg}/bin/httpd httpd -f /etc/httpd/httpd.conf";
777           ExecStop = "${pkg}/bin/httpd -f /etc/httpd/httpd.conf -k graceful-stop";
778           ExecReload = "${pkg}/bin/httpd -f /etc/httpd/httpd.conf -k graceful";
779           User = cfg.user;
780           Group = cfg.group;
781           Type = "forking";
782           PIDFile = "${runtimeDir}/httpd.pid";
783           Restart = "always";
784           RestartSec = "5s";
785           RuntimeDirectory = "httpd httpd/runtime";
786           RuntimeDirectoryMode = "0750";
787           AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
788         };
789       };
791     # postRun hooks on cert renew can't be used to restart Apache since renewal
792     # runs as the unprivileged acme user. sslTargets are added to wantedBy + before
793     # which allows the acme-finished-$cert.target to signify the successful updating
794     # of certs end-to-end.
795     systemd.services.httpd-config-reload = let
796       sslServices = map (certName: "acme-${certName}.service") vhostCertNames;
797       sslTargets = map (certName: "acme-finished-${certName}.target") vhostCertNames;
798     in mkIf (vhostCertNames != []) {
799       wantedBy = sslServices ++ [ "multi-user.target" ];
800       # Before the finished targets, after the renew services.
801       # This service might be needed for HTTP-01 challenges, but we only want to confirm
802       # certs are updated _after_ config has been reloaded.
803       before = sslTargets;
804       after = sslServices;
805       restartTriggers = [ cfg.configFile ];
806       # Block reloading if not all certs exist yet.
807       # Happens when config changes add new vhosts/certs.
808       unitConfig.ConditionPathExists = map (certName: certs.${certName}.directory + "/fullchain.pem") vhostCertNames;
809       serviceConfig = {
810         Type = "oneshot";
811         TimeoutSec = 60;
812         ExecCondition = "/run/current-system/systemd/bin/systemctl -q is-active httpd.service";
813         ExecStartPre = "${pkg}/bin/httpd -f /etc/httpd/httpd.conf -t";
814         ExecStart = "/run/current-system/systemd/bin/systemctl reload httpd.service";
815       };
816     };
818   };