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