at master 60 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8with lib; 9 10let 11 cfg = config.services.nginx; 12 inherit (config.security.acme) certs; 13 vhostsConfigs = mapAttrsToList (vhostName: vhostConfig: vhostConfig) virtualHosts; 14 acmeEnabledVhosts = filter ( 15 vhostConfig: vhostConfig.enableACME || vhostConfig.useACMEHost != null 16 ) vhostsConfigs; 17 vhostCertNames = unique (map (hostOpts: hostOpts.certName) acmeEnabledVhosts); 18 virtualHosts = mapAttrs ( 19 vhostName: vhostConfig: 20 let 21 serverName = if vhostConfig.serverName != null then vhostConfig.serverName else vhostName; 22 certName = if vhostConfig.useACMEHost != null then vhostConfig.useACMEHost else serverName; 23 in 24 vhostConfig 25 // { 26 inherit serverName certName; 27 } 28 // (optionalAttrs (vhostConfig.enableACME || vhostConfig.useACMEHost != null) { 29 sslCertificate = "${certs.${certName}.directory}/fullchain.pem"; 30 sslCertificateKey = "${certs.${certName}.directory}/key.pem"; 31 sslTrustedCertificate = 32 if vhostConfig.sslTrustedCertificate != null then 33 vhostConfig.sslTrustedCertificate 34 else 35 "${certs.${certName}.directory}/chain.pem"; 36 }) 37 ) cfg.virtualHosts; 38 inherit (config.networking) enableIPv6; 39 40 # Mime.types values are taken from brotli sample configuration - https://github.com/google/ngx_brotli 41 # and Nginx Server Configs - https://github.com/h5bp/server-configs-nginx 42 # "text/html" is implicitly included in {brotli,gzip,zstd}_types 43 compressMimeTypes = [ 44 "application/atom+xml" 45 "application/geo+json" 46 "application/javascript" # Deprecated by IETF RFC 9239, but still widely used 47 "application/json" 48 "application/ld+json" 49 "application/manifest+json" 50 "application/rdf+xml" 51 "application/vnd.ms-fontobject" 52 "application/wasm" 53 "application/x-rss+xml" 54 "application/x-web-app-manifest+json" 55 "application/xhtml+xml" 56 "application/xliff+xml" 57 "application/xml" 58 "font/collection" 59 "font/otf" 60 "font/ttf" 61 "image/bmp" 62 "image/svg+xml" 63 "image/vnd.microsoft.icon" 64 "text/cache-manifest" 65 "text/calendar" 66 "text/css" 67 "text/csv" 68 "text/javascript" 69 "text/markdown" 70 "text/plain" 71 "text/vcard" 72 "text/vnd.rim.location.xloc" 73 "text/vtt" 74 "text/x-component" 75 "text/xml" 76 ]; 77 78 defaultFastcgiParams = { 79 SCRIPT_FILENAME = "$document_root$fastcgi_script_name"; 80 QUERY_STRING = "$query_string"; 81 REQUEST_METHOD = "$request_method"; 82 CONTENT_TYPE = "$content_type"; 83 CONTENT_LENGTH = "$content_length"; 84 85 SCRIPT_NAME = "$fastcgi_script_name"; 86 REQUEST_URI = "$request_uri"; 87 DOCUMENT_URI = "$document_uri"; 88 DOCUMENT_ROOT = "$document_root"; 89 SERVER_PROTOCOL = "$server_protocol"; 90 REQUEST_SCHEME = "$scheme"; 91 HTTPS = "$https if_not_empty"; 92 93 GATEWAY_INTERFACE = "CGI/1.1"; 94 SERVER_SOFTWARE = "nginx/$nginx_version"; 95 96 REMOTE_ADDR = "$remote_addr"; 97 REMOTE_PORT = "$remote_port"; 98 SERVER_ADDR = "$server_addr"; 99 SERVER_PORT = "$server_port"; 100 SERVER_NAME = "$server_name"; 101 102 REDIRECT_STATUS = "200"; 103 }; 104 105 recommendedProxyConfig = pkgs.writeText "nginx-recommended-proxy_set_header-headers.conf" '' 106 proxy_set_header Host $host; 107 proxy_set_header X-Real-IP $remote_addr; 108 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 109 proxy_set_header X-Forwarded-Proto $scheme; 110 proxy_set_header X-Forwarded-Host $host; 111 proxy_set_header X-Forwarded-Server $hostname; 112 ''; 113 114 proxyCachePathConfig = concatStringsSep "\n" ( 115 mapAttrsToList (name: proxyCachePath: '' 116 proxy_cache_path ${ 117 concatStringsSep " " [ 118 "/var/cache/nginx/${name}" 119 "keys_zone=${proxyCachePath.keysZoneName}:${proxyCachePath.keysZoneSize}" 120 "levels=${proxyCachePath.levels}" 121 "use_temp_path=${if proxyCachePath.useTempPath then "on" else "off"}" 122 "inactive=${proxyCachePath.inactive}" 123 "max_size=${proxyCachePath.maxSize}" 124 ] 125 }; 126 '') (filterAttrs (name: conf: conf.enable) cfg.proxyCachePath) 127 ); 128 129 toUpstreamParameter = 130 key: value: 131 if builtins.isBool value then lib.optionalString value key else "${key}=${toString value}"; 132 133 upstreamConfig = toString ( 134 flip mapAttrsToList cfg.upstreams ( 135 name: upstream: '' 136 upstream ${name} { 137 ${toString ( 138 flip mapAttrsToList upstream.servers ( 139 name: server: '' 140 server ${name} ${concatStringsSep " " (mapAttrsToList toUpstreamParameter server)}; 141 '' 142 ) 143 )} 144 ${upstream.extraConfig} 145 } 146 '' 147 ) 148 ); 149 150 commonHttpConfig = '' 151 # Load mime types and configure maximum size of the types hash tables. 152 include ${cfg.defaultMimeTypes}; 153 types_hash_max_size ${toString cfg.typesHashMaxSize}; 154 155 include ${cfg.package}/conf/fastcgi.conf; 156 include ${cfg.package}/conf/uwsgi_params; 157 158 default_type application/octet-stream; 159 ''; 160 161 configFile = 162 (if cfg.validateConfigFile then pkgs.writers.writeNginxConfig else pkgs.writeText) "nginx.conf" 163 '' 164 ${cfg.prependConfig} 165 166 pid /run/nginx/nginx.pid; 167 error_log ${cfg.logError}; 168 daemon off; 169 170 ${optionalString cfg.enableQuicBPF '' 171 quic_bpf on; 172 ''} 173 174 ${cfg.config} 175 176 ${optionalString (cfg.eventsConfig != "" || cfg.config == "") '' 177 events { 178 ${cfg.eventsConfig} 179 } 180 ''} 181 182 ${optionalString (cfg.httpConfig == "" && cfg.config == "") '' 183 http { 184 ${commonHttpConfig} 185 186 ${optionalString (cfg.resolver.addresses != [ ]) '' 187 resolver ${toString cfg.resolver.addresses} ${ 188 optionalString (cfg.resolver.valid != "") "valid=${cfg.resolver.valid}" 189 } ${optionalString (!cfg.resolver.ipv4) "ipv4=off"} ${ 190 optionalString (!cfg.resolver.ipv6) "ipv6=off" 191 }; 192 ''} 193 ${upstreamConfig} 194 195 ${optionalString cfg.recommendedOptimisation '' 196 # optimisation 197 sendfile on; 198 tcp_nopush on; 199 tcp_nodelay on; 200 keepalive_timeout 65; 201 ''} 202 203 ssl_protocols ${cfg.sslProtocols}; 204 ${optionalString (cfg.sslCiphers != null) "ssl_ciphers ${cfg.sslCiphers};"} 205 ${optionalString (cfg.sslDhparam != null) "ssl_dhparam ${cfg.sslDhparam};"} 206 207 ${optionalString cfg.recommendedTlsSettings '' 208 # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate 209 210 ssl_ecdh_curve X25519:prime256v1:secp384r1; 211 ssl_session_timeout 1d; 212 ssl_session_cache shared:SSL:10m; 213 # Breaks forward secrecy: https://github.com/mozilla/server-side-tls/issues/135 214 ssl_session_tickets off; 215 # We don't enable insecure ciphers by default, so this allows 216 # clients to pick the most performant, per https://github.com/mozilla/server-side-tls/issues/260 217 ssl_prefer_server_ciphers off; 218 ''} 219 220 ${optionalString cfg.recommendedBrotliSettings '' 221 brotli on; 222 brotli_static on; 223 brotli_comp_level 5; 224 brotli_window 512k; 225 brotli_min_length 256; 226 brotli_types ${lib.concatStringsSep " " compressMimeTypes}; 227 ''} 228 229 ${optionalString cfg.recommendedGzipSettings 230 # https://docs.nginx.com/nginx/admin-guide/web-server/compression/ 231 '' 232 gzip on; 233 gzip_static on; 234 gzip_vary on; 235 gzip_comp_level 5; 236 gzip_min_length 256; 237 gzip_proxied expired no-cache no-store private auth; 238 gzip_types ${lib.concatStringsSep " " compressMimeTypes}; 239 '' 240 } 241 242 ${optionalString cfg.experimentalZstdSettings '' 243 zstd on; 244 zstd_comp_level 9; 245 zstd_min_length 256; 246 zstd_static on; 247 zstd_types ${lib.concatStringsSep " " compressMimeTypes}; 248 ''} 249 250 ${optionalString cfg.recommendedProxySettings '' 251 proxy_redirect off; 252 proxy_connect_timeout ${cfg.proxyTimeout}; 253 proxy_send_timeout ${cfg.proxyTimeout}; 254 proxy_read_timeout ${cfg.proxyTimeout}; 255 proxy_http_version 1.1; 256 # don't let clients close the keep-alive connection to upstream. See the nginx blog for details: 257 # https://www.nginx.com/blog/avoiding-top-10-nginx-configuration-mistakes/#no-keepalives 258 proxy_set_header "Connection" ""; 259 include ${recommendedProxyConfig}; 260 ''} 261 262 ${optionalString cfg.recommendedUwsgiSettings '' 263 uwsgi_connect_timeout ${cfg.uwsgiTimeout}; 264 uwsgi_send_timeout ${cfg.uwsgiTimeout}; 265 uwsgi_read_timeout ${cfg.uwsgiTimeout}; 266 uwsgi_param HTTP_CONNECTION ""; 267 include ${cfg.package}/conf/uwsgi_params; 268 ''} 269 270 ${optionalString (cfg.mapHashBucketSize != null) '' 271 map_hash_bucket_size ${toString cfg.mapHashBucketSize}; 272 ''} 273 274 ${optionalString (cfg.mapHashMaxSize != null) '' 275 map_hash_max_size ${toString cfg.mapHashMaxSize}; 276 ''} 277 278 ${optionalString (cfg.serverNamesHashBucketSize != null) '' 279 server_names_hash_bucket_size ${toString cfg.serverNamesHashBucketSize}; 280 ''} 281 282 ${optionalString (cfg.serverNamesHashMaxSize != null) '' 283 server_names_hash_max_size ${toString cfg.serverNamesHashMaxSize}; 284 ''} 285 286 # $connection_upgrade is used for websocket proxying 287 map $http_upgrade $connection_upgrade { 288 default upgrade; 289 ''' close; 290 } 291 client_max_body_size ${cfg.clientMaxBodySize}; 292 293 server_tokens ${if cfg.serverTokens then "on" else "off"}; 294 295 ${cfg.commonHttpConfig} 296 297 ${proxyCachePathConfig} 298 299 ${vhosts} 300 301 ${cfg.appendHttpConfig} 302 }''} 303 304 ${optionalString (cfg.httpConfig != "") '' 305 http { 306 ${commonHttpConfig} 307 ${cfg.httpConfig} 308 }''} 309 310 ${optionalString (cfg.streamConfig != "") '' 311 stream { 312 ${cfg.streamConfig} 313 } 314 ''} 315 316 ${cfg.appendConfig} 317 ''; 318 319 configPath = if cfg.enableReload then "/etc/nginx/nginx.conf" else configFile; 320 321 execCommand = "${cfg.package}/bin/nginx -c '${configPath}'"; 322 323 vhosts = concatStringsSep "\n" ( 324 mapAttrsToList ( 325 vhostName: vhost: 326 let 327 onlySSL = vhost.onlySSL || vhost.enableSSL; 328 hasSSL = onlySSL || vhost.addSSL || vhost.forceSSL; 329 330 # First evaluation of defaultListen based on a set of listen lines. 331 mkDefaultListenVhost = 332 listenLines: 333 # If this vhost has SSL or is a SSL rejection host. 334 # We enable a TLS variant for lines without explicit ssl or ssl = true. 335 optionals (hasSSL || vhost.rejectSSL) ( 336 map ( 337 listen: 338 { 339 port = if (hasPrefix "unix:" listen.addr) then null else cfg.defaultSSLListenPort; 340 ssl = true; 341 } 342 // listen 343 ) (filter (listen: !(listen ? ssl) || listen.ssl) listenLines) 344 ) 345 # If this vhost is supposed to serve HTTP 346 # We provide listen lines for those without explicit ssl or ssl = false. 347 ++ optionals (!onlySSL) ( 348 map ( 349 listen: 350 { 351 port = if (hasPrefix "unix:" listen.addr) then null else cfg.defaultHTTPListenPort; 352 ssl = false; 353 } 354 // listen 355 ) (filter (listen: !(listen ? ssl) || !listen.ssl) listenLines) 356 ); 357 358 defaultListen = 359 if vhost.listen != [ ] then 360 vhost.listen 361 else if cfg.defaultListen != [ ] then 362 mkDefaultListenVhost 363 # Cleanup nulls which will mess up with //. 364 # TODO: is there a better way to achieve this? i.e. mergeButIgnoreNullPlease? 365 (map (listenLine: filterAttrs (_: v: (v != null)) listenLine) cfg.defaultListen) 366 else 367 let 368 addrs = if vhost.listenAddresses != [ ] then vhost.listenAddresses else cfg.defaultListenAddresses; 369 in 370 mkDefaultListenVhost (map (addr: { inherit addr; }) addrs); 371 372 hostListen = if vhost.forceSSL then filter (x: x.ssl) defaultListen else defaultListen; 373 374 listenString = 375 { 376 addr, 377 port, 378 ssl, 379 proxyProtocol ? false, 380 extraParameters ? [ ], 381 ... 382 }: 383 # UDP listener for QUIC transport protocol. 384 (optionalString (ssl && vhost.quic) ( 385 " 386 listen ${addr}${optionalString (port != null) ":${toString port}"} quic " 387 + optionalString vhost.default "default_server " 388 + optionalString vhost.reuseport "reuseport " 389 + optionalString (extraParameters != [ ]) ( 390 concatStringsSep " " ( 391 let 392 inCompatibleParameters = [ 393 "accept_filter" 394 "backlog" 395 "deferred" 396 "fastopen" 397 "http2" 398 "proxy_protocol" 399 "so_keepalive" 400 "ssl" 401 ]; 402 isCompatibleParameter = param: !(any (p: lib.hasPrefix p param) inCompatibleParameters); 403 in 404 filter isCompatibleParameter extraParameters 405 ) 406 ) 407 + ";" 408 )) 409 + " 410 listen ${addr}${optionalString (port != null) ":${toString port}"} " 411 + optionalString (ssl && vhost.http2 && oldHTTP2) "http2 " 412 + optionalString ssl "ssl " 413 + optionalString vhost.default "default_server " 414 + optionalString vhost.reuseport "reuseport " 415 + optionalString proxyProtocol "proxy_protocol " 416 + optionalString (extraParameters != [ ]) (concatStringsSep " " extraParameters) 417 + ";"; 418 419 redirectListen = filter (x: !x.ssl) defaultListen; 420 421 # The acme-challenge location doesn't need to be added if we are not using any automated 422 # certificate provisioning and can also be omitted when we use a certificate obtained via a DNS-01 challenge 423 acmeName = if vhost.useACMEHost != null then vhost.useACMEHost else vhost.serverName; 424 acmeLocation = 425 optionalString 426 ( 427 (vhost.enableACME || vhost.useACMEHost != null) 428 && config.security.acme.certs.${acmeName}.dnsProvider == null 429 ) 430 # Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx) 431 # We use ^~ here, so that we don't check any regexes (which could 432 # otherwise easily override this intended match accidentally). 433 '' 434 location ^~ /.well-known/acme-challenge/ { 435 ${optionalString (vhost.acmeFallbackHost != null) "try_files $uri @acme-fallback;"} 436 ${optionalString (vhost.acmeRoot != null) "root ${vhost.acmeRoot};"} 437 auth_basic off; 438 auth_request off; 439 } 440 ${optionalString (vhost.acmeFallbackHost != null) '' 441 location @acme-fallback { 442 auth_basic off; 443 auth_request off; 444 proxy_pass http://${vhost.acmeFallbackHost}; 445 proxy_set_header Host $host; 446 } 447 ''} 448 ''; 449 450 in 451 '' 452 ${optionalString vhost.forceSSL '' 453 server { 454 ${concatMapStringsSep "\n" listenString redirectListen} 455 456 server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases}; 457 458 location / { 459 return ${toString vhost.redirectCode} https://$host$request_uri; 460 } 461 ${acmeLocation} 462 } 463 ''} 464 465 server { 466 ${concatMapStringsSep "\n" listenString hostListen} 467 server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases}; 468 ${optionalString (hasSSL && vhost.http2 && !oldHTTP2) '' 469 http2 on; 470 ''} 471 ${optionalString (hasSSL && vhost.quic) '' 472 http3 ${if vhost.http3 then "on" else "off"}; 473 http3_hq ${if vhost.http3_hq then "on" else "off"}; 474 ''} 475 ${optionalString hasSSL '' 476 ssl_certificate ${vhost.sslCertificate}; 477 ssl_certificate_key ${vhost.sslCertificateKey}; 478 ''} 479 ${optionalString (hasSSL && vhost.sslTrustedCertificate != null) '' 480 ssl_trusted_certificate ${vhost.sslTrustedCertificate}; 481 ''} 482 ${optionalString vhost.rejectSSL '' 483 ssl_reject_handshake on; 484 ''} 485 ${optionalString (hasSSL && vhost.kTLS) '' 486 ssl_conf_command Options KTLS; 487 ''} 488 489 ${mkBasicAuth vhostName vhost} 490 491 ${optionalString (vhost.root != null) "root ${vhost.root};"} 492 493 ${optionalString (vhost.globalRedirect != null) '' 494 location / { 495 return ${toString vhost.redirectCode} http${optionalString hasSSL "s"}://${vhost.globalRedirect}$request_uri; 496 } 497 ''} 498 ${acmeLocation} 499 ${mkLocations vhost.locations} 500 501 ${vhost.extraConfig} 502 } 503 '' 504 ) virtualHosts 505 ); 506 mkLocations = 507 locations: 508 concatStringsSep "\n" ( 509 map (config: '' 510 location ${config.location} { 511 ${optionalString ( 512 config.proxyPass != null && !cfg.proxyResolveWhileRunning 513 ) "proxy_pass ${config.proxyPass};"} 514 ${optionalString (config.proxyPass != null && cfg.proxyResolveWhileRunning) '' 515 set $nix_proxy_target "${config.proxyPass}"; 516 proxy_pass $nix_proxy_target; 517 ''} 518 ${optionalString config.proxyWebsockets '' 519 proxy_http_version 1.1; 520 proxy_set_header Upgrade $http_upgrade; 521 proxy_set_header Connection $connection_upgrade; 522 ''} 523 ${optionalString ( 524 config.uwsgiPass != null && !cfg.uwsgiResolveWhileRunning 525 ) "uwsgi_pass ${config.uwsgiPass};"} 526 ${optionalString (config.uwsgiPass != null && cfg.uwsgiResolveWhileRunning) '' 527 set $nix_proxy_target "${config.uwsgiPass}"; 528 uwsgi_pass $nix_proxy_target; 529 ''} 530 ${concatStringsSep "\n" ( 531 mapAttrsToList (n: v: ''fastcgi_param ${n} "${v}";'') ( 532 optionalAttrs (config.fastcgiParams != { }) (defaultFastcgiParams // config.fastcgiParams) 533 ) 534 )} 535 ${optionalString (config.index != null) "index ${config.index};"} 536 ${optionalString (config.tryFiles != null) "try_files ${config.tryFiles};"} 537 ${optionalString (config.root != null) "root ${config.root};"} 538 ${optionalString (config.alias != null) "alias ${config.alias};"} 539 ${optionalString (config.return != null) "return ${toString config.return};"} 540 ${config.extraConfig} 541 ${optionalString ( 542 config.proxyPass != null && config.recommendedProxySettings 543 ) "include ${recommendedProxyConfig};"} 544 ${optionalString ( 545 config.uwsgiPass != null && config.recommendedUwsgiSettings 546 ) "include ${cfg.package}/conf/uwsgi_params;"} 547 ${mkBasicAuth "sublocation" config} 548 } 549 '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)) 550 ); 551 552 mkBasicAuth = 553 name: zone: 554 optionalString (zone.basicAuthFile != null || zone.basicAuth != { }) ( 555 let 556 auth_file = 557 if zone.basicAuthFile != null then zone.basicAuthFile else mkHtpasswd name zone.basicAuth; 558 in 559 '' 560 auth_basic secured; 561 auth_basic_user_file ${auth_file}; 562 '' 563 ); 564 mkHtpasswd = 565 name: authDef: 566 pkgs.writeText "${name}.htpasswd" ( 567 concatStringsSep "\n" ( 568 mapAttrsToList (user: password: '' 569 ${user}:{PLAIN}${password} 570 '') authDef 571 ) 572 ); 573 574 mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix lib; 575 576 oldHTTP2 = ( 577 versionOlder cfg.package.version "1.25.1" 578 && !(cfg.package.pname == "angie" || cfg.package.pname == "angieQuic") 579 ); 580in 581 582{ 583 options = { 584 services.nginx = { 585 enable = mkEnableOption "Nginx Web Server"; 586 587 statusPage = mkOption { 588 default = false; 589 type = types.bool; 590 description = '' 591 Enable status page reachable from localhost on http://127.0.0.1/nginx_status. 592 ''; 593 }; 594 595 recommendedTlsSettings = mkOption { 596 default = false; 597 type = types.bool; 598 description = '' 599 Enable recommended TLS settings. 600 ''; 601 }; 602 603 recommendedOptimisation = mkOption { 604 default = false; 605 type = types.bool; 606 description = '' 607 Enable recommended optimisation settings. 608 ''; 609 }; 610 611 recommendedBrotliSettings = mkOption { 612 default = false; 613 type = types.bool; 614 description = '' 615 Enable recommended brotli settings. 616 Learn more about compression in Brotli format [here](https://github.com/google/ngx_brotli/). 617 618 This adds `pkgs.nginxModules.brotli` to `services.nginx.additionalModules`. 619 ''; 620 }; 621 622 recommendedGzipSettings = mkOption { 623 default = false; 624 type = types.bool; 625 description = '' 626 Enable recommended gzip settings. 627 Learn more about compression in Gzip format [here](https://docs.nginx.com/nginx/admin-guide/web-server/compression/). 628 ''; 629 }; 630 631 experimentalZstdSettings = mkOption { 632 default = false; 633 type = types.bool; 634 description = '' 635 Enable alpha quality zstd module with recommended settings. 636 Learn more about compression in Zstd format [here](https://github.com/tokers/zstd-nginx-module). 637 638 This adds `pkgs.nginxModules.zstd` to `services.nginx.additionalModules`. 639 ''; 640 }; 641 642 recommendedProxySettings = mkOption { 643 default = false; 644 type = types.bool; 645 description = '' 646 Whether to enable recommended proxy settings if a vhost does not specify the option manually. 647 ''; 648 }; 649 650 proxyTimeout = mkOption { 651 type = types.str; 652 default = "60s"; 653 example = "20s"; 654 description = '' 655 Change the proxy related timeouts in recommendedProxySettings. 656 ''; 657 }; 658 659 recommendedUwsgiSettings = mkOption { 660 default = false; 661 type = types.bool; 662 description = '' 663 Whether to enable recommended uwsgi settings if a vhost does not specify the option manually. 664 ''; 665 }; 666 667 uwsgiTimeout = mkOption { 668 type = types.str; 669 default = "60s"; 670 example = "20s"; 671 description = '' 672 Change the uwsgi related timeouts in recommendedUwsgiSettings. 673 ''; 674 }; 675 676 defaultListen = mkOption { 677 type = 678 with types; 679 listOf (submodule { 680 options = { 681 addr = mkOption { 682 type = str; 683 description = "IP address."; 684 }; 685 port = mkOption { 686 type = nullOr port; 687 description = "Port number."; 688 default = null; 689 }; 690 ssl = mkOption { 691 type = nullOr bool; 692 default = null; 693 description = "Enable SSL."; 694 }; 695 proxyProtocol = mkOption { 696 type = bool; 697 description = "Enable PROXY protocol."; 698 default = false; 699 }; 700 extraParameters = mkOption { 701 type = listOf str; 702 description = "Extra parameters of this listen directive."; 703 default = [ ]; 704 example = [ 705 "backlog=1024" 706 "deferred" 707 ]; 708 }; 709 }; 710 }); 711 default = [ ]; 712 example = literalExpression '' 713 [ 714 { addr = "10.0.0.12"; proxyProtocol = true; ssl = true; } 715 { addr = "0.0.0.0"; } 716 { addr = "[::0]"; } 717 ] 718 ''; 719 description = '' 720 If vhosts do not specify listen, use these addresses by default. 721 This option takes precedence over {option}`defaultListenAddresses` and 722 other listen-related defaults options. 723 ''; 724 }; 725 726 defaultListenAddresses = mkOption { 727 type = types.listOf types.str; 728 default = [ "0.0.0.0" ] ++ optional enableIPv6 "[::0]"; 729 defaultText = literalExpression ''[ "0.0.0.0" ] ++ lib.optional config.networking.enableIPv6 "[::0]"''; 730 example = literalExpression ''[ "10.0.0.12" "[2002:a00:1::]" ]''; 731 description = '' 732 If vhosts do not specify listenAddresses, use these addresses by default. 733 This is akin to writing `defaultListen = [ { addr = "0.0.0.0" } ]`. 734 ''; 735 }; 736 737 defaultHTTPListenPort = mkOption { 738 type = types.port; 739 default = 80; 740 example = 8080; 741 description = '' 742 If vhosts do not specify listen.port, use these ports for HTTP by default. 743 ''; 744 }; 745 746 defaultSSLListenPort = mkOption { 747 type = types.port; 748 default = 443; 749 example = 8443; 750 description = '' 751 If vhosts do not specify listen.port, use these ports for SSL by default. 752 ''; 753 }; 754 755 defaultMimeTypes = mkOption { 756 type = types.path; 757 default = "${pkgs.mailcap}/etc/nginx/mime.types"; 758 defaultText = literalExpression "$''{pkgs.mailcap}/etc/nginx/mime.types"; 759 example = literalExpression "$''{pkgs.nginx}/conf/mime.types"; 760 description = '' 761 Default MIME types for NGINX, as MIME types definitions from NGINX are very incomplete, 762 we use by default the ones bundled in the mailcap package, used by most of the other 763 Linux distributions. 764 ''; 765 }; 766 767 package = mkOption { 768 default = pkgs.nginxStable; 769 defaultText = literalExpression "pkgs.nginxStable"; 770 type = types.package; 771 apply = 772 p: 773 p.override { 774 modules = lib.unique (p.modules ++ cfg.additionalModules); 775 }; 776 description = '' 777 Nginx package to use. This defaults to the stable version. Note 778 that the nginx team recommends to use the mainline version which 779 available in nixpkgs as `nginxMainline`. 780 Supported Nginx forks include `angie`, `openresty` and `tengine`. 781 For HTTP/3 support use `nginxQuic` or `angieQuic`. 782 ''; 783 }; 784 785 additionalModules = mkOption { 786 default = [ ]; 787 type = types.listOf (types.attrsOf types.anything); 788 example = literalExpression "[ pkgs.nginxModules.echo ]"; 789 description = '' 790 Additional [third-party nginx modules](https://www.nginx.com/resources/wiki/modules/) 791 to install. Packaged modules are available in `pkgs.nginxModules`. 792 ''; 793 }; 794 795 logError = mkOption { 796 default = "stderr"; 797 type = types.str; 798 description = '' 799 Configures logging. 800 The first parameter defines a file that will store the log. The 801 special value stderr selects the standard error file. Logging to 802 syslog can be configured by specifying the syslog: prefix. 803 The second parameter determines the level of logging, and can be 804 one of the following: debug, info, notice, warn, error, crit, 805 alert, or emerg. Log levels above are listed in the order of 806 increasing severity. Setting a certain log level will cause all 807 messages of the specified and more severe log levels to be logged. 808 If this parameter is omitted then error is used. 809 ''; 810 }; 811 812 preStart = mkOption { 813 type = types.lines; 814 default = ""; 815 description = '' 816 Shell commands executed before the service's nginx is started. 817 ''; 818 }; 819 820 config = mkOption { 821 type = types.str; 822 default = ""; 823 description = '' 824 Verbatim {file}`nginx.conf` configuration. 825 This is mutually exclusive to any other config option for 826 {file}`nginx.conf` except for 827 - [](#opt-services.nginx.appendConfig) 828 - [](#opt-services.nginx.httpConfig) 829 - [](#opt-services.nginx.logError) 830 831 If additional verbatim config in addition to other options is needed, 832 [](#opt-services.nginx.appendConfig) should be used instead. 833 ''; 834 }; 835 836 prependConfig = mkOption { 837 type = types.lines; 838 default = ""; 839 description = '' 840 Configuration lines prepended to the generated Nginx 841 configuration file. Can for example be used to load modules. 842 {option}`prependConfig` can be specified more than once 843 and its value will be concatenated (contrary to {option}`config` 844 which can be set only once). 845 ''; 846 }; 847 848 appendConfig = mkOption { 849 type = types.lines; 850 default = ""; 851 description = '' 852 Configuration lines appended to the generated Nginx 853 configuration file. Commonly used by different modules 854 providing http snippets. {option}`appendConfig` 855 can be specified more than once and its value will be 856 concatenated (contrary to {option}`config` which 857 can be set only once). 858 ''; 859 }; 860 861 commonHttpConfig = mkOption { 862 type = types.lines; 863 default = ""; 864 example = '' 865 resolver 127.0.0.1 valid=5s; 866 867 log_format myformat '$remote_addr - $remote_user [$time_local] ' 868 '"$request" $status $body_bytes_sent ' 869 '"$http_referer" "$http_user_agent"'; 870 ''; 871 description = '' 872 With nginx you must provide common http context definitions before 873 they are used, e.g. log_format, resolver, etc. inside of server 874 or location contexts. Use this attribute to set these definitions 875 at the appropriate location. 876 ''; 877 }; 878 879 httpConfig = mkOption { 880 type = types.lines; 881 default = ""; 882 description = '' 883 Configuration lines to be set inside the http block. 884 This is mutually exclusive with the structured configuration 885 via virtualHosts and the recommendedXyzSettings configuration 886 options. See appendHttpConfig for appending to the generated http block. 887 ''; 888 }; 889 890 streamConfig = mkOption { 891 type = types.lines; 892 default = ""; 893 example = '' 894 server { 895 listen 127.0.0.1:53 udp reuseport; 896 proxy_timeout 20s; 897 proxy_pass 192.168.0.1:53535; 898 } 899 ''; 900 description = '' 901 Configuration lines to be set inside the stream block. 902 ''; 903 }; 904 905 eventsConfig = mkOption { 906 type = types.lines; 907 default = ""; 908 description = '' 909 Configuration lines to be set inside the events block. 910 ''; 911 }; 912 913 appendHttpConfig = mkOption { 914 type = types.lines; 915 default = ""; 916 description = '' 917 Configuration lines to be appended to the generated http block. 918 This is mutually exclusive with using config and httpConfig for 919 specifying the whole http block verbatim. 920 ''; 921 }; 922 923 enableReload = mkOption { 924 default = false; 925 type = types.bool; 926 description = '' 927 Reload nginx when configuration file changes (instead of restart). 928 The configuration file is exposed at {file}`/etc/nginx/nginx.conf`. 929 See also `systemd.services.*.restartIfChanged`. 930 ''; 931 }; 932 933 enableQuicBPF = mkOption { 934 default = false; 935 type = types.bool; 936 description = '' 937 Enables routing of QUIC packets using eBPF. When enabled, this allows 938 to support QUIC connection migration. The directive is only supported 939 on Linux 5.7+. 940 Note that enabling this option will make nginx run with extended 941 capabilities that are usually limited to processes running as root 942 namely `CAP_SYS_ADMIN` and `CAP_NET_ADMIN`. 943 ''; 944 }; 945 946 user = mkOption { 947 type = types.str; 948 default = "nginx"; 949 description = "User account under which nginx runs."; 950 }; 951 952 group = mkOption { 953 type = types.str; 954 default = "nginx"; 955 description = "Group account under which nginx runs."; 956 }; 957 958 serverTokens = mkOption { 959 type = types.bool; 960 default = false; 961 description = "Show nginx version in headers and error pages."; 962 }; 963 964 clientMaxBodySize = mkOption { 965 type = types.str; 966 default = "10m"; 967 description = "Set nginx global client_max_body_size."; 968 }; 969 970 sslCiphers = mkOption { 971 type = types.nullOr types.str; 972 # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate 973 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:DHE-RSA-CHACHA20-POLY1305"; 974 description = "Ciphers to choose from when negotiating TLS handshakes."; 975 }; 976 977 sslProtocols = mkOption { 978 type = types.str; 979 default = "TLSv1.2 TLSv1.3"; 980 example = "TLSv1 TLSv1.1 TLSv1.2 TLSv1.3"; 981 description = "Allowed TLS protocol versions."; 982 }; 983 984 sslDhparam = mkOption { 985 type = types.nullOr types.path; 986 default = null; 987 example = "/path/to/dhparams.pem"; 988 description = "Path to DH parameters file."; 989 }; 990 991 proxyResolveWhileRunning = mkOption { 992 type = types.bool; 993 default = false; 994 description = '' 995 Resolves domains of proxyPass targets at runtime and not only at startup. 996 This can be used as a workaround if nginx fails to start because of not-yet-working DNS. 997 998 :::{.warn} 999 `services.nginx.resolver` must be set for this option to work. 1000 ::: 1001 ''; 1002 }; 1003 1004 uwsgiResolveWhileRunning = mkOption { 1005 type = types.bool; 1006 default = false; 1007 description = '' 1008 Resolves domains of uwsgi targets at runtime 1009 and not only at start, you have to set 1010 services.nginx.resolver, too. 1011 ''; 1012 }; 1013 1014 mapHashBucketSize = mkOption { 1015 type = types.nullOr (types.ints.positive); 1016 default = null; 1017 description = '' 1018 Sets the bucket size for the map variables hash tables. Default 1019 value depends on the processors cache line size. 1020 1021 Refer to [the nginx docs on hashes](https://nginx.org/en/docs/hash.html) 1022 for more information. 1023 ''; 1024 }; 1025 1026 mapHashMaxSize = mkOption { 1027 type = types.nullOr types.ints.positive; 1028 default = null; 1029 description = '' 1030 Sets the maximum size of the map variables hash tables. 1031 ''; 1032 }; 1033 1034 serverNamesHashBucketSize = mkOption { 1035 type = types.nullOr types.ints.positive; 1036 default = null; 1037 description = '' 1038 Sets the bucket size for the server names hash tables. Default 1039 value depends on the processors cache line size. 1040 ''; 1041 }; 1042 1043 serverNamesHashMaxSize = mkOption { 1044 type = types.nullOr types.ints.positive; 1045 default = null; 1046 description = '' 1047 Sets the maximum size of the server names hash tables. 1048 ''; 1049 }; 1050 1051 typesHashMaxSize = mkOption { 1052 type = types.ints.positive; 1053 default = if cfg.defaultMimeTypes == "${pkgs.mailcap}/etc/nginx/mime.types" then 2688 else 1024; 1054 defaultText = literalExpression ''if config.services.nginx.defaultMimeTypes == "''${pkgs.mailcap}/etc/nginx/mime.types" then 2688 else 1024''; 1055 description = '' 1056 Sets the maximum size of the types hash tables (`types_hash_max_size`). 1057 It is recommended that the minimum size possible size is used. 1058 If {option}`recommendedOptimisation` is disabled, nginx would otherwise 1059 fail to start since the mailmap `mime.types` database has more entries 1060 than the nginx default value 1024. 1061 ''; 1062 }; 1063 1064 proxyCachePath = mkOption { 1065 type = types.attrsOf ( 1066 types.submodule ( 1067 { ... }: 1068 { 1069 options = { 1070 enable = mkEnableOption "this proxy cache path entry"; 1071 1072 keysZoneName = mkOption { 1073 type = types.str; 1074 default = "cache"; 1075 example = "my_cache"; 1076 description = "Set name to shared memory zone."; 1077 }; 1078 1079 keysZoneSize = mkOption { 1080 type = types.str; 1081 default = "10m"; 1082 example = "32m"; 1083 description = "Set size to shared memory zone."; 1084 }; 1085 1086 levels = mkOption { 1087 type = types.str; 1088 default = "1:2"; 1089 example = "1:2:2"; 1090 description = '' 1091 The levels parameter defines structure of subdirectories in cache: from 1092 1 to 3, each level accepts values 1 or 2. Сan be used any combination of 1093 1 and 2 in these formats: x, x:x and x:x:x. 1094 ''; 1095 }; 1096 1097 useTempPath = mkOption { 1098 type = types.bool; 1099 default = false; 1100 example = true; 1101 description = '' 1102 Nginx first writes files that are destined for the cache to a temporary 1103 storage area, and the use_temp_path=off directive instructs Nginx to 1104 write them to the same directories where they will be cached. Recommended 1105 that you set this parameter to off to avoid unnecessary copying of data 1106 between file systems. 1107 ''; 1108 }; 1109 1110 inactive = mkOption { 1111 type = types.str; 1112 default = "10m"; 1113 example = "1d"; 1114 description = '' 1115 Cached data that has not been accessed for the time specified by 1116 the inactive parameter is removed from the cache, regardless of 1117 its freshness. 1118 ''; 1119 }; 1120 1121 maxSize = mkOption { 1122 type = types.str; 1123 default = "1g"; 1124 example = "2048m"; 1125 description = "Set maximum cache size"; 1126 }; 1127 }; 1128 } 1129 ) 1130 ); 1131 default = { }; 1132 description = '' 1133 Configure a proxy cache path entry. 1134 See <https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path> for documentation. 1135 ''; 1136 }; 1137 1138 resolver = mkOption { 1139 type = types.submodule { 1140 options = { 1141 addresses = mkOption { 1142 type = types.listOf types.str; 1143 default = [ ]; 1144 example = literalExpression ''[ "[::1]" "127.0.0.1:5353" ]''; 1145 description = "List of resolvers to use"; 1146 }; 1147 valid = mkOption { 1148 type = types.str; 1149 default = ""; 1150 example = "30s"; 1151 description = '' 1152 By default, nginx caches answers using the TTL value of a response. 1153 An optional valid parameter allows overriding it 1154 ''; 1155 }; 1156 ipv4 = mkOption { 1157 type = types.bool; 1158 default = true; 1159 description = '' 1160 By default, nginx will look up both IPv4 and IPv6 addresses while resolving. 1161 If looking up of IPv4 addresses is not desired, the ipv4=off parameter can be 1162 specified. 1163 ''; 1164 }; 1165 ipv6 = mkOption { 1166 type = types.bool; 1167 default = config.networking.enableIPv6; 1168 defaultText = lib.literalExpression "config.networking.enableIPv6"; 1169 description = '' 1170 By default, nginx will look up both IPv4 and IPv6 addresses while resolving. 1171 If looking up of IPv6 addresses is not desired, the ipv6=off parameter can be 1172 specified. 1173 ''; 1174 }; 1175 }; 1176 }; 1177 description = '' 1178 Configures name servers used to resolve names of upstream servers into addresses 1179 ''; 1180 default = { }; 1181 }; 1182 1183 upstreams = mkOption { 1184 type = types.attrsOf ( 1185 types.submodule { 1186 options = { 1187 servers = mkOption { 1188 type = types.attrsOf ( 1189 types.submodule { 1190 freeformType = types.attrsOf ( 1191 types.oneOf [ 1192 types.bool 1193 types.int 1194 types.str 1195 ] 1196 ); 1197 options = { 1198 backup = mkOption { 1199 type = types.bool; 1200 default = false; 1201 description = '' 1202 Marks the server as a backup server. It will be passed 1203 requests when the primary servers are unavailable. 1204 ''; 1205 }; 1206 }; 1207 } 1208 ); 1209 description = '' 1210 Defines the address and other parameters of the upstream servers. 1211 See [the documentation](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#server) 1212 for the available parameters. 1213 ''; 1214 default = { }; 1215 example = lib.literalMD "see [](#opt-services.nginx.upstreams)"; 1216 }; 1217 extraConfig = mkOption { 1218 type = types.lines; 1219 default = ""; 1220 description = '' 1221 These lines go to the end of the upstream verbatim. 1222 ''; 1223 }; 1224 }; 1225 } 1226 ); 1227 description = '' 1228 Defines a group of servers to use as proxy target. 1229 ''; 1230 default = { }; 1231 example = { 1232 "backend" = { 1233 servers = { 1234 "backend1.example.com:8080" = { 1235 weight = 5; 1236 }; 1237 "backend2.example.com" = { 1238 max_fails = 3; 1239 fail_timeout = "30s"; 1240 }; 1241 "backend3.example.com" = { }; 1242 "backup1.example.com" = { 1243 backup = true; 1244 }; 1245 "backup2.example.com" = { 1246 backup = true; 1247 }; 1248 }; 1249 extraConfig = '' 1250 keepalive 16; 1251 ''; 1252 }; 1253 "memcached" = { 1254 servers."unix:/run/memcached/memcached.sock" = { }; 1255 }; 1256 }; 1257 }; 1258 1259 virtualHosts = mkOption { 1260 type = types.attrsOf ( 1261 types.submodule ( 1262 import ./vhost-options.nix { 1263 inherit config lib; 1264 } 1265 ) 1266 ); 1267 default = { 1268 localhost = { }; 1269 }; 1270 example = literalExpression '' 1271 { 1272 "hydra.example.com" = { 1273 forceSSL = true; 1274 enableACME = true; 1275 locations."/" = { 1276 proxyPass = "http://localhost:3000"; 1277 }; 1278 }; 1279 }; 1280 ''; 1281 description = "Declarative vhost config"; 1282 }; 1283 validateConfigFile = lib.mkEnableOption "validating configuration with pkgs.writeNginxConfig" // { 1284 default = true; 1285 }; 1286 }; 1287 }; 1288 1289 imports = [ 1290 (mkRemovedOptionModule [ "services" "nginx" "stateDir" ] '' 1291 The Nginx log directory has been moved to /var/log/nginx, the cache directory 1292 to /var/cache/nginx. The option services.nginx.stateDir has been removed. 1293 '') 1294 (mkRenamedOptionModule 1295 [ "services" "nginx" "proxyCache" "inactive" ] 1296 [ "services" "nginx" "proxyCachePath" "" "inactive" ] 1297 ) 1298 (mkRenamedOptionModule 1299 [ "services" "nginx" "proxyCache" "useTempPath" ] 1300 [ "services" "nginx" "proxyCachePath" "" "useTempPath" ] 1301 ) 1302 (mkRenamedOptionModule 1303 [ "services" "nginx" "proxyCache" "levels" ] 1304 [ "services" "nginx" "proxyCachePath" "" "levels" ] 1305 ) 1306 (mkRenamedOptionModule 1307 [ "services" "nginx" "proxyCache" "keysZoneSize" ] 1308 [ "services" "nginx" "proxyCachePath" "" "keysZoneSize" ] 1309 ) 1310 (mkRenamedOptionModule 1311 [ "services" "nginx" "proxyCache" "keysZoneName" ] 1312 [ "services" "nginx" "proxyCachePath" "" "keysZoneName" ] 1313 ) 1314 (mkRenamedOptionModule 1315 [ "services" "nginx" "proxyCache" "enable" ] 1316 [ "services" "nginx" "proxyCachePath" "" "enable" ] 1317 ) 1318 (mkRemovedOptionModule [ "services" "nginx" "recommendedZstdSettings" ] '' 1319 The zstd module for Nginx has known bugs and is not maintained well. It is thus not 1320 generally recommend to use it. You may enable anyway by setting 1321 `services.nginx.experimentalZstdSettings` which adds the same configuration as the 1322 removed option. 1323 '') 1324 ]; 1325 1326 config = mkIf cfg.enable { 1327 warnings = 1328 let 1329 deprecatedSSL = 1330 name: config: 1331 optional config.enableSSL '' 1332 config.services.nginx.virtualHosts.<name>.enableSSL is deprecated, 1333 use config.services.nginx.virtualHosts.<name>.onlySSL instead. 1334 ''; 1335 1336 in 1337 flatten (mapAttrsToList deprecatedSSL virtualHosts); 1338 1339 assertions = 1340 let 1341 hostOrAliasIsNull = l: l.root == null || l.alias == null; 1342 in 1343 [ 1344 { 1345 assertion = all (host: all hostOrAliasIsNull (attrValues host.locations)) (attrValues virtualHosts); 1346 message = "Only one of nginx root or alias can be specified on a location."; 1347 } 1348 1349 { 1350 assertion = all ( 1351 host: 1352 with host; 1353 count id [ 1354 addSSL 1355 (onlySSL || enableSSL) 1356 forceSSL 1357 rejectSSL 1358 ] <= 1 1359 ) (attrValues virtualHosts); 1360 message = '' 1361 Options services.nginx.service.virtualHosts.<name>.addSSL, 1362 services.nginx.virtualHosts.<name>.onlySSL, 1363 services.nginx.virtualHosts.<name>.forceSSL and 1364 services.nginx.virtualHosts.<name>.rejectSSL are mutually exclusive. 1365 ''; 1366 } 1367 1368 { 1369 assertion = all (host: !(host.enableACME && host.useACMEHost != null)) (attrValues virtualHosts); 1370 message = '' 1371 Options services.nginx.service.virtualHosts.<name>.enableACME and 1372 services.nginx.virtualHosts.<name>.useACMEHost are mutually exclusive. 1373 ''; 1374 } 1375 1376 { 1377 assertion = all ( 1378 host: 1379 all (location: !(location.proxyPass != null && location.uwsgiPass != null)) ( 1380 attrValues host.locations 1381 ) 1382 ) (attrValues virtualHosts); 1383 message = '' 1384 Options services.nginx.service.virtualHosts.<name>.proxyPass and 1385 services.nginx.virtualHosts.<name>.uwsgiPass are mutually exclusive. 1386 ''; 1387 } 1388 1389 { 1390 assertion = 1391 cfg.package.pname != "nginxQuic" && cfg.package.pname != "angieQuic" -> !(cfg.enableQuicBPF); 1392 message = '' 1393 services.nginx.enableQuicBPF requires using nginxQuic package, 1394 which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;` or 1395 `services.nginx.package = pkgs.angieQuic;`. 1396 ''; 1397 } 1398 1399 { 1400 assertion = 1401 cfg.package.pname != "nginxQuic" && cfg.package.pname != "angieQuic" 1402 -> all (host: !host.quic) (attrValues virtualHosts); 1403 message = '' 1404 services.nginx.service.virtualHosts.<name>.quic requires using nginxQuic or angie packages, 1405 which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;` or 1406 `services.nginx.package = pkgs.angieQuic;`. 1407 ''; 1408 } 1409 1410 { 1411 # The idea is to understand whether there is a virtual host with a listen configuration 1412 # that requires ACME configuration but has no HTTP listener which will make deterministically fail 1413 # this operation. 1414 # Options' priorities are the following at the moment: 1415 # listen (vhost) > defaultListen (server) > listenAddresses (vhost) > defaultListenAddresses (server) 1416 assertion = 1417 let 1418 hasAtLeastHttpListener = 1419 listenOptions: 1420 any ( 1421 listenLine: if listenLine ? proxyProtocol then !listenLine.proxyProtocol else true 1422 ) listenOptions; 1423 hasAtLeastDefaultHttpListener = 1424 if cfg.defaultListen != [ ] then 1425 hasAtLeastHttpListener cfg.defaultListen 1426 else 1427 (cfg.defaultListenAddresses != [ ]); 1428 in 1429 all ( 1430 host: 1431 let 1432 hasAtLeastVhostHttpListener = 1433 if host.listen != [ ] then hasAtLeastHttpListener host.listen else (host.listenAddresses != [ ]); 1434 vhostAuthority = host.listen != [ ] || (cfg.defaultListen == [ ] && host.listenAddresses != [ ]); 1435 in 1436 # Either vhost has precedence and we need a vhost specific http listener 1437 # Either vhost set nothing and inherit from server settings 1438 host.enableACME 1439 -> ( 1440 (vhostAuthority && hasAtLeastVhostHttpListener) 1441 || (!vhostAuthority && hasAtLeastDefaultHttpListener) 1442 ) 1443 ) (attrValues virtualHosts); 1444 message = '' 1445 services.nginx.virtualHosts.<name>.enableACME requires a HTTP listener 1446 to answer to ACME requests. 1447 ''; 1448 } 1449 1450 { 1451 assertion = cfg.resolver.ipv4 || cfg.resolver.ipv6; 1452 message = '' 1453 At least one of services.nginx.resolver.ipv4 and services.nginx.resolver.ipv6 must be true. 1454 ''; 1455 } 1456 ] 1457 ++ map ( 1458 name: 1459 mkCertOwnershipAssertion { 1460 cert = config.security.acme.certs.${name}; 1461 groups = config.users.groups; 1462 services = [ 1463 config.systemd.services.nginx 1464 ] 1465 ++ lib.optional ( 1466 cfg.enableReload || vhostCertNames != [ ] 1467 ) config.systemd.services.nginx-config-reload; 1468 } 1469 ) vhostCertNames; 1470 1471 services.nginx.additionalModules = 1472 optional cfg.recommendedBrotliSettings pkgs.nginxModules.brotli 1473 ++ lib.optional cfg.experimentalZstdSettings pkgs.nginxModules.zstd; 1474 1475 services.nginx.virtualHosts.localhost = mkIf cfg.statusPage { 1476 serverAliases = [ "127.0.0.1" ] ++ lib.optional config.networking.enableIPv6 "[::1]"; 1477 listenAddresses = lib.mkDefault ( 1478 [ 1479 "0.0.0.0" 1480 ] 1481 ++ lib.optional enableIPv6 "[::]" 1482 ); 1483 locations."/nginx_status" = { 1484 extraConfig = '' 1485 stub_status on; 1486 access_log off; 1487 allow 127.0.0.1; 1488 ${optionalString enableIPv6 "allow ::1;"} 1489 deny all; 1490 ''; 1491 }; 1492 }; 1493 1494 systemd.services.nginx = { 1495 description = "Nginx Web Server"; 1496 wantedBy = [ "multi-user.target" ]; 1497 wants = concatLists (map (certName: [ "acme-${certName}.service" ]) vhostCertNames); 1498 after = [ 1499 "network.target" 1500 ] 1501 # Ensure nginx runs with baseline certificates in place. 1502 ++ map (certName: "acme-${certName}.service") vhostCertNames; 1503 # Ensure nginx runs (with current config) before the actual ACME jobs run 1504 before = map (certName: "acme-order-renew-${certName}.service") vhostCertNames; 1505 stopIfChanged = false; 1506 preStart = '' 1507 ${cfg.preStart} 1508 ${execCommand} -t 1509 ''; 1510 1511 startLimitIntervalSec = 60; 1512 serviceConfig = { 1513 ExecStart = execCommand; 1514 ExecReload = [ 1515 "${execCommand} -t" 1516 "${pkgs.coreutils}/bin/kill -HUP $MAINPID" 1517 ]; 1518 Restart = "always"; 1519 RestartSec = "10s"; 1520 # User and group 1521 User = cfg.user; 1522 Group = cfg.group; 1523 # Runtime directory and mode 1524 RuntimeDirectory = "nginx"; 1525 RuntimeDirectoryMode = "0750"; 1526 # Cache directory and mode 1527 CacheDirectory = "nginx"; 1528 CacheDirectoryMode = "0750"; 1529 # Logs directory and mode 1530 LogsDirectory = "nginx"; 1531 LogsDirectoryMode = "0750"; 1532 # Proc filesystem 1533 ProcSubset = "pid"; 1534 ProtectProc = "invisible"; 1535 # New file permissions 1536 UMask = "0027"; # 0640 / 0750 1537 # Capabilities 1538 AmbientCapabilities = [ 1539 "CAP_NET_BIND_SERVICE" 1540 "CAP_SYS_RESOURCE" 1541 ] 1542 ++ optionals cfg.enableQuicBPF [ 1543 "CAP_SYS_ADMIN" 1544 "CAP_NET_ADMIN" 1545 ]; 1546 CapabilityBoundingSet = [ 1547 "CAP_NET_BIND_SERVICE" 1548 "CAP_SYS_RESOURCE" 1549 ] 1550 ++ optionals cfg.enableQuicBPF [ 1551 "CAP_SYS_ADMIN" 1552 "CAP_NET_ADMIN" 1553 ]; 1554 # Security 1555 NoNewPrivileges = true; 1556 # Sandboxing (sorted by occurrence in https://www.freedesktop.org/software/systemd/man/systemd.exec.html) 1557 ProtectSystem = "strict"; 1558 ProtectHome = mkDefault true; 1559 PrivateTmp = true; 1560 PrivateDevices = true; 1561 ProtectHostname = true; 1562 ProtectClock = true; 1563 ProtectKernelTunables = true; 1564 ProtectKernelModules = true; 1565 ProtectKernelLogs = true; 1566 ProtectControlGroups = true; 1567 RestrictAddressFamilies = [ 1568 "AF_UNIX" 1569 "AF_INET" 1570 "AF_INET6" 1571 ]; 1572 RestrictNamespaces = true; 1573 LockPersonality = true; 1574 MemoryDenyWriteExecute = 1575 !( 1576 (builtins.any (mod: (mod.allowMemoryWriteExecute or false)) cfg.package.modules) 1577 || (cfg.package == pkgs.openresty) 1578 ); 1579 RestrictRealtime = true; 1580 RestrictSUIDSGID = true; 1581 RemoveIPC = true; 1582 PrivateMounts = true; 1583 # System Call Filtering 1584 SystemCallArchitectures = "native"; 1585 SystemCallFilter = [ 1586 "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid" 1587 ] 1588 ++ optional cfg.enableQuicBPF [ "bpf" ]; 1589 }; 1590 }; 1591 1592 environment.etc."nginx/nginx.conf" = mkIf cfg.enableReload { 1593 source = configFile; 1594 }; 1595 1596 # This service waits for all certificates to be available 1597 # before reloading nginx configuration. 1598 # sslTargets are added to wantedBy + before 1599 # which allows the acme-order-renew-$cert.service to signify the successful updating 1600 # of certs end-to-end. 1601 systemd.services.nginx-config-reload = 1602 let 1603 sslOrderRenewServices = map (certName: "acme-order-renew-${certName}.service") vhostCertNames; 1604 in 1605 mkIf (cfg.enableReload || vhostCertNames != [ ]) { 1606 wants = optionals cfg.enableReload [ "nginx.service" ]; 1607 wantedBy = sslOrderRenewServices ++ [ "multi-user.target" ]; 1608 # XXX Before the finished targets, after the renew services. 1609 # This service might be needed for HTTP-01 challenges, but we only want to confirm 1610 # certs are updated _after_ config has been reloaded. 1611 after = sslOrderRenewServices; 1612 restartTriggers = optionals cfg.enableReload [ configFile ]; 1613 # Block reloading if not all certs exist yet. 1614 # Happens when config changes add new vhosts/certs. 1615 unitConfig = { 1616 ConditionPathExists = optionals (vhostCertNames != [ ]) ( 1617 map (certName: certs.${certName}.directory + "/fullchain.pem") vhostCertNames 1618 ); 1619 # Disable rate limiting for this, because it may be triggered quickly a bunch of times 1620 # if a lot of certificates are renewed in quick succession. The reload itself is cheap, 1621 # so even doing a lot of them in a short burst is fine. 1622 # FIXME: there's probably a better way to do this. 1623 StartLimitIntervalSec = 0; 1624 }; 1625 serviceConfig = { 1626 Type = "oneshot"; 1627 TimeoutSec = 60; 1628 ExecCondition = "/run/current-system/systemd/bin/systemctl -q is-active nginx.service"; 1629 ExecStart = "/run/current-system/systemd/bin/systemctl reload nginx.service"; 1630 }; 1631 }; 1632 1633 security.acme.certs = 1634 let 1635 acmePairs = map ( 1636 vhostConfig: 1637 let 1638 hasRoot = vhostConfig.acmeRoot != null; 1639 in 1640 nameValuePair vhostConfig.serverName { 1641 group = mkDefault cfg.group; 1642 # if acmeRoot is null inherit config.security.acme 1643 # Since config.security.acme.certs.<cert>.webroot's own default value 1644 # should take precedence set priority higher than mkOptionDefault 1645 webroot = mkOverride (if hasRoot then 1000 else 2000) vhostConfig.acmeRoot; 1646 # Also nudge dnsProvider to null in case it is inherited 1647 dnsProvider = mkOverride (if hasRoot then 1000 else 2000) null; 1648 extraDomainNames = vhostConfig.serverAliases; 1649 # Filter for enableACME-only vhosts. Don't want to create dud certs 1650 } 1651 ) (filter (vhostConfig: vhostConfig.useACMEHost == null) acmeEnabledVhosts); 1652 in 1653 listToAttrs acmePairs; 1654 1655 users.users = optionalAttrs (cfg.user == "nginx") { 1656 nginx = { 1657 group = cfg.group; 1658 isSystemUser = true; 1659 uid = config.ids.uids.nginx; 1660 }; 1661 }; 1662 1663 users.groups = optionalAttrs (cfg.group == "nginx") { 1664 nginx.gid = config.ids.gids.nginx; 1665 }; 1666 1667 boot.kernelModules = optional (versionAtLeast config.boot.kernelPackages.kernel.version "4.17") "tls"; 1668 1669 # do not delete the default temp directories created upon nginx startup 1670 systemd.tmpfiles.rules = [ 1671 "X /tmp/systemd-private-%b-nginx.service-*/tmp/nginx_*" 1672 ]; 1673 1674 services.logrotate.settings.nginx = mapAttrs (_: mkDefault) { 1675 files = [ "/var/log/nginx/*.log" ]; 1676 frequency = "weekly"; 1677 su = "${cfg.user} ${cfg.group}"; 1678 rotate = 26; 1679 compress = true; 1680 delaycompress = true; 1681 postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`"; 1682 }; 1683 }; 1684}