at 22.05-pre 33 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.nginx; 7 certs = config.security.acme.certs; 8 vhostsConfigs = mapAttrsToList (vhostName: vhostConfig: vhostConfig) virtualHosts; 9 acmeEnabledVhosts = filter (vhostConfig: vhostConfig.enableACME || vhostConfig.useACMEHost != null) vhostsConfigs; 10 dependentCertNames = unique (map (hostOpts: hostOpts.certName) acmeEnabledVhosts); 11 virtualHosts = mapAttrs (vhostName: vhostConfig: 12 let 13 serverName = if vhostConfig.serverName != null 14 then vhostConfig.serverName 15 else vhostName; 16 certName = if vhostConfig.useACMEHost != null 17 then vhostConfig.useACMEHost 18 else serverName; 19 in 20 vhostConfig // { 21 inherit serverName certName; 22 } // (optionalAttrs (vhostConfig.enableACME || vhostConfig.useACMEHost != null) { 23 sslCertificate = "${certs.${certName}.directory}/fullchain.pem"; 24 sslCertificateKey = "${certs.${certName}.directory}/key.pem"; 25 sslTrustedCertificate = if vhostConfig.sslTrustedCertificate != null 26 then vhostConfig.sslTrustedCertificate 27 else "${certs.${certName}.directory}/chain.pem"; 28 }) 29 ) cfg.virtualHosts; 30 enableIPv6 = config.networking.enableIPv6; 31 32 defaultFastcgiParams = { 33 SCRIPT_FILENAME = "$document_root$fastcgi_script_name"; 34 QUERY_STRING = "$query_string"; 35 REQUEST_METHOD = "$request_method"; 36 CONTENT_TYPE = "$content_type"; 37 CONTENT_LENGTH = "$content_length"; 38 39 SCRIPT_NAME = "$fastcgi_script_name"; 40 REQUEST_URI = "$request_uri"; 41 DOCUMENT_URI = "$document_uri"; 42 DOCUMENT_ROOT = "$document_root"; 43 SERVER_PROTOCOL = "$server_protocol"; 44 REQUEST_SCHEME = "$scheme"; 45 HTTPS = "$https if_not_empty"; 46 47 GATEWAY_INTERFACE = "CGI/1.1"; 48 SERVER_SOFTWARE = "nginx/$nginx_version"; 49 50 REMOTE_ADDR = "$remote_addr"; 51 REMOTE_PORT = "$remote_port"; 52 SERVER_ADDR = "$server_addr"; 53 SERVER_PORT = "$server_port"; 54 SERVER_NAME = "$server_name"; 55 56 REDIRECT_STATUS = "200"; 57 }; 58 59 recommendedProxyConfig = pkgs.writeText "nginx-recommended-proxy-headers.conf" '' 60 proxy_set_header Host $host; 61 proxy_set_header X-Real-IP $remote_addr; 62 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 63 proxy_set_header X-Forwarded-Proto $scheme; 64 proxy_set_header X-Forwarded-Host $host; 65 proxy_set_header X-Forwarded-Server $host; 66 ''; 67 68 upstreamConfig = toString (flip mapAttrsToList cfg.upstreams (name: upstream: '' 69 upstream ${name} { 70 ${toString (flip mapAttrsToList upstream.servers (name: server: '' 71 server ${name} ${optionalString server.backup "backup"}; 72 ''))} 73 ${upstream.extraConfig} 74 } 75 '')); 76 77 commonHttpConfig = '' 78 # The mime type definitions included with nginx are very incomplete, so 79 # we use a list of mime types from the mailcap package, which is also 80 # used by most other Linux distributions by default. 81 include ${pkgs.mailcap}/etc/nginx/mime.types; 82 include ${cfg.package}/conf/fastcgi.conf; 83 include ${cfg.package}/conf/uwsgi_params; 84 85 default_type application/octet-stream; 86 ''; 87 88 configFile = pkgs.writers.writeNginxConfig "nginx.conf" '' 89 pid /run/nginx/nginx.pid; 90 error_log ${cfg.logError}; 91 daemon off; 92 93 ${cfg.config} 94 95 ${optionalString (cfg.eventsConfig != "" || cfg.config == "") '' 96 events { 97 ${cfg.eventsConfig} 98 } 99 ''} 100 101 ${optionalString (cfg.httpConfig == "" && cfg.config == "") '' 102 http { 103 ${commonHttpConfig} 104 105 ${optionalString (cfg.resolver.addresses != []) '' 106 resolver ${toString cfg.resolver.addresses} ${optionalString (cfg.resolver.valid != "") "valid=${cfg.resolver.valid}"} ${optionalString (!cfg.resolver.ipv6) "ipv6=off"}; 107 ''} 108 ${upstreamConfig} 109 110 ${optionalString (cfg.recommendedOptimisation) '' 111 # optimisation 112 sendfile on; 113 tcp_nopush on; 114 tcp_nodelay on; 115 keepalive_timeout 65; 116 types_hash_max_size 4096; 117 ''} 118 119 ssl_protocols ${cfg.sslProtocols}; 120 ${optionalString (cfg.sslCiphers != null) "ssl_ciphers ${cfg.sslCiphers};"} 121 ${optionalString (cfg.sslDhparam != null) "ssl_dhparam ${cfg.sslDhparam};"} 122 123 ${optionalString (cfg.recommendedTlsSettings) '' 124 # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate 125 126 ssl_session_timeout 1d; 127 ssl_session_cache shared:SSL:10m; 128 # Breaks forward secrecy: https://github.com/mozilla/server-side-tls/issues/135 129 ssl_session_tickets off; 130 # We don't enable insecure ciphers by default, so this allows 131 # clients to pick the most performant, per https://github.com/mozilla/server-side-tls/issues/260 132 ssl_prefer_server_ciphers off; 133 134 # OCSP stapling 135 ssl_stapling on; 136 ssl_stapling_verify on; 137 ''} 138 139 ${optionalString (cfg.recommendedGzipSettings) '' 140 gzip on; 141 gzip_proxied any; 142 gzip_comp_level 5; 143 gzip_types 144 application/atom+xml 145 application/javascript 146 application/json 147 application/xml 148 application/xml+rss 149 image/svg+xml 150 text/css 151 text/javascript 152 text/plain 153 text/xml; 154 gzip_vary on; 155 ''} 156 157 ${optionalString (cfg.recommendedProxySettings) '' 158 proxy_redirect off; 159 proxy_connect_timeout ${cfg.proxyTimeout}; 160 proxy_send_timeout ${cfg.proxyTimeout}; 161 proxy_read_timeout ${cfg.proxyTimeout}; 162 proxy_http_version 1.1; 163 include ${recommendedProxyConfig}; 164 ''} 165 166 ${optionalString (cfg.mapHashBucketSize != null) '' 167 map_hash_bucket_size ${toString cfg.mapHashBucketSize}; 168 ''} 169 170 ${optionalString (cfg.mapHashMaxSize != null) '' 171 map_hash_max_size ${toString cfg.mapHashMaxSize}; 172 ''} 173 174 ${optionalString (cfg.serverNamesHashBucketSize != null) '' 175 server_names_hash_bucket_size ${toString cfg.serverNamesHashBucketSize}; 176 ''} 177 178 ${optionalString (cfg.serverNamesHashMaxSize != null) '' 179 server_names_hash_max_size ${toString cfg.serverNamesHashMaxSize}; 180 ''} 181 182 # $connection_upgrade is used for websocket proxying 183 map $http_upgrade $connection_upgrade { 184 default upgrade; 185 ''' close; 186 } 187 client_max_body_size ${cfg.clientMaxBodySize}; 188 189 server_tokens ${if cfg.serverTokens then "on" else "off"}; 190 191 ${cfg.commonHttpConfig} 192 193 ${vhosts} 194 195 ${optionalString cfg.statusPage '' 196 server { 197 listen 80; 198 ${optionalString enableIPv6 "listen [::]:80;" } 199 200 server_name localhost; 201 202 location /nginx_status { 203 stub_status on; 204 access_log off; 205 allow 127.0.0.1; 206 ${optionalString enableIPv6 "allow ::1;"} 207 deny all; 208 } 209 } 210 ''} 211 212 ${cfg.appendHttpConfig} 213 }''} 214 215 ${optionalString (cfg.httpConfig != "") '' 216 http { 217 ${commonHttpConfig} 218 ${cfg.httpConfig} 219 }''} 220 221 ${optionalString (cfg.streamConfig != "") '' 222 stream { 223 ${cfg.streamConfig} 224 } 225 ''} 226 227 ${cfg.appendConfig} 228 ''; 229 230 configPath = if cfg.enableReload 231 then "/etc/nginx/nginx.conf" 232 else configFile; 233 234 execCommand = "${cfg.package}/bin/nginx -c '${configPath}'"; 235 236 vhosts = concatStringsSep "\n" (mapAttrsToList (vhostName: vhost: 237 let 238 onlySSL = vhost.onlySSL || vhost.enableSSL; 239 hasSSL = onlySSL || vhost.addSSL || vhost.forceSSL; 240 241 defaultListen = 242 if vhost.listen != [] then vhost.listen 243 else 244 let addrs = if vhost.listenAddresses != [] then vhost.listenAddresses else ( 245 [ "0.0.0.0" ] ++ optional enableIPv6 "[::0]" 246 ); 247 in 248 optionals (hasSSL || vhost.rejectSSL) (map (addr: { inherit addr; port = 443; ssl = true; }) addrs) 249 ++ optionals (!onlySSL) (map (addr: { inherit addr; port = 80; ssl = false; }) addrs); 250 251 hostListen = 252 if vhost.forceSSL 253 then filter (x: x.ssl) defaultListen 254 else defaultListen; 255 256 listenString = { addr, port, ssl, extraParameters ? [], ... }: 257 "listen ${addr}:${toString port} " 258 + optionalString ssl "ssl " 259 + optionalString (ssl && vhost.http2) "http2 " 260 + optionalString vhost.default "default_server " 261 + optionalString (extraParameters != []) (concatStringsSep " " extraParameters) 262 + ";" 263 + (if ssl && vhost.http3 then '' 264 # UDP listener for **QUIC+HTTP/3 265 listen ${addr}:${toString port} http3 reuseport; 266 # Advertise that HTTP/3 is available 267 add_header Alt-Svc 'h3=":443"'; 268 # Sent when QUIC was used 269 add_header QUIC-Status $quic; 270 '' else ""); 271 272 redirectListen = filter (x: !x.ssl) defaultListen; 273 274 acmeLocation = optionalString (vhost.enableACME || vhost.useACMEHost != null) '' 275 location /.well-known/acme-challenge { 276 ${optionalString (vhost.acmeFallbackHost != null) "try_files $uri @acme-fallback;"} 277 root ${vhost.acmeRoot}; 278 auth_basic off; 279 } 280 ${optionalString (vhost.acmeFallbackHost != null) '' 281 location @acme-fallback { 282 auth_basic off; 283 proxy_pass http://${vhost.acmeFallbackHost}; 284 } 285 ''} 286 ''; 287 288 in '' 289 ${optionalString vhost.forceSSL '' 290 server { 291 ${concatMapStringsSep "\n" listenString redirectListen} 292 293 server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases}; 294 ${acmeLocation} 295 location / { 296 return 301 https://$host$request_uri; 297 } 298 } 299 ''} 300 301 server { 302 ${concatMapStringsSep "\n" listenString hostListen} 303 server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases}; 304 ${acmeLocation} 305 ${optionalString (vhost.root != null) "root ${vhost.root};"} 306 ${optionalString (vhost.globalRedirect != null) '' 307 return 301 http${optionalString hasSSL "s"}://${vhost.globalRedirect}$request_uri; 308 ''} 309 ${optionalString hasSSL '' 310 ssl_certificate ${vhost.sslCertificate}; 311 ssl_certificate_key ${vhost.sslCertificateKey}; 312 ''} 313 ${optionalString (hasSSL && vhost.sslTrustedCertificate != null) '' 314 ssl_trusted_certificate ${vhost.sslTrustedCertificate}; 315 ''} 316 ${optionalString vhost.rejectSSL '' 317 ssl_reject_handshake on; 318 ''} 319 320 ${mkBasicAuth vhostName vhost} 321 322 ${mkLocations vhost.locations} 323 324 ${vhost.extraConfig} 325 } 326 '' 327 ) virtualHosts); 328 mkLocations = locations: concatStringsSep "\n" (map (config: '' 329 location ${config.location} { 330 ${optionalString (config.proxyPass != null && !cfg.proxyResolveWhileRunning) 331 "proxy_pass ${config.proxyPass};" 332 } 333 ${optionalString (config.proxyPass != null && cfg.proxyResolveWhileRunning) '' 334 set $nix_proxy_target "${config.proxyPass}"; 335 proxy_pass $nix_proxy_target; 336 ''} 337 ${optionalString config.proxyWebsockets '' 338 proxy_http_version 1.1; 339 proxy_set_header Upgrade $http_upgrade; 340 proxy_set_header Connection $connection_upgrade; 341 ''} 342 ${concatStringsSep "\n" 343 (mapAttrsToList (n: v: ''fastcgi_param ${n} "${v}";'') 344 (optionalAttrs (config.fastcgiParams != {}) 345 (defaultFastcgiParams // config.fastcgiParams)))} 346 ${optionalString (config.index != null) "index ${config.index};"} 347 ${optionalString (config.tryFiles != null) "try_files ${config.tryFiles};"} 348 ${optionalString (config.root != null) "root ${config.root};"} 349 ${optionalString (config.alias != null) "alias ${config.alias};"} 350 ${optionalString (config.return != null) "return ${config.return};"} 351 ${config.extraConfig} 352 ${optionalString (config.proxyPass != null && cfg.recommendedProxySettings) "include ${recommendedProxyConfig};"} 353 ${mkBasicAuth "sublocation" config} 354 } 355 '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations))); 356 357 mkBasicAuth = name: zone: optionalString (zone.basicAuthFile != null || zone.basicAuth != {}) (let 358 auth_file = if zone.basicAuthFile != null 359 then zone.basicAuthFile 360 else mkHtpasswd name zone.basicAuth; 361 in '' 362 auth_basic secured; 363 auth_basic_user_file ${auth_file}; 364 ''); 365 mkHtpasswd = name: authDef: pkgs.writeText "${name}.htpasswd" ( 366 concatStringsSep "\n" (mapAttrsToList (user: password: '' 367 ${user}:{PLAIN}${password} 368 '') authDef) 369 ); 370in 371 372{ 373 options = { 374 services.nginx = { 375 enable = mkEnableOption "Nginx Web Server"; 376 377 statusPage = mkOption { 378 default = false; 379 type = types.bool; 380 description = " 381 Enable status page reachable from localhost on http://127.0.0.1/nginx_status. 382 "; 383 }; 384 385 recommendedTlsSettings = mkOption { 386 default = false; 387 type = types.bool; 388 description = " 389 Enable recommended TLS settings. 390 "; 391 }; 392 393 recommendedOptimisation = mkOption { 394 default = false; 395 type = types.bool; 396 description = " 397 Enable recommended optimisation settings. 398 "; 399 }; 400 401 recommendedGzipSettings = mkOption { 402 default = false; 403 type = types.bool; 404 description = " 405 Enable recommended gzip settings. 406 "; 407 }; 408 409 recommendedProxySettings = mkOption { 410 default = false; 411 type = types.bool; 412 description = " 413 Enable recommended proxy settings. 414 "; 415 }; 416 417 proxyTimeout = mkOption { 418 type = types.str; 419 default = "60s"; 420 example = "20s"; 421 description = " 422 Change the proxy related timeouts in recommendedProxySettings. 423 "; 424 }; 425 426 package = mkOption { 427 default = pkgs.nginxStable; 428 defaultText = literalExpression "pkgs.nginxStable"; 429 type = types.package; 430 apply = p: p.override { 431 modules = p.modules ++ cfg.additionalModules; 432 }; 433 description = " 434 Nginx package to use. This defaults to the stable version. Note 435 that the nginx team recommends to use the mainline version which 436 available in nixpkgs as <literal>nginxMainline</literal>. 437 "; 438 }; 439 440 additionalModules = mkOption { 441 default = []; 442 type = types.listOf (types.attrsOf types.anything); 443 example = literalExpression "[ pkgs.nginxModules.brotli ]"; 444 description = '' 445 Additional <link xlink:href="https://www.nginx.com/resources/wiki/modules/">third-party nginx modules</link> 446 to install. Packaged modules are available in 447 <literal>pkgs.nginxModules</literal>. 448 ''; 449 }; 450 451 logError = mkOption { 452 default = "stderr"; 453 type = types.str; 454 description = " 455 Configures logging. 456 The first parameter defines a file that will store the log. The 457 special value stderr selects the standard error file. Logging to 458 syslog can be configured by specifying the syslog: prefix. 459 The second parameter determines the level of logging, and can be 460 one of the following: debug, info, notice, warn, error, crit, 461 alert, or emerg. Log levels above are listed in the order of 462 increasing severity. Setting a certain log level will cause all 463 messages of the specified and more severe log levels to be logged. 464 If this parameter is omitted then error is used. 465 "; 466 }; 467 468 preStart = mkOption { 469 type = types.lines; 470 default = ""; 471 description = " 472 Shell commands executed before the service's nginx is started. 473 "; 474 }; 475 476 config = mkOption { 477 type = types.str; 478 default = ""; 479 description = '' 480 Verbatim <filename>nginx.conf</filename> configuration. 481 This is mutually exclusive to any other config option for 482 <filename>nginx.conf</filename> except for 483 <itemizedlist> 484 <listitem><para><xref linkend="opt-services.nginx.appendConfig" /> 485 </para></listitem> 486 <listitem><para><xref linkend="opt-services.nginx.httpConfig" /> 487 </para></listitem> 488 <listitem><para><xref linkend="opt-services.nginx.logError" /> 489 </para></listitem> 490 </itemizedlist> 491 492 If additional verbatim config in addition to other options is needed, 493 <xref linkend="opt-services.nginx.appendConfig" /> should be used instead. 494 ''; 495 }; 496 497 appendConfig = mkOption { 498 type = types.lines; 499 default = ""; 500 description = '' 501 Configuration lines appended to the generated Nginx 502 configuration file. Commonly used by different modules 503 providing http snippets. <option>appendConfig</option> 504 can be specified more than once and it's value will be 505 concatenated (contrary to <option>config</option> which 506 can be set only once). 507 ''; 508 }; 509 510 commonHttpConfig = mkOption { 511 type = types.lines; 512 default = ""; 513 example = '' 514 resolver 127.0.0.1 valid=5s; 515 516 log_format myformat '$remote_addr - $remote_user [$time_local] ' 517 '"$request" $status $body_bytes_sent ' 518 '"$http_referer" "$http_user_agent"'; 519 ''; 520 description = '' 521 With nginx you must provide common http context definitions before 522 they are used, e.g. log_format, resolver, etc. inside of server 523 or location contexts. Use this attribute to set these definitions 524 at the appropriate location. 525 ''; 526 }; 527 528 httpConfig = mkOption { 529 type = types.lines; 530 default = ""; 531 description = " 532 Configuration lines to be set inside the http block. 533 This is mutually exclusive with the structured configuration 534 via virtualHosts and the recommendedXyzSettings configuration 535 options. See appendHttpConfig for appending to the generated http block. 536 "; 537 }; 538 539 streamConfig = mkOption { 540 type = types.lines; 541 default = ""; 542 example = '' 543 server { 544 listen 127.0.0.1:53 udp reuseport; 545 proxy_timeout 20s; 546 proxy_pass 192.168.0.1:53535; 547 } 548 ''; 549 description = " 550 Configuration lines to be set inside the stream block. 551 "; 552 }; 553 554 eventsConfig = mkOption { 555 type = types.lines; 556 default = ""; 557 description = '' 558 Configuration lines to be set inside the events block. 559 ''; 560 }; 561 562 appendHttpConfig = mkOption { 563 type = types.lines; 564 default = ""; 565 description = " 566 Configuration lines to be appended to the generated http block. 567 This is mutually exclusive with using config and httpConfig for 568 specifying the whole http block verbatim. 569 "; 570 }; 571 572 enableReload = mkOption { 573 default = false; 574 type = types.bool; 575 description = '' 576 Reload nginx when configuration file changes (instead of restart). 577 The configuration file is exposed at <filename>/etc/nginx/nginx.conf</filename>. 578 See also <literal>systemd.services.*.restartIfChanged</literal>. 579 ''; 580 }; 581 582 user = mkOption { 583 type = types.str; 584 default = "nginx"; 585 description = "User account under which nginx runs."; 586 }; 587 588 group = mkOption { 589 type = types.str; 590 default = "nginx"; 591 description = "Group account under which nginx runs."; 592 }; 593 594 serverTokens = mkOption { 595 type = types.bool; 596 default = false; 597 description = "Show nginx version in headers and error pages."; 598 }; 599 600 clientMaxBodySize = mkOption { 601 type = types.str; 602 default = "10m"; 603 description = "Set nginx global client_max_body_size."; 604 }; 605 606 sslCiphers = mkOption { 607 type = types.nullOr types.str; 608 # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate 609 default = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; 610 description = "Ciphers to choose from when negotiating TLS handshakes."; 611 }; 612 613 sslProtocols = mkOption { 614 type = types.str; 615 default = "TLSv1.2 TLSv1.3"; 616 example = "TLSv1 TLSv1.1 TLSv1.2 TLSv1.3"; 617 description = "Allowed TLS protocol versions."; 618 }; 619 620 sslDhparam = mkOption { 621 type = types.nullOr types.path; 622 default = null; 623 example = "/path/to/dhparams.pem"; 624 description = "Path to DH parameters file."; 625 }; 626 627 proxyResolveWhileRunning = mkOption { 628 type = types.bool; 629 default = false; 630 description = '' 631 Resolves domains of proxyPass targets at runtime 632 and not only at start, you have to set 633 services.nginx.resolver, too. 634 ''; 635 }; 636 637 mapHashBucketSize = mkOption { 638 type = types.nullOr (types.enum [ 32 64 128 ]); 639 default = null; 640 description = '' 641 Sets the bucket size for the map variables hash tables. Default 642 value depends on the processors cache line size. 643 ''; 644 }; 645 646 mapHashMaxSize = mkOption { 647 type = types.nullOr types.ints.positive; 648 default = null; 649 description = '' 650 Sets the maximum size of the map variables hash tables. 651 ''; 652 }; 653 654 serverNamesHashBucketSize = mkOption { 655 type = types.nullOr types.ints.positive; 656 default = null; 657 description = '' 658 Sets the bucket size for the server names hash tables. Default 659 value depends on the processors cache line size. 660 ''; 661 }; 662 663 serverNamesHashMaxSize = mkOption { 664 type = types.nullOr types.ints.positive; 665 default = null; 666 description = '' 667 Sets the maximum size of the server names hash tables. 668 ''; 669 }; 670 671 resolver = mkOption { 672 type = types.submodule { 673 options = { 674 addresses = mkOption { 675 type = types.listOf types.str; 676 default = []; 677 example = literalExpression ''[ "[::1]" "127.0.0.1:5353" ]''; 678 description = "List of resolvers to use"; 679 }; 680 valid = mkOption { 681 type = types.str; 682 default = ""; 683 example = "30s"; 684 description = '' 685 By default, nginx caches answers using the TTL value of a response. 686 An optional valid parameter allows overriding it 687 ''; 688 }; 689 ipv6 = mkOption { 690 type = types.bool; 691 default = true; 692 description = '' 693 By default, nginx will look up both IPv4 and IPv6 addresses while resolving. 694 If looking up of IPv6 addresses is not desired, the ipv6=off parameter can be 695 specified. 696 ''; 697 }; 698 }; 699 }; 700 description = '' 701 Configures name servers used to resolve names of upstream servers into addresses 702 ''; 703 default = {}; 704 }; 705 706 upstreams = mkOption { 707 type = types.attrsOf (types.submodule { 708 options = { 709 servers = mkOption { 710 type = types.attrsOf (types.submodule { 711 options = { 712 backup = mkOption { 713 type = types.bool; 714 default = false; 715 description = '' 716 Marks the server as a backup server. It will be passed 717 requests when the primary servers are unavailable. 718 ''; 719 }; 720 }; 721 }); 722 description = '' 723 Defines the address and other parameters of the upstream servers. 724 ''; 725 default = {}; 726 example = { "127.0.0.1:8000" = {}; }; 727 }; 728 extraConfig = mkOption { 729 type = types.lines; 730 default = ""; 731 description = '' 732 These lines go to the end of the upstream verbatim. 733 ''; 734 }; 735 }; 736 }); 737 description = '' 738 Defines a group of servers to use as proxy target. 739 ''; 740 default = {}; 741 example = literalExpression '' 742 "backend_server" = { 743 servers = { "127.0.0.1:8000" = {}; }; 744 extraConfig = '''' 745 keepalive 16; 746 ''''; 747 }; 748 ''; 749 }; 750 751 virtualHosts = mkOption { 752 type = types.attrsOf (types.submodule (import ./vhost-options.nix { 753 inherit config lib; 754 })); 755 default = { 756 localhost = {}; 757 }; 758 example = literalExpression '' 759 { 760 "hydra.example.com" = { 761 forceSSL = true; 762 enableACME = true; 763 locations."/" = { 764 proxyPass = "http://localhost:3000"; 765 }; 766 }; 767 }; 768 ''; 769 description = "Declarative vhost config"; 770 }; 771 }; 772 }; 773 774 imports = [ 775 (mkRemovedOptionModule [ "services" "nginx" "stateDir" ] '' 776 The Nginx log directory has been moved to /var/log/nginx, the cache directory 777 to /var/cache/nginx. The option services.nginx.stateDir has been removed. 778 '') 779 ]; 780 781 config = mkIf cfg.enable { 782 # TODO: test user supplied config file pases syntax test 783 784 warnings = 785 let 786 deprecatedSSL = name: config: optional config.enableSSL 787 '' 788 config.services.nginx.virtualHosts.<name>.enableSSL is deprecated, 789 use config.services.nginx.virtualHosts.<name>.onlySSL instead. 790 ''; 791 792 in flatten (mapAttrsToList deprecatedSSL virtualHosts); 793 794 assertions = 795 let 796 hostOrAliasIsNull = l: l.root == null || l.alias == null; 797 in [ 798 { 799 assertion = all (host: all hostOrAliasIsNull (attrValues host.locations)) (attrValues virtualHosts); 800 message = "Only one of nginx root or alias can be specified on a location."; 801 } 802 803 { 804 assertion = all (host: with host; 805 count id [ addSSL (onlySSL || enableSSL) forceSSL rejectSSL ] <= 1 806 ) (attrValues virtualHosts); 807 message = '' 808 Options services.nginx.service.virtualHosts.<name>.addSSL, 809 services.nginx.virtualHosts.<name>.onlySSL, 810 services.nginx.virtualHosts.<name>.forceSSL and 811 services.nginx.virtualHosts.<name>.rejectSSL are mutually exclusive. 812 ''; 813 } 814 815 { 816 assertion = any (host: host.rejectSSL) (attrValues virtualHosts) -> versionAtLeast cfg.package.version "1.19.4"; 817 message = '' 818 services.nginx.virtualHosts.<name>.rejectSSL requires nginx version 819 1.19.4 or above; see the documentation for services.nginx.package. 820 ''; 821 } 822 823 { 824 assertion = all (host: !(host.enableACME && host.useACMEHost != null)) (attrValues virtualHosts); 825 message = '' 826 Options services.nginx.service.virtualHosts.<name>.enableACME and 827 services.nginx.virtualHosts.<name>.useACMEHost are mutually exclusive. 828 ''; 829 } 830 ]; 831 832 systemd.services.nginx = { 833 description = "Nginx Web Server"; 834 wantedBy = [ "multi-user.target" ]; 835 wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) dependentCertNames); 836 after = [ "network.target" ] ++ map (certName: "acme-selfsigned-${certName}.service") dependentCertNames; 837 # Nginx needs to be started in order to be able to request certificates 838 # (it's hosting the acme challenge after all) 839 # This fixes https://github.com/NixOS/nixpkgs/issues/81842 840 before = map (certName: "acme-${certName}.service") dependentCertNames; 841 stopIfChanged = false; 842 preStart = '' 843 ${cfg.preStart} 844 ${execCommand} -t 845 ''; 846 847 startLimitIntervalSec = 60; 848 serviceConfig = { 849 ExecStart = execCommand; 850 ExecReload = [ 851 "${execCommand} -t" 852 "${pkgs.coreutils}/bin/kill -HUP $MAINPID" 853 ]; 854 Restart = "always"; 855 RestartSec = "10s"; 856 # User and group 857 User = cfg.user; 858 Group = cfg.group; 859 # Runtime directory and mode 860 RuntimeDirectory = "nginx"; 861 RuntimeDirectoryMode = "0750"; 862 # Cache directory and mode 863 CacheDirectory = "nginx"; 864 CacheDirectoryMode = "0750"; 865 # Logs directory and mode 866 LogsDirectory = "nginx"; 867 LogsDirectoryMode = "0750"; 868 # Proc filesystem 869 ProcSubset = "pid"; 870 ProtectProc = "invisible"; 871 # New file permissions 872 UMask = "0027"; # 0640 / 0750 873 # Capabilities 874 AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ]; 875 CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ]; 876 # Security 877 NoNewPrivileges = true; 878 # Sandboxing (sorted by occurrence in https://www.freedesktop.org/software/systemd/man/systemd.exec.html) 879 ProtectSystem = "strict"; 880 ProtectHome = mkDefault true; 881 PrivateTmp = true; 882 PrivateDevices = true; 883 ProtectHostname = true; 884 ProtectClock = true; 885 ProtectKernelTunables = true; 886 ProtectKernelModules = true; 887 ProtectKernelLogs = true; 888 ProtectControlGroups = true; 889 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; 890 RestrictNamespaces = true; 891 LockPersonality = true; 892 MemoryDenyWriteExecute = !((builtins.any (mod: (mod.allowMemoryWriteExecute or false)) cfg.package.modules) || (cfg.package == pkgs.openresty)); 893 RestrictRealtime = true; 894 RestrictSUIDSGID = true; 895 RemoveIPC = true; 896 PrivateMounts = true; 897 # System Call Filtering 898 SystemCallArchitectures = "native"; 899 SystemCallFilter = "~@cpu-emulation @debug @keyring @ipc @mount @obsolete @privileged @setuid @mincore"; 900 }; 901 }; 902 903 environment.etc."nginx/nginx.conf" = mkIf cfg.enableReload { 904 source = configFile; 905 }; 906 907 # This service waits for all certificates to be available 908 # before reloading nginx configuration. 909 # sslTargets are added to wantedBy + before 910 # which allows the acme-finished-$cert.target to signify the successful updating 911 # of certs end-to-end. 912 systemd.services.nginx-config-reload = let 913 sslServices = map (certName: "acme-${certName}.service") dependentCertNames; 914 sslTargets = map (certName: "acme-finished-${certName}.target") dependentCertNames; 915 in mkIf (cfg.enableReload || sslServices != []) { 916 wants = optionals (cfg.enableReload) [ "nginx.service" ]; 917 wantedBy = sslServices ++ [ "multi-user.target" ]; 918 # Before the finished targets, after the renew services. 919 # This service might be needed for HTTP-01 challenges, but we only want to confirm 920 # certs are updated _after_ config has been reloaded. 921 before = sslTargets; 922 after = sslServices; 923 restartTriggers = optionals (cfg.enableReload) [ configFile ]; 924 # Block reloading if not all certs exist yet. 925 # Happens when config changes add new vhosts/certs. 926 unitConfig.ConditionPathExists = optionals (sslServices != []) (map (certName: certs.${certName}.directory + "/fullchain.pem") dependentCertNames); 927 serviceConfig = { 928 Type = "oneshot"; 929 TimeoutSec = 60; 930 ExecCondition = "/run/current-system/systemd/bin/systemctl -q is-active nginx.service"; 931 ExecStart = "/run/current-system/systemd/bin/systemctl reload nginx.service"; 932 }; 933 }; 934 935 security.acme.certs = let 936 acmePairs = map (vhostConfig: nameValuePair vhostConfig.serverName { 937 group = mkDefault cfg.group; 938 webroot = vhostConfig.acmeRoot; 939 extraDomainNames = vhostConfig.serverAliases; 940 # Filter for enableACME-only vhosts. Don't want to create dud certs 941 }) (filter (vhostConfig: vhostConfig.useACMEHost == null) acmeEnabledVhosts); 942 in listToAttrs acmePairs; 943 944 users.users = optionalAttrs (cfg.user == "nginx") { 945 nginx = { 946 group = cfg.group; 947 isSystemUser = true; 948 uid = config.ids.uids.nginx; 949 }; 950 }; 951 952 users.groups = optionalAttrs (cfg.group == "nginx") { 953 nginx.gid = config.ids.gids.nginx; 954 }; 955 956 }; 957}