python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / modules / services / web-servers / apache-httpd / default.nix
blob3ccff8aa5008d421ac68215646c9ae9dd9ccecff
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 { 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 (lib.mdDoc "the Apache HTTP Server");
409       package = mkOption {
410         type = types.package;
411         default = pkgs.apacheHttpd;
412         defaultText = literalExpression "pkgs.apacheHttpd";
413         description = lib.mdDoc ''
414           Overridable attribute of the Apache HTTP Server package to use.
415         '';
416       };
418       configFile = mkOption {
419         type = types.path;
420         default = confFile;
421         defaultText = literalExpression "confFile";
422         example = literalExpression ''pkgs.writeText "httpd.conf" "# my custom config file ..."'';
423         description = lib.mdDoc ''
424           Override the configuration file used by Apache. By default,
425           NixOS generates one automatically.
426         '';
427       };
429       extraConfig = mkOption {
430         type = types.lines;
431         default = "";
432         description = lib.mdDoc ''
433           Configuration lines appended to the generated Apache
434           configuration file. Note that this mechanism will not work
435           when {option}`configFile` is overridden.
436         '';
437       };
439       extraModules = mkOption {
440         type = types.listOf types.unspecified;
441         default = [];
442         example = literalExpression ''
443           [
444             "proxy_connect"
445             { name = "jk"; path = "''${pkgs.tomcat_connectors}/modules/mod_jk.so"; }
446           ]
447         '';
448         description = lib.mdDoc ''
449           Additional Apache modules to be used. These can be
450           specified as a string in the case of modules distributed
451           with Apache, or as an attribute set specifying the
452           {var}`name` and {var}`path` of the
453           module.
454         '';
455       };
457       adminAddr = mkOption {
458         type = types.nullOr types.str;
459         example = "admin@example.org";
460         default = null;
461         description = lib.mdDoc "E-mail address of the server administrator.";
462       };
464       logFormat = mkOption {
465         type = types.str;
466         default = "common";
467         example = "combined";
468         description = lib.mdDoc ''
469           Log format for log files. Possible values are: combined, common, referer, agent, none.
470           See <https://httpd.apache.org/docs/2.4/logs.html> for more details.
471         '';
472       };
474       logPerVirtualHost = mkOption {
475         type = types.bool;
476         default = true;
477         description = lib.mdDoc ''
478           If enabled, each virtual host gets its own
479           {file}`access.log` and
480           {file}`error.log`, namely suffixed by the
481           {option}`hostName` of the virtual host.
482         '';
483       };
485       user = mkOption {
486         type = types.str;
487         default = "wwwrun";
488         description = lib.mdDoc ''
489           User account under which httpd children processes run.
491           If you require the main httpd process to run as
492           `root` add the following configuration:
493           ```
494           systemd.services.httpd.serviceConfig.User = lib.mkForce "root";
495           ```
496         '';
497       };
499       group = mkOption {
500         type = types.str;
501         default = "wwwrun";
502         description = lib.mdDoc ''
503           Group under which httpd children processes run.
504         '';
505       };
507       logDir = mkOption {
508         type = types.path;
509         default = "/var/log/httpd";
510         description = lib.mdDoc ''
511           Directory for Apache's log files. It is created automatically.
512         '';
513       };
515       virtualHosts = mkOption {
516         type = with types; attrsOf (submodule (import ./vhost-options.nix));
517         default = {
518           localhost = {
519             documentRoot = "${pkg}/htdocs";
520           };
521         };
522         defaultText = literalExpression ''
523           {
524             localhost = {
525               documentRoot = "''${package.out}/htdocs";
526             };
527           }
528         '';
529         example = literalExpression ''
530           {
531             "foo.example.com" = {
532               forceSSL = true;
533               documentRoot = "/var/www/foo.example.com"
534             };
535             "bar.example.com" = {
536               addSSL = true;
537               documentRoot = "/var/www/bar.example.com";
538             };
539           }
540         '';
541         description = lib.mdDoc ''
542           Specification of the virtual hosts served by Apache. Each
543           element should be an attribute set specifying the
544           configuration of the virtual host.
545         '';
546       };
548       enableMellon = mkOption {
549         type = types.bool;
550         default = false;
551         description = lib.mdDoc "Whether to enable the mod_auth_mellon module.";
552       };
554       enablePHP = mkOption {
555         type = types.bool;
556         default = false;
557         description = lib.mdDoc "Whether to enable the PHP module.";
558       };
560       phpPackage = mkOption {
561         type = types.package;
562         default = pkgs.php;
563         defaultText = literalExpression "pkgs.php";
564         description = lib.mdDoc ''
565           Overridable attribute of the PHP package to use.
566         '';
567       };
569       enablePerl = mkOption {
570         type = types.bool;
571         default = false;
572         description = lib.mdDoc "Whether to enable the Perl module (mod_perl).";
573       };
575       phpOptions = mkOption {
576         type = types.lines;
577         default = "";
578         example =
579           ''
580             date.timezone = "CET"
581           '';
582         description = lib.mdDoc ''
583           Options appended to the PHP configuration file {file}`php.ini`.
584         '';
585       };
587       mpm = mkOption {
588         type = types.enum [ "event" "prefork" "worker" ];
589         default = "event";
590         example = "worker";
591         description =
592           lib.mdDoc ''
593             Multi-processing module to be used by Apache. Available
594             modules are `prefork` (handles each
595             request in a separate child process), `worker`
596             (hybrid approach that starts a number of child processes
597             each running a number of threads) and `event`
598             (the default; a recent variant of `worker`
599             that handles persistent connections more efficiently).
600           '';
601       };
603       maxClients = mkOption {
604         type = types.int;
605         default = 150;
606         example = 8;
607         description = lib.mdDoc "Maximum number of httpd processes (prefork)";
608       };
610       maxRequestsPerChild = mkOption {
611         type = types.int;
612         default = 0;
613         example = 500;
614         description = lib.mdDoc ''
615           Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited.
616         '';
617       };
619       sslCiphers = mkOption {
620         type = types.str;
621         default = "HIGH:!aNULL:!MD5:!EXP";
622         description = lib.mdDoc "Cipher Suite available for negotiation in SSL proxy handshake.";
623       };
625       sslProtocols = mkOption {
626         type = types.str;
627         default = "All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1";
628         example = "All -SSLv2 -SSLv3";
629         description = lib.mdDoc "Allowed SSL/TLS protocol versions.";
630       };
631     };
633   };
635   # implementation
637   config = mkIf cfg.enable {
639     assertions = [
640       {
641         assertion = all (hostOpts: !hostOpts.enableSSL) vhosts;
642         message = ''
643           The option `services.httpd.virtualHosts.<name>.enableSSL` no longer has any effect; please remove it.
644           Select one of `services.httpd.virtualHosts.<name>.addSSL`, `services.httpd.virtualHosts.<name>.forceSSL`,
645           or `services.httpd.virtualHosts.<name>.onlySSL`.
646         '';
647       }
648       {
649         assertion = all (hostOpts: with hostOpts; !(addSSL && onlySSL) && !(forceSSL && onlySSL) && !(addSSL && forceSSL)) vhosts;
650         message = ''
651           Options `services.httpd.virtualHosts.<name>.addSSL`,
652           `services.httpd.virtualHosts.<name>.onlySSL` and `services.httpd.virtualHosts.<name>.forceSSL`
653           are mutually exclusive.
654         '';
655       }
656       {
657         assertion = all (hostOpts: !(hostOpts.enableACME && hostOpts.useACMEHost != null)) vhosts;
658         message = ''
659           Options `services.httpd.virtualHosts.<name>.enableACME` and
660           `services.httpd.virtualHosts.<name>.useACMEHost` are mutually exclusive.
661         '';
662       }
663     ] ++ map (name: mkCertOwnershipAssertion {
664       inherit (cfg) group user;
665       cert = config.security.acme.certs.${name};
666       groups = config.users.groups;
667     }) dependentCertNames;
669     warnings =
670       mapAttrsToList (name: hostOpts: ''
671         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.
672       '') (filterAttrs (name: hostOpts: hostOpts.servedFiles != []) cfg.virtualHosts);
674     users.users = optionalAttrs (cfg.user == "wwwrun") {
675       wwwrun = {
676         group = cfg.group;
677         description = "Apache httpd user";
678         uid = config.ids.uids.wwwrun;
679       };
680     };
682     users.groups = optionalAttrs (cfg.group == "wwwrun") {
683       wwwrun.gid = config.ids.gids.wwwrun;
684     };
686     security.acme.certs = let
687       acmePairs = map (hostOpts: let
688         hasRoot = hostOpts.acmeRoot != null;
689       in nameValuePair hostOpts.hostName {
690         group = mkDefault cfg.group;
691         # if acmeRoot is null inherit config.security.acme
692         # Since config.security.acme.certs.<cert>.webroot's own default value
693         # should take precedence set priority higher than mkOptionDefault
694         webroot = mkOverride (if hasRoot then 1000 else 2000) hostOpts.acmeRoot;
695         # Also nudge dnsProvider to null in case it is inherited
696         dnsProvider = mkOverride (if hasRoot then 1000 else 2000) null;
697         extraDomainNames = hostOpts.serverAliases;
698         # Use the vhost-specific email address if provided, otherwise let
699         # security.acme.email or security.acme.certs.<cert>.email be used.
700         email = mkOverride 2000 (if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr);
701       # Filter for enableACME-only vhosts. Don't want to create dud certs
702       }) (filter (hostOpts: hostOpts.useACMEHost == null) acmeEnabledVhosts);
703     in listToAttrs acmePairs;
705     # httpd requires a stable path to the configuration file for reloads
706     environment.etc."httpd/httpd.conf".source = cfg.configFile;
707     environment.systemPackages = [
708       apachectl
709       pkg
710     ];
712     services.logrotate = optionalAttrs (cfg.logFormat != "none") {
713       enable = mkDefault true;
714       settings.httpd = {
715         files = "${cfg.logDir}/*.log";
716         su = "${cfg.user} ${cfg.group}";
717         frequency = "daily";
718         rotate = 28;
719         sharedscripts = true;
720         compress = true;
721         delaycompress = true;
722         postrotate = "systemctl reload httpd.service > /dev/null 2>/dev/null || true";
723       };
724     };
726     services.httpd.phpOptions =
727       ''
728         ; Don't advertise PHP
729         expose_php = off
730       '' + optionalString (config.time.timeZone != null) ''
732         ; Apparently PHP doesn't use $TZ.
733         date.timezone = "${config.time.timeZone}"
734       '';
736     services.httpd.extraModules = mkBefore [
737       # HTTP authentication mechanisms: basic and digest.
738       "auth_basic" "auth_digest"
740       # Authentication: is the user who he claims to be?
741       "authn_file" "authn_dbm" "authn_anon"
743       # Authorization: is the user allowed access?
744       "authz_user" "authz_groupfile" "authz_host"
746       # Other modules.
747       "ext_filter" "include" "env" "mime_magic"
748       "cern_meta" "expires" "headers" "usertrack" "setenvif"
749       "dav" "status" "asis" "info" "dav_fs"
750       "vhost_alias" "imagemap" "actions" "speling"
751       "proxy" "proxy_http"
752       "cache" "cache_disk"
754       # For compatibility with old configurations, the new module mod_access_compat is provided.
755       "access_compat"
756     ];
758     systemd.tmpfiles.rules =
759       let
760         svc = config.systemd.services.httpd.serviceConfig;
761       in
762         [
763           "d '${cfg.logDir}' 0700 ${svc.User} ${svc.Group}"
764           "Z '${cfg.logDir}' - ${svc.User} ${svc.Group}"
765         ];
767     systemd.services.httpd = {
768         description = "Apache HTTPD";
769         wantedBy = [ "multi-user.target" ];
770         wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) dependentCertNames);
771         after = [ "network.target" ] ++ map (certName: "acme-selfsigned-${certName}.service") dependentCertNames;
772         before = map (certName: "acme-${certName}.service") dependentCertNames;
773         restartTriggers = [ cfg.configFile ];
775         path = [ pkg pkgs.coreutils pkgs.gnugrep ];
777         environment =
778           optionalAttrs cfg.enablePHP { PHPRC = phpIni; }
779           // optionalAttrs cfg.enableMellon { LD_LIBRARY_PATH  = "${pkgs.xmlsec}/lib"; };
781         preStart =
782           ''
783             # Get rid of old semaphores.  These tend to accumulate across
784             # server restarts, eventually preventing it from restarting
785             # successfully.
786             for i in $(${pkgs.util-linux}/bin/ipcs -s | grep ' ${cfg.user} ' | cut -f2 -d ' '); do
787                 ${pkgs.util-linux}/bin/ipcrm -s $i
788             done
789           '';
791         serviceConfig = {
792           ExecStart = "@${pkg}/bin/httpd httpd -f /etc/httpd/httpd.conf";
793           ExecStop = "${pkg}/bin/httpd -f /etc/httpd/httpd.conf -k graceful-stop";
794           ExecReload = "${pkg}/bin/httpd -f /etc/httpd/httpd.conf -k graceful";
795           User = cfg.user;
796           Group = cfg.group;
797           Type = "forking";
798           PIDFile = "${runtimeDir}/httpd.pid";
799           Restart = "always";
800           RestartSec = "5s";
801           RuntimeDirectory = "httpd httpd/runtime";
802           RuntimeDirectoryMode = "0750";
803           AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
804         };
805       };
807     # postRun hooks on cert renew can't be used to restart Apache since renewal
808     # runs as the unprivileged acme user. sslTargets are added to wantedBy + before
809     # which allows the acme-finished-$cert.target to signify the successful updating
810     # of certs end-to-end.
811     systemd.services.httpd-config-reload = let
812       sslServices = map (certName: "acme-${certName}.service") dependentCertNames;
813       sslTargets = map (certName: "acme-finished-${certName}.target") dependentCertNames;
814     in mkIf (sslServices != []) {
815       wantedBy = sslServices ++ [ "multi-user.target" ];
816       # Before the finished targets, after the renew services.
817       # This service might be needed for HTTP-01 challenges, but we only want to confirm
818       # certs are updated _after_ config has been reloaded.
819       before = sslTargets;
820       after = sslServices;
821       restartTriggers = [ cfg.configFile ];
822       # Block reloading if not all certs exist yet.
823       # Happens when config changes add new vhosts/certs.
824       unitConfig.ConditionPathExists = map (certName: certs.${certName}.directory + "/fullchain.pem") dependentCertNames;
825       serviceConfig = {
826         Type = "oneshot";
827         TimeoutSec = 60;
828         ExecCondition = "/run/current-system/systemd/bin/systemctl -q is-active httpd.service";
829         ExecStartPre = "${pkg}/bin/httpd -f /etc/httpd/httpd.conf -t";
830         ExecStart = "/run/current-system/systemd/bin/systemctl reload httpd.service";
831       };
832     };
834   };