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