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