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