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