1 # to help backporting this builder should stay as close as possible to
2 # nixos/modules/services/web-servers/apache-httpd/default.nix
3 { httpdName, withUsers ? true }:
4 { config, lib, pkgs, ... }:
10 mainCfg = config.services.httpd."${httpdName}";
12 httpd = mainCfg.package.out;
14 version24 = !versionOlder httpd.version "2.4";
16 httpdConf = mainCfg.configFile;
18 php = mainCfg.phpPackage.override { apacheHttpd = httpd.dev; /* otherwise it only gets .out */ };
20 phpMajorVersion = head (splitString "." php.version);
22 mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = httpd; };
24 defaultListen = cfg: if cfg.enableSSL
25 then [{ip = "*"; port = 443;}]
26 else [{ip = "*"; port = 80;}];
29 let list = (lib.optional (cfg.port != 0) {ip = "*"; port = cfg.port;}) ++ cfg.listen;
31 then defaultListen cfg
34 listenToString = l: "${l.ip}:${toString l.port}";
36 extraModules = attrByPath ["extraModules"] [] mainCfg;
37 extraForeignModules = filter isAttrs extraModules;
38 extraApacheModules = filter isString extraModules;
41 makeServerInfo = cfg: {
42 # Canonical name must not include a trailing slash.
44 let defaultPort = (head (defaultListen cfg)).port; in
46 (if cfg.enableSSL then "https" else "http") + "://" +
48 (if port != defaultPort then ":${toString port}" else "")
49 ) (map (x: x.port) (getListen cfg));
51 # Admin address: inherit from the main server if not specified for
53 adminAddr = if cfg.adminAddr != null then cfg.adminAddr else mainCfg.adminAddr;
56 serverConfig = mainCfg;
57 fullConfig = config; # machine config
61 allHosts = [mainCfg] ++ mainCfg.virtualHosts;
64 callSubservices = serverInfo: defs:
68 if svc ? function then svc.function
69 # instead of using serviceType="mediawiki"; you can copy mediawiki.nix to any location outside nixpkgs, modify it at will, and use serviceExpression=./mediawiki.nix;
70 else if svc ? serviceExpression then import (toString svc.serviceExpression)
71 else import (toString "${toString ./.}/${if svc ? serviceType then svc.serviceType else svc.serviceName}.nix");
73 { modules = [ { options = res.options; config = svc.config or svc; } ];
91 res = defaults // svcFunction { inherit config lib pkgs serverInfo php; };
96 # !!! callSubservices is expensive
97 subservicesFor = cfg: callSubservices (makeServerInfo cfg) cfg.extraSubservices;
99 mainSubservices = subservicesFor mainCfg;
101 allSubservices = mainSubservices ++ concatMap subservicesFor mainCfg.virtualHosts;
104 enableSSL = any (vhost: vhost.enableSSL) allHosts;
107 # Names of modules from ${httpd}/modules that we want to load.
109 [ # HTTP authentication mechanisms: basic and digest.
110 "auth_basic" "auth_digest"
112 # Authentication: is the user who he claims to be?
113 "authn_file" "authn_dbm" "authn_anon"
114 (if version24 then "authn_core" else "authn_alias")
116 # Authorization: is the user allowed access?
117 "authz_user" "authz_groupfile" "authz_host"
120 "ext_filter" "include" "log_config" "env" "mime_magic"
121 "cern_meta" "expires" "headers" "usertrack" /* "unique_id" */ "setenvif"
122 "mime" "dav" "status" "autoindex" "asis" "info" "dav_fs"
123 "vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling"
124 "userdir" "alias" "rewrite" "proxy" "proxy_http"
126 ++ optionals version24 [
127 "mpm_${mainCfg.multiProcessingModule}"
133 # For compatibility with old configurations, the new module mod_access_compat is provided.
136 ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
137 ++ optional enableSSL "ssl"
138 ++ extraApacheModules;
141 allDenied = if version24 then ''
148 allGranted = if version24 then ''
156 loggingConf = (if mainCfg.logFormat != "none" then ''
157 ErrorLog ${mainCfg.logDir}/error.log
161 LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
162 LogFormat "%h %l %u %t \"%r\" %>s %b" common
163 LogFormat "%{Referer}i -> %U" referer
164 LogFormat "%{User-agent}i" agent
166 CustomLog ${mainCfg.logDir}/access.log ${mainCfg.logFormat}
173 BrowserMatch "Mozilla/2" nokeepalive
174 BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
175 BrowserMatch "RealPlayer 4\.0" force-response-1.0
176 BrowserMatch "Java/1\.0" force-response-1.0
177 BrowserMatch "JDK/1\.0" force-response-1.0
178 BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
179 BrowserMatch "^WebDrive" redirect-carefully
180 BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
181 BrowserMatch "^gnome-vfs" redirect-carefully
186 SSLSessionCache ${if version24 then "shmcb" else "shm"}:${mainCfg.stateDir}/ssl_scache(512000)
188 ${if version24 then "Mutex" else "SSLMutex"} posixsem
190 SSLRandomSeed startup builtin
191 SSLRandomSeed connect builtin
193 SSLProtocol ${mainCfg.sslProtocols}
194 SSLCipherSuite ${mainCfg.sslCiphers}
195 SSLHonorCipherOrder on
200 TypesConfig ${httpd}/conf/mime.types
202 AddType application/x-x509-ca-cert .crt
203 AddType application/x-pkcs7-crl .crl
204 AddType application/x-httpd-php .php .phtml
206 <IfModule mod_mime_magic.c>
207 MIMEMagicFile ${httpd}/conf/magic
212 perServerConf = isMainServer: cfg: let
214 serverInfo = makeServerInfo cfg;
216 subservices = callSubservices serverInfo cfg.extraSubservices;
218 maybeDocumentRoot = fold (svc: acc:
219 if acc == null then svc.documentRoot else assert svc.documentRoot == null; acc
220 ) null ([ cfg ] ++ subservices);
222 documentRoot = if maybeDocumentRoot != null then maybeDocumentRoot else
223 pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out";
225 documentRootConf = ''
226 DocumentRoot "${documentRoot}"
228 <Directory "${documentRoot}">
229 Options Indexes FollowSymLinks
236 concatStringsSep "\n" (filter (x: x != "") (
237 # If this is a vhost, the include the entries for the main server as well.
238 (if isMainServer then [] else [mainCfg.robotsEntries] ++ map (svc: svc.robotsEntries) mainSubservices)
239 ++ [cfg.robotsEntries]
240 ++ (map (svc: svc.robotsEntries) subservices)));
243 ${concatStringsSep "\n" (map (n: "ServerName ${n}") serverInfo.canonicalNames)}
245 ${concatMapStrings (alias: "ServerAlias ${alias}\n") cfg.serverAliases}
247 ${if cfg.sslServerCert != null then ''
248 SSLCertificateFile ${cfg.sslServerCert}
249 SSLCertificateKeyFile ${cfg.sslServerKey}
250 ${if cfg.sslServerChain != null then ''
251 SSLCertificateChainFile ${cfg.sslServerChain}
255 ${if cfg.enableSSL then ''
257 '' else if enableSSL then /* i.e., SSL is enabled for some host, but not this one */
262 ${if isMainServer || cfg.adminAddr != null then ''
263 ServerAdmin ${cfg.adminAddr}
266 ${if !isMainServer && mainCfg.logPerVirtualHost then ''
267 ErrorLog ${mainCfg.logDir}/error-${cfg.hostName}.log
268 CustomLog ${mainCfg.logDir}/access-${cfg.hostName}.log ${cfg.logFormat}
271 ${optionalString (robotsTxt != "") ''
272 Alias /robots.txt ${pkgs.writeText "robots.txt" robotsTxt}
275 ${if isMainServer || maybeDocumentRoot != null then documentRootConf else ""}
277 ${if cfg.enableUserDir then ''
280 UserDir disabled root
282 <Directory "/home/*/public_html">
283 AllowOverride FileInfo AuthConfig Limit Indexes
284 Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
285 <Limit GET POST OPTIONS>
288 <LimitExcept GET POST OPTIONS>
295 ${if cfg.globalRedirect != null && cfg.globalRedirect != "" then ''
296 RedirectPermanent / ${cfg.globalRedirect}
300 let makeFileConf = elem: ''
301 Alias ${elem.urlPath} ${elem.file}
303 in concatMapStrings makeFileConf cfg.servedFiles
307 let makeDirConf = elem: ''
308 Alias ${elem.urlPath} ${elem.dir}/
309 <Directory ${elem.dir}>
315 in concatMapStrings makeDirConf cfg.servedDirs
318 ${concatMapStrings (svc: svc.extraConfig) subservices}
324 confFile = pkgs.writeText "httpd.conf" ''
328 ${optionalString version24 ''
329 DefaultRuntimeDir ${mainCfg.stateDir}/runtime
332 PidFile ${mainCfg.stateDir}/httpd.pid
334 ${optionalString (mainCfg.multiProcessingModule != "prefork") ''
335 # mod_cgid requires this.
336 ScriptSock ${mainCfg.stateDir}/cgisock
340 MaxClients ${toString mainCfg.maxClients}
341 MaxRequestsPerChild ${toString mainCfg.maxRequestsPerChild}
345 listen = concatMap getListen allHosts;
346 toStr = listen: "Listen ${listenToString listen}\n";
347 uniqueListen = uniqList {inputList = map toStr listen;};
348 in concatStrings uniqueListen
352 Group ${mainCfg.group}
355 load = {name, path}: "LoadModule ${name}_module ${path}\n";
357 concatMap (svc: svc.extraModulesPre) allSubservices
358 ++ map (name: {inherit name; path = "${httpd}/modules/mod_${name}.so";}) apacheModules
359 ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
360 ++ optional enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
361 ++ optional enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
362 ++ concatMap (svc: svc.extraModules) allSubservices
363 ++ extraForeignModules;
364 in concatMapStrings load allModules
367 AddHandler type-map var
377 Include ${httpd}/conf/extra/httpd-default.conf
378 Include ${httpd}/conf/extra/httpd-autoindex.conf
379 Include ${httpd}/conf/extra/httpd-multilang-errordoc.conf
380 Include ${httpd}/conf/extra/httpd-languages.conf
384 ${if enableSSL then sslConf else ""}
386 # Fascist default - deny access to everything.
388 Options FollowSymLinks
393 # Generate directives for the main server.
394 ${perServerConf true mainCfg}
396 # Always enable virtual hosts; it doesn't seem to hurt.
398 listen = concatMap getListen allHosts;
399 uniqueListen = uniqList {inputList = listen;};
400 directives = concatMapStrings (listen: "NameVirtualHost ${listenToString listen}\n") uniqueListen;
401 in optionalString (!version24) directives
405 makeVirtualHost = vhost: ''
406 <VirtualHost ${concatStringsSep " " (map listenToString (getListen vhost))}>
407 ${perServerConf false vhost}
410 in concatMapStrings makeVirtualHost mainCfg.virtualHosts
415 enablePHP = mainCfg.enablePHP || any (svc: svc.enablePHP) allSubservices;
417 enablePerl = mainCfg.enablePerl || any (svc: svc.enablePerl) allSubservices;
420 # Generate the PHP configuration file. Should probably be factored
421 # out into a separate module.
422 phpIni = pkgs.runCommand "php.ini"
423 { options = concatStringsSep "\n"
424 ([ mainCfg.phpOptions ] ++ (map (svc: svc.phpOptions) allSubservices));
425 preferLocalBuild = true;
428 cat ${php}/etc/php.ini > $out
429 echo "$options" >> $out
441 services.httpd."${httpdName}" = {
446 description = "Whether to enable the Apache HTTP Server.";
450 type = types.package;
451 default = pkgs.apacheHttpd;
452 defaultText = "pkgs.apacheHttpd";
454 Overridable attribute of the Apache HTTP Server package to use.
458 configFile = mkOption {
461 defaultText = "confFile";
462 example = literalExample ''pkgs.writeText "httpd.conf" "# my custom config file ..."'';
464 Override the configuration file used by Apache. By default,
465 NixOS generates one automatically.
469 extraConfig = mkOption {
473 Cnfiguration lines appended to the generated Apache
474 configuration file. Note that this mechanism may not work
475 when <option>configFile</option> is overridden.
479 extraModules = mkOption {
480 type = types.listOf types.unspecified;
482 example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${pkgs.php}/modules/libphp5.so"; } ]'';
484 Additional Apache modules to be used. These can be
485 specified as a string in the case of modules distributed
486 with Apache, or as an attribute set specifying the
487 <varname>name</varname> and <varname>path</varname> of the
492 logPerVirtualHost = mkOption {
496 If enabled, each virtual host gets its own
497 <filename>access.log</filename> and
498 <filename>error.log</filename>, namely suffixed by the
499 <option>hostName</option> of the virtual host.
507 User account under which httpd runs. The account is created
508 automatically if it doesn't exist.
516 Group under which httpd runs. The account is created
517 automatically if it doesn't exist.
523 default = "/var/log/httpd";
525 Directory for Apache's log files. It is created automatically.
529 stateDir = mkOption {
531 default = "/run/httpd";
533 Directory for Apache's transient runtime state (such as PID
534 files). It is created automatically. Note that the default,
535 <filename>/run/httpd</filename>, is deleted at boot time.
539 virtualHosts = mkOption {
540 type = types.listOf (types.submodule (
541 { options = import <nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix> {
543 forMainServer = false;
549 documentRoot = "/data/webroot-foo";
552 documentRoot = "/data/webroot-bar";
556 Specification of the virtual hosts served by Apache. Each
557 element should be an attribute set specifying the
558 configuration of the virtual host. The available options
559 are the non-global options permissible for the main host.
563 enableMellon = mkOption {
566 description = "Whether to enable the mod_auth_mellon module.";
569 enablePHP = mkOption {
572 description = "Whether to enable the PHP module.";
575 phpPackage = mkOption {
576 type = types.package;
578 defaultText = "pkgs.php";
580 Overridable attribute of the PHP package to use.
584 enablePerl = mkOption {
587 description = "Whether to enable the Perl module (mod_perl).";
590 phpOptions = mkOption {
595 date.timezone = "CET"
598 "Options appended to the PHP configuration file <filename>php.ini</filename>.";
601 multiProcessingModule = mkOption {
607 Multi-processing module to be used by Apache. Available
608 modules are <literal>prefork</literal> (the default;
609 handles each request in a separate child process),
610 <literal>worker</literal> (hybrid approach that starts a
611 number of child processes each running a number of
612 threads) and <literal>event</literal> (a recent variant of
613 <literal>worker</literal> that handles persistent
614 connections more efficiently).
618 maxClients = mkOption {
622 description = "Maximum number of httpd processes (prefork)";
625 maxRequestsPerChild = mkOption {
630 "Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited";
633 sslCiphers = mkOption {
635 default = "HIGH:!aNULL:!MD5:!EXP";
636 description = "Cipher Suite available for negotiation in SSL proxy handshake.";
639 sslProtocols = mkOption {
641 default = "All -SSLv2 -SSLv3 -TLSv1";
642 example = "All -SSLv2 -SSLv3";
643 description = "Allowed SSL/TLS protocol versions.";
647 # Include the options shared between the main server and virtual hosts.
648 // (import <nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix> {
650 forMainServer = true;
656 ###### implementation
658 config = mkIf config.services.httpd."${httpdName}".enable {
660 assertions = [ { assertion = mainCfg.enableSSL == true
661 -> mainCfg.sslServerCert != null
662 && mainCfg.sslServerKey != null;
663 message = "SSL is enabled for httpd, but sslServerCert and/or sslServerKey haven't been specified."; }
666 warnings = map (cfg: ''apache-httpd's port option is deprecated. Use listen = [{/*ip = "*"; */ port = ${toString cfg.port};}]; instead'' ) (lib.filter (cfg: cfg.port != 0) allHosts);
668 users.users = optionalAttrs (withUsers && mainCfg.user == "wwwrun") (singleton
670 group = mainCfg.group;
671 description = "Apache httpd user";
672 uid = config.ids.uids.wwwrun;
675 users.groups = optionalAttrs (withUsers && mainCfg.group == "wwwrun") (singleton
677 gid = config.ids.gids.wwwrun;
680 environment.systemPackages = [httpd] ++ concatMap (svc: svc.extraPath) allSubservices;
682 services.httpd."${httpdName}".phpOptions =
684 ; Needed for PHP's mail() function.
685 sendmail_path = sendmail -t -i
687 ; Don't advertise PHP
689 '' + optionalString (!isNull config.time.timeZone) ''
691 ; Apparently PHP doesn't use $TZ.
692 date.timezone = "${config.time.timeZone}"
695 systemd.services."httpd${httpdName}" =
696 { description = "Apache HTTPD";
698 wantedBy = [ "multi-user.target" ];
699 wants = [ "keys.target" ];
700 after = [ "network.target" "fs.target" "postgresql.service" "keys.target" ];
703 [ httpd pkgs.coreutils pkgs.gnugrep ]
704 ++ optional enablePHP pkgs.system-sendmail # Needed for PHP's mail() function.
705 ++ concatMap (svc: svc.extraServerPath) allSubservices;
708 optionalAttrs enablePHP { PHPRC = phpIni; }
709 // optionalAttrs mainCfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; }
710 // (listToAttrs (concatMap (svc: svc.globalEnvVars) allSubservices));
714 mkdir -m 0750 -p ${mainCfg.stateDir}
715 [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir}
716 ${optionalString version24 ''
717 mkdir -m 0750 -p "${mainCfg.stateDir}/runtime"
718 [ $(id -u) != 0 ] || chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime"
720 mkdir -m 0700 -p ${mainCfg.logDir}
722 # Get rid of old semaphores. These tend to accumulate across
723 # server restarts, eventually preventing it from restarting
725 for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${mainCfg.user} ' | cut -f2 -d ' '); do
726 ${pkgs.utillinux}/bin/ipcrm -s $i
729 # Run the startup hooks for the subservices.
730 for i in ${toString (map (svn: svn.startupScript) allSubservices)}; do
731 echo Running Apache startup hook $i...
736 serviceConfig.ExecStart = "@${httpd}/bin/httpd httpd -f ${httpdConf}";
737 serviceConfig.ExecStop = "${httpd}/bin/httpd -f ${httpdConf} -k graceful-stop";
738 serviceConfig.ExecReload = "${httpd}/bin/httpd -f ${httpdConf} -k graceful";
739 serviceConfig.Type = "forking";
740 serviceConfig.PIDFile = "${mainCfg.stateDir}/httpd.pid";
741 serviceConfig.Restart = "always";
742 serviceConfig.RestartSec = "5s";