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