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