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 httpdConf = mainCfg.configFile;
16 php = mainCfg.phpPackage.override { apacheHttpd = httpd.dev; /* otherwise it only gets .out */ };
18 phpMajorVersion = head (splitString "." php.version);
20 mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = httpd; };
22 defaultListen = cfg: if cfg.enableSSL
23 then [{ip = "*"; port = 443;}]
24 else [{ip = "*"; port = 80;}];
28 then defaultListen cfg
31 listenToString = l: "${l.ip}:${toString l.port}";
33 extraModules = attrByPath ["extraModules"] [] mainCfg;
34 extraForeignModules = filter isAttrs extraModules;
35 extraApacheModules = filter isString extraModules;
38 makeServerInfo = cfg: {
39 # Canonical name must not include a trailing slash.
41 let defaultPort = (head (defaultListen cfg)).port; in
43 (if cfg.enableSSL then "https" else "http") + "://" +
45 (if port != defaultPort then ":${toString port}" else "")
46 ) (map (x: x.port) (getListen cfg));
48 # Admin address: inherit from the main server if not specified for
50 adminAddr = if cfg.adminAddr != null then cfg.adminAddr else mainCfg.adminAddr;
53 serverConfig = mainCfg;
54 fullConfig = config; # machine config
58 allHosts = [mainCfg] ++ mainCfg.virtualHosts;
61 callSubservices = serverInfo: defs:
65 if svc ? function then svc.function
66 # instead of using serviceType="mediawiki"; you can copy mediawiki.nix to any location outside nixpkgs, modify it at will, and use serviceExpression=./mediawiki.nix;
67 else if svc ? serviceExpression then import (toString svc.serviceExpression)
68 else import (toString "${toString ./.}/${if svc ? serviceType then svc.serviceType else svc.serviceName}.nix");
70 { modules = [ { options = res.options; config = svc.config or svc; } ];
88 res = defaults // svcFunction { inherit config lib pkgs serverInfo php; };
93 # !!! callSubservices is expensive
94 subservicesFor = cfg: callSubservices (makeServerInfo cfg) cfg.extraSubservices;
96 mainSubservices = subservicesFor mainCfg;
98 allSubservices = mainSubservices ++ concatMap subservicesFor mainCfg.virtualHosts;
101 enableSSL = any (vhost: vhost.enableSSL) allHosts;
104 # Names of modules from ${httpd}/modules that we want to load.
106 [ # HTTP authentication mechanisms: basic and digest.
107 "auth_basic" "auth_digest"
109 # Authentication: is the user who he claims to be?
110 "authn_file" "authn_dbm" "authn_anon" "authn_core"
112 # Authorization: is the user allowed access?
113 "authz_user" "authz_groupfile" "authz_host" "authz_core"
116 "ext_filter" "include" "log_config" "env" "mime_magic"
117 "cern_meta" "expires" "headers" "usertrack" /* "unique_id" */ "setenvif"
118 "mime" "dav" "status" "autoindex" "asis" "info" "dav_fs"
119 "vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling"
120 "userdir" "alias" "rewrite" "proxy" "proxy_http"
121 "unixd" "cache" "cache_disk" "slotmem_shm" "socache_shmcb"
122 "mpm_${mainCfg.multiProcessingModule}"
124 # For compatibility with old configurations, the new module mod_access_compat is provided.
127 ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
128 ++ optional enableSSL "ssl"
129 ++ extraApacheModules;
132 allDenied = "Require all denied";
133 allGranted = "Require all granted";
136 loggingConf = (if mainCfg.logFormat != "none" then ''
137 ErrorLog ${mainCfg.logDir}/error.log
141 LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
142 LogFormat "%h %l %u %t \"%r\" %>s %b" common
143 LogFormat "%{Referer}i -> %U" referer
144 LogFormat "%{User-agent}i" agent
146 CustomLog ${mainCfg.logDir}/access.log ${mainCfg.logFormat}
153 BrowserMatch "Mozilla/2" nokeepalive
154 BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
155 BrowserMatch "RealPlayer 4\.0" force-response-1.0
156 BrowserMatch "Java/1\.0" force-response-1.0
157 BrowserMatch "JDK/1\.0" force-response-1.0
158 BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
159 BrowserMatch "^WebDrive" redirect-carefully
160 BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
161 BrowserMatch "^gnome-vfs" redirect-carefully
166 SSLSessionCache shmcb:${mainCfg.stateDir}/ssl_scache(512000)
170 SSLRandomSeed startup builtin
171 SSLRandomSeed connect builtin
173 SSLProtocol ${mainCfg.sslProtocols}
174 SSLCipherSuite ${mainCfg.sslCiphers}
175 SSLHonorCipherOrder on
180 TypesConfig ${httpd}/conf/mime.types
182 AddType application/x-x509-ca-cert .crt
183 AddType application/x-pkcs7-crl .crl
184 AddType application/x-httpd-php .php .phtml
186 <IfModule mod_mime_magic.c>
187 MIMEMagicFile ${httpd}/conf/magic
192 perServerConf = isMainServer: cfg: let
194 serverInfo = makeServerInfo cfg;
196 subservices = callSubservices serverInfo cfg.extraSubservices;
198 maybeDocumentRoot = fold (svc: acc:
199 if acc == null then svc.documentRoot else assert svc.documentRoot == null; acc
200 ) null ([ cfg ] ++ subservices);
202 documentRoot = if maybeDocumentRoot != null then maybeDocumentRoot else
203 pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out";
205 documentRootConf = ''
206 DocumentRoot "${documentRoot}"
208 <Directory "${documentRoot}">
209 Options Indexes FollowSymLinks
216 concatStringsSep "\n" (filter (x: x != "") (
217 # If this is a vhost, the include the entries for the main server as well.
218 (if isMainServer then [] else [mainCfg.robotsEntries] ++ map (svc: svc.robotsEntries) mainSubservices)
219 ++ [cfg.robotsEntries]
220 ++ (map (svc: svc.robotsEntries) subservices)));
223 ${concatStringsSep "\n" (map (n: "ServerName ${n}") serverInfo.canonicalNames)}
225 ${concatMapStrings (alias: "ServerAlias ${alias}\n") cfg.serverAliases}
227 ${if cfg.sslServerCert != null then ''
228 SSLCertificateFile ${cfg.sslServerCert}
229 SSLCertificateKeyFile ${cfg.sslServerKey}
230 ${if cfg.sslServerChain != null then ''
231 SSLCertificateChainFile ${cfg.sslServerChain}
235 ${if cfg.enableSSL then ''
237 '' else if enableSSL then /* i.e., SSL is enabled for some host, but not this one */
242 ${if isMainServer || cfg.adminAddr != null then ''
243 ServerAdmin ${cfg.adminAddr}
246 ${if !isMainServer && mainCfg.logPerVirtualHost then ''
247 ErrorLog ${mainCfg.logDir}/error-${cfg.hostName}.log
248 CustomLog ${mainCfg.logDir}/access-${cfg.hostName}.log ${cfg.logFormat}
251 ${optionalString (robotsTxt != "") ''
252 Alias /robots.txt ${pkgs.writeText "robots.txt" robotsTxt}
255 ${if isMainServer || maybeDocumentRoot != null then documentRootConf else ""}
257 ${if cfg.enableUserDir then ''
260 UserDir disabled root
262 <Directory "/home/*/public_html">
263 AllowOverride FileInfo AuthConfig Limit Indexes
264 Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
265 <Limit GET POST OPTIONS>
268 <LimitExcept GET POST OPTIONS>
275 ${if cfg.globalRedirect != null && cfg.globalRedirect != "" then ''
276 RedirectPermanent / ${cfg.globalRedirect}
280 let makeFileConf = elem: ''
281 Alias ${elem.urlPath} ${elem.file}
283 in concatMapStrings makeFileConf cfg.servedFiles
287 let makeDirConf = elem: ''
288 Alias ${elem.urlPath} ${elem.dir}/
289 <Directory ${elem.dir}>
295 in concatMapStrings makeDirConf cfg.servedDirs
298 ${concatMapStrings (svc: svc.extraConfig) subservices}
304 confFile = pkgs.writeText "httpd.conf" ''
308 DefaultRuntimeDir ${mainCfg.stateDir}/runtime
310 PidFile ${mainCfg.stateDir}/httpd.pid
312 ${optionalString (mainCfg.multiProcessingModule != "prefork") ''
313 # mod_cgid requires this.
314 ScriptSock ${mainCfg.stateDir}/cgisock
318 MaxClients ${toString mainCfg.maxClients}
319 MaxRequestsPerChild ${toString mainCfg.maxRequestsPerChild}
323 listen = concatMap getListen allHosts;
324 toStr = listen: "Listen ${listenToString listen}\n";
325 uniqueListen = uniqList {inputList = map toStr listen;};
326 in concatStrings uniqueListen
330 Group ${mainCfg.group}
333 load = {name, path}: "LoadModule ${name}_module ${path}\n";
335 concatMap (svc: svc.extraModulesPre) allSubservices
336 ++ map (name: {inherit name; path = "${httpd}/modules/mod_${name}.so";}) apacheModules
337 ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
338 ++ optional enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
339 ++ optional enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
340 ++ concatMap (svc: svc.extraModules) allSubservices
341 ++ extraForeignModules;
342 in concatMapStrings load (unique allModules)
345 AddHandler type-map var
355 Include ${httpd}/conf/extra/httpd-default.conf
356 Include ${httpd}/conf/extra/httpd-autoindex.conf
357 Include ${httpd}/conf/extra/httpd-multilang-errordoc.conf
358 Include ${httpd}/conf/extra/httpd-languages.conf
362 ${if enableSSL then sslConf else ""}
364 # Fascist default - deny access to everything.
366 Options FollowSymLinks
371 # Generate directives for the main server.
372 ${perServerConf true mainCfg}
375 makeVirtualHost = vhost: ''
376 <VirtualHost ${concatStringsSep " " (map listenToString (getListen vhost))}>
377 ${perServerConf false vhost}
380 in concatMapStrings makeVirtualHost mainCfg.virtualHosts
385 enablePHP = mainCfg.enablePHP || any (svc: svc.enablePHP) allSubservices;
387 enablePerl = mainCfg.enablePerl || any (svc: svc.enablePerl) allSubservices;
390 # Generate the PHP configuration file. Should probably be factored
391 # out into a separate module.
392 phpIni = pkgs.runCommand "php.ini"
393 { options = concatStringsSep "\n"
394 ([ mainCfg.phpOptions ] ++ (map (svc: svc.phpOptions) allSubservices));
395 preferLocalBuild = true;
398 cat ${php}/etc/php.ini > $out
399 echo "$options" >> $out
411 services.httpd."${httpdName}" = {
416 description = "Whether to enable the Apache HTTP Server.";
420 type = types.package;
421 default = pkgs.apacheHttpd;
422 defaultText = "pkgs.apacheHttpd";
424 Overridable attribute of the Apache HTTP Server package to use.
428 configFile = mkOption {
431 defaultText = "confFile";
432 example = literalExample ''pkgs.writeText "httpd.conf" "# my custom config file ..."'';
434 Override the configuration file used by Apache. By default,
435 NixOS generates one automatically.
439 extraConfig = mkOption {
443 Cnfiguration lines appended to the generated Apache
444 configuration file. Note that this mechanism may not work
445 when <option>configFile</option> is overridden.
449 extraModules = mkOption {
450 type = types.listOf types.unspecified;
452 example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${pkgs.php}/modules/libphp5.so"; } ]'';
454 Additional Apache modules to be used. These can be
455 specified as a string in the case of modules distributed
456 with Apache, or as an attribute set specifying the
457 <varname>name</varname> and <varname>path</varname> of the
462 logPerVirtualHost = mkOption {
466 If enabled, each virtual host gets its own
467 <filename>access.log</filename> and
468 <filename>error.log</filename>, namely suffixed by the
469 <option>hostName</option> of the virtual host.
477 User account under which httpd runs. The account is created
478 automatically if it doesn't exist.
486 Group under which httpd runs. The account is created
487 automatically if it doesn't exist.
493 default = "/var/log/httpd";
495 Directory for Apache's log files. It is created automatically.
499 stateDir = mkOption {
501 default = "/run/httpd";
503 Directory for Apache's transient runtime state (such as PID
504 files). It is created automatically. Note that the default,
505 <filename>/run/httpd</filename>, is deleted at boot time.
509 virtualHosts = mkOption {
510 type = types.listOf (types.submodule (
511 { options = import <nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix> {
513 forMainServer = false;
519 documentRoot = "/data/webroot-foo";
522 documentRoot = "/data/webroot-bar";
526 Specification of the virtual hosts served by Apache. Each
527 element should be an attribute set specifying the
528 configuration of the virtual host. The available options
529 are the non-global options permissible for the main host.
533 enableMellon = mkOption {
536 description = "Whether to enable the mod_auth_mellon module.";
539 enablePHP = mkOption {
542 description = "Whether to enable the PHP module.";
545 phpPackage = mkOption {
546 type = types.package;
548 defaultText = "pkgs.php";
550 Overridable attribute of the PHP package to use.
554 enablePerl = mkOption {
557 description = "Whether to enable the Perl module (mod_perl).";
560 phpOptions = mkOption {
565 date.timezone = "CET"
568 "Options appended to the PHP configuration file <filename>php.ini</filename>.";
571 multiProcessingModule = mkOption {
577 Multi-processing module to be used by Apache. Available
578 modules are <literal>prefork</literal> (the default;
579 handles each request in a separate child process),
580 <literal>worker</literal> (hybrid approach that starts a
581 number of child processes each running a number of
582 threads) and <literal>event</literal> (a recent variant of
583 <literal>worker</literal> that handles persistent
584 connections more efficiently).
588 maxClients = mkOption {
592 description = "Maximum number of httpd processes (prefork)";
595 maxRequestsPerChild = mkOption {
600 "Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited";
603 sslCiphers = mkOption {
605 default = "HIGH:!aNULL:!MD5:!EXP";
606 description = "Cipher Suite available for negotiation in SSL proxy handshake.";
609 sslProtocols = mkOption {
611 default = "All -SSLv2 -SSLv3 -TLSv1";
612 example = "All -SSLv2 -SSLv3";
613 description = "Allowed SSL/TLS protocol versions.";
617 # Include the options shared between the main server and virtual hosts.
618 // (import <nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix> {
620 forMainServer = true;
626 ###### implementation
628 config = mkIf config.services.httpd."${httpdName}".enable {
630 assertions = [ { assertion = mainCfg.enableSSL == true
631 -> mainCfg.sslServerCert != null
632 && mainCfg.sslServerKey != null;
633 message = "SSL is enabled for httpd, but sslServerCert and/or sslServerKey haven't been specified."; }
636 warnings = map (cfg: "apache-httpd's extraSubservices option is deprecated. Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.") (lib.filter (cfg: cfg.extraSubservices != []) allHosts);
638 users.users = optionalAttrs (withUsers && mainCfg.user == "wwwrun") (singleton
640 group = mainCfg.group;
641 description = "Apache httpd user";
642 uid = config.ids.uids.wwwrun;
645 users.groups = optionalAttrs (withUsers && mainCfg.group == "wwwrun") (singleton
647 gid = config.ids.gids.wwwrun;
650 environment.systemPackages = [httpd] ++ concatMap (svc: svc.extraPath) allSubservices;
652 services.httpd."${httpdName}".phpOptions =
654 ; Needed for PHP's mail() function.
655 sendmail_path = sendmail -t -i
657 ; Don't advertise PHP
659 '' + optionalString (config.time.timeZone != null) ''
661 ; Apparently PHP doesn't use $TZ.
662 date.timezone = "${config.time.timeZone}"
665 systemd.services."httpd${httpdName}" =
666 { description = "Apache HTTPD";
668 wantedBy = [ "multi-user.target" ];
669 wants = [ "keys.target" ];
670 after = [ "network.target" "fs.target" "postgresql.service" "keys.target" ];
673 [ httpd pkgs.coreutils pkgs.gnugrep ]
674 ++ optional enablePHP pkgs.system-sendmail # Needed for PHP's mail() function.
675 ++ concatMap (svc: svc.extraServerPath) allSubservices;
678 optionalAttrs enablePHP { PHPRC = phpIni; }
679 // optionalAttrs mainCfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; }
680 // (listToAttrs (concatMap (svc: svc.globalEnvVars) allSubservices));
684 mkdir -m 0750 -p ${mainCfg.stateDir}
685 [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir}
687 mkdir -m 0750 -p "${mainCfg.stateDir}/runtime"
688 [ $(id -u) != 0 ] || chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime"
690 mkdir -m 0700 -p ${mainCfg.logDir}
692 # Get rid of old semaphores. These tend to accumulate across
693 # server restarts, eventually preventing it from restarting
695 for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${mainCfg.user} ' | cut -f2 -d ' '); do
696 ${pkgs.utillinux}/bin/ipcrm -s $i
699 # Run the startup hooks for the subservices.
700 for i in ${toString (map (svn: svn.startupScript) allSubservices)}; do
701 echo Running Apache startup hook $i...
706 serviceConfig.ExecStart = "@${httpd}/bin/httpd httpd -f ${httpdConf}";
707 serviceConfig.ExecStop = "${httpd}/bin/httpd -f ${httpdConf} -k graceful-stop";
708 serviceConfig.ExecReload = "${httpd}/bin/httpd -f ${httpdConf} -k graceful";
709 serviceConfig.Type = "forking";
710 serviceConfig.PIDFile = "${mainCfg.stateDir}/httpd.pid";
711 serviceConfig.Restart = "always";
712 serviceConfig.RestartSec = "5s";