grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / web-servers / nginx / default.nix
bloba7efd69adac392bed5d11eb698a1114beb3b585e
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   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;
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   ];
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";
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";
85     GATEWAY_INTERFACE = "CGI/1.1";
86     SERVER_SOFTWARE   = "nginx/$nginx_version";
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";
94     REDIRECT_STATUS   = "200";
95   };
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   '';
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));
117   toUpstreamParameter = key: value:
118     if builtins.isBool value
119     then lib.optionalString value key
120     else "${key}=${toString value}";
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   ''));
131   commonHttpConfig = ''
132       # Load mime types and configure maximum size of the types hash tables.
133       include ${cfg.defaultMimeTypes};
134       types_hash_max_size ${toString cfg.typesHashMaxSize};
136       include ${cfg.package}/conf/fastcgi.conf;
137       include ${cfg.package}/conf/uwsgi_params;
139       default_type application/octet-stream;
140   '';
142   configFile = (
143       if cfg.validateConfigFile
144       then pkgs.writers.writeNginxConfig
145       else pkgs.writeText
146     ) "nginx.conf" ''
147     pid /run/nginx/nginx.pid;
148     error_log ${cfg.logError};
149     daemon off;
151     ${optionalString cfg.enableQuicBPF ''
152       quic_bpf on;
153     ''}
155     ${cfg.config}
157     ${optionalString (cfg.eventsConfig != "" || cfg.config == "") ''
158     events {
159       ${cfg.eventsConfig}
160     }
161     ''}
163     ${optionalString (cfg.httpConfig == "" && cfg.config == "") ''
164     http {
165       ${commonHttpConfig}
167       ${optionalString (cfg.resolver.addresses != []) ''
168         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"};
169       ''}
170       ${upstreamConfig}
172       ${optionalString cfg.recommendedOptimisation ''
173         # optimisation
174         sendfile on;
175         tcp_nopush on;
176         tcp_nodelay on;
177         keepalive_timeout 65;
178       ''}
180       ssl_protocols ${cfg.sslProtocols};
181       ${optionalString (cfg.sslCiphers != null) "ssl_ciphers ${cfg.sslCiphers};"}
182       ${optionalString (cfg.sslDhparam != null) "ssl_dhparam ${cfg.sslDhparam};"}
184       ${optionalString cfg.recommendedTlsSettings ''
185         # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate
187         ssl_session_timeout 1d;
188         ssl_session_cache shared:SSL:10m;
189         # Breaks forward secrecy: https://github.com/mozilla/server-side-tls/issues/135
190         ssl_session_tickets off;
191         # We don't enable insecure ciphers by default, so this allows
192         # clients to pick the most performant, per https://github.com/mozilla/server-side-tls/issues/260
193         ssl_prefer_server_ciphers off;
195         # OCSP stapling
196         ssl_stapling on;
197         ssl_stapling_verify on;
198       ''}
200       ${optionalString cfg.recommendedBrotliSettings ''
201         brotli on;
202         brotli_static on;
203         brotli_comp_level 5;
204         brotli_window 512k;
205         brotli_min_length 256;
206         brotli_types ${lib.concatStringsSep " " compressMimeTypes};
207       ''}
209       ${optionalString cfg.recommendedGzipSettings
210         # https://docs.nginx.com/nginx/admin-guide/web-server/compression/
211       ''
212         gzip on;
213         gzip_static on;
214         gzip_vary on;
215         gzip_comp_level 5;
216         gzip_min_length 256;
217         gzip_proxied expired no-cache no-store private auth;
218         gzip_types ${lib.concatStringsSep " " compressMimeTypes};
219       ''}
221       ${optionalString cfg.recommendedZstdSettings ''
222         zstd on;
223         zstd_comp_level 9;
224         zstd_min_length 256;
225         zstd_static on;
226         zstd_types ${lib.concatStringsSep " " compressMimeTypes};
227       ''}
229       ${optionalString cfg.recommendedProxySettings ''
230         proxy_redirect          off;
231         proxy_connect_timeout   ${cfg.proxyTimeout};
232         proxy_send_timeout      ${cfg.proxyTimeout};
233         proxy_read_timeout      ${cfg.proxyTimeout};
234         proxy_http_version      1.1;
235         # don't let clients close the keep-alive connection to upstream. See the nginx blog for details:
236         # https://www.nginx.com/blog/avoiding-top-10-nginx-configuration-mistakes/#no-keepalives
237         proxy_set_header        "Connection" "";
238         include ${recommendedProxyConfig};
239       ''}
241       ${optionalString (cfg.mapHashBucketSize != null) ''
242         map_hash_bucket_size ${toString cfg.mapHashBucketSize};
243       ''}
245       ${optionalString (cfg.mapHashMaxSize != null) ''
246         map_hash_max_size ${toString cfg.mapHashMaxSize};
247       ''}
249       ${optionalString (cfg.serverNamesHashBucketSize != null) ''
250         server_names_hash_bucket_size ${toString cfg.serverNamesHashBucketSize};
251       ''}
253       ${optionalString (cfg.serverNamesHashMaxSize != null) ''
254         server_names_hash_max_size ${toString cfg.serverNamesHashMaxSize};
255       ''}
257       # $connection_upgrade is used for websocket proxying
258       map $http_upgrade $connection_upgrade {
259           default upgrade;
260           '''      close;
261       }
262       client_max_body_size ${cfg.clientMaxBodySize};
264       server_tokens ${if cfg.serverTokens then "on" else "off"};
266       ${cfg.commonHttpConfig}
268       ${proxyCachePathConfig}
270       ${vhosts}
272       ${cfg.appendHttpConfig}
273     }''}
275     ${optionalString (cfg.httpConfig != "") ''
276     http {
277       ${commonHttpConfig}
278       ${cfg.httpConfig}
279     }''}
281     ${optionalString (cfg.streamConfig != "") ''
282     stream {
283       ${cfg.streamConfig}
284     }
285     ''}
287     ${cfg.appendConfig}
288   '';
290   configPath = if cfg.enableReload
291     then "/etc/nginx/nginx.conf"
292     else configFile;
294   execCommand = "${cfg.package}/bin/nginx -c '${configPath}'";
296   vhosts = concatStringsSep "\n" (mapAttrsToList (vhostName: vhost:
297     let
298         onlySSL = vhost.onlySSL || vhost.enableSSL;
299         hasSSL = onlySSL || vhost.addSSL || vhost.forceSSL;
301         # First evaluation of defaultListen based on a set of listen lines.
302         mkDefaultListenVhost = listenLines:
303           # If this vhost has SSL or is a SSL rejection host.
304           # We enable a TLS variant for lines without explicit ssl or ssl = true.
305           optionals (hasSSL || vhost.rejectSSL)
306             (map (listen: { port = cfg.defaultSSLListenPort; ssl = true; } // listen)
307             (filter (listen: !(listen ? ssl) || listen.ssl) listenLines))
308           # If this vhost is supposed to serve HTTP
309           # We provide listen lines for those without explicit ssl or ssl = false.
310           ++ optionals (!onlySSL)
311             (map (listen: { port = cfg.defaultHTTPListenPort; ssl = false; } // listen)
312             (filter (listen: !(listen ? ssl) || !listen.ssl) listenLines));
314         defaultListen =
315           if vhost.listen != [] then vhost.listen
316           else
317           if cfg.defaultListen != [] then mkDefaultListenVhost
318             # Cleanup nulls which will mess up with //.
319             # TODO: is there a better way to achieve this? i.e. mergeButIgnoreNullPlease?
320             (map (listenLine: filterAttrs (_: v: (v != null)) listenLine) cfg.defaultListen)
321           else
322             let addrs = if vhost.listenAddresses != [] then vhost.listenAddresses else cfg.defaultListenAddresses;
323             in mkDefaultListenVhost (map (addr: { inherit addr; }) addrs);
326         hostListen =
327           if vhost.forceSSL
328             then filter (x: x.ssl) defaultListen
329             else defaultListen;
331         listenString = { addr, port, ssl, proxyProtocol ? false, extraParameters ? [], ... }:
332           # UDP listener for QUIC transport protocol.
333           (optionalString (ssl && vhost.quic) ("
334             listen ${addr}${optionalString (port != null) ":${toString port}"} quic "
335           + optionalString vhost.default "default_server "
336           + optionalString vhost.reuseport "reuseport "
337           + optionalString (extraParameters != []) (concatStringsSep " "
338             (let inCompatibleParameters = [ "accept_filter" "backlog" "deferred" "fastopen" "http2" "proxy_protocol" "so_keepalive" "ssl" ];
339                 isCompatibleParameter = param: !(any (p: lib.hasPrefix p param) inCompatibleParameters);
340             in filter isCompatibleParameter extraParameters))
341           + ";"))
342           + "
343             listen ${addr}${optionalString (port != null) ":${toString port}"} "
344           + optionalString (ssl && vhost.http2 && oldHTTP2) "http2 "
345           + optionalString ssl "ssl "
346           + optionalString vhost.default "default_server "
347           + optionalString vhost.reuseport "reuseport "
348           + optionalString proxyProtocol "proxy_protocol "
349           + optionalString (extraParameters != []) (concatStringsSep " " extraParameters)
350           + ";";
352         redirectListen = filter (x: !x.ssl) defaultListen;
354         # The acme-challenge location doesn't need to be added if we are not using any automated
355         # certificate provisioning and can also be omitted when we use a certificate obtained via a DNS-01 challenge
356         acmeName = if vhost.useACMEHost != null then vhost.useACMEHost else vhost.serverName;
357         acmeLocation = optionalString ((vhost.enableACME || vhost.useACMEHost != null) && config.security.acme.certs.${acmeName}.dnsProvider == null)
358           # Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx)
359           # We use ^~ here, so that we don't check any regexes (which could
360           # otherwise easily override this intended match accidentally).
361         ''
362           location ^~ /.well-known/acme-challenge/ {
363             ${optionalString (vhost.acmeFallbackHost != null) "try_files $uri @acme-fallback;"}
364             ${optionalString (vhost.acmeRoot != null) "root ${vhost.acmeRoot};"}
365             auth_basic off;
366             auth_request off;
367           }
368           ${optionalString (vhost.acmeFallbackHost != null) ''
369             location @acme-fallback {
370               auth_basic off;
371               auth_request off;
372               proxy_pass http://${vhost.acmeFallbackHost};
373             }
374           ''}
375         '';
377       in ''
378         ${optionalString vhost.forceSSL ''
379           server {
380             ${concatMapStringsSep "\n" listenString redirectListen}
382             server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
384             location / {
385               return ${toString vhost.redirectCode} https://$host$request_uri;
386             }
387             ${acmeLocation}
388           }
389         ''}
391         server {
392           ${concatMapStringsSep "\n" listenString hostListen}
393           server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
394           ${optionalString (hasSSL && vhost.http2 && !oldHTTP2) ''
395             http2 on;
396           ''}
397           ${optionalString (hasSSL && vhost.quic) ''
398             http3 ${if vhost.http3 then "on" else "off"};
399             http3_hq ${if vhost.http3_hq then "on" else "off"};
400           ''}
401           ${optionalString hasSSL ''
402             ssl_certificate ${vhost.sslCertificate};
403             ssl_certificate_key ${vhost.sslCertificateKey};
404           ''}
405           ${optionalString (hasSSL && vhost.sslTrustedCertificate != null) ''
406             ssl_trusted_certificate ${vhost.sslTrustedCertificate};
407           ''}
408           ${optionalString vhost.rejectSSL ''
409             ssl_reject_handshake on;
410           ''}
411           ${optionalString (hasSSL && vhost.kTLS) ''
412             ssl_conf_command Options KTLS;
413           ''}
415           ${mkBasicAuth vhostName vhost}
417           ${optionalString (vhost.root != null) "root ${vhost.root};"}
419           ${optionalString (vhost.globalRedirect != null) ''
420             location / {
421               return ${toString vhost.redirectCode} http${optionalString hasSSL "s"}://${vhost.globalRedirect}$request_uri;
422             }
423           ''}
424           ${acmeLocation}
425           ${mkLocations vhost.locations}
427           ${vhost.extraConfig}
428         }
429       ''
430   ) virtualHosts);
431   mkLocations = locations: concatStringsSep "\n" (map (config: ''
432     location ${config.location} {
433       ${optionalString (config.proxyPass != null && !cfg.proxyResolveWhileRunning)
434         "proxy_pass ${config.proxyPass};"
435       }
436       ${optionalString (config.proxyPass != null && cfg.proxyResolveWhileRunning) ''
437         set $nix_proxy_target "${config.proxyPass}";
438         proxy_pass $nix_proxy_target;
439       ''}
440       ${optionalString config.proxyWebsockets ''
441         proxy_http_version 1.1;
442         proxy_set_header Upgrade $http_upgrade;
443         proxy_set_header Connection $connection_upgrade;
444       ''}
445       ${concatStringsSep "\n"
446         (mapAttrsToList (n: v: ''fastcgi_param ${n} "${v}";'')
447           (optionalAttrs (config.fastcgiParams != {})
448             (defaultFastcgiParams // config.fastcgiParams)))}
449       ${optionalString (config.index != null) "index ${config.index};"}
450       ${optionalString (config.tryFiles != null) "try_files ${config.tryFiles};"}
451       ${optionalString (config.root != null) "root ${config.root};"}
452       ${optionalString (config.alias != null) "alias ${config.alias};"}
453       ${optionalString (config.return != null) "return ${toString config.return};"}
454       ${config.extraConfig}
455       ${optionalString (config.proxyPass != null && config.recommendedProxySettings) "include ${recommendedProxyConfig};"}
456       ${mkBasicAuth "sublocation" config}
457     }
458   '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)));
460   mkBasicAuth = name: zone: optionalString (zone.basicAuthFile != null || zone.basicAuth != {}) (let
461     auth_file = if zone.basicAuthFile != null
462       then zone.basicAuthFile
463       else mkHtpasswd name zone.basicAuth;
464   in ''
465     auth_basic secured;
466     auth_basic_user_file ${auth_file};
467   '');
468   mkHtpasswd = name: authDef: pkgs.writeText "${name}.htpasswd" (
469     concatStringsSep "\n" (mapAttrsToList (user: password: ''
470       ${user}:{PLAIN}${password}
471     '') authDef)
472   );
474   mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix;
476   oldHTTP2 = (versionOlder cfg.package.version "1.25.1" && !(cfg.package.pname == "angie" || cfg.package.pname == "angieQuic"));
480   options = {
481     services.nginx = {
482       enable = mkEnableOption "Nginx Web Server";
484       statusPage = mkOption {
485         default = false;
486         type = types.bool;
487         description = ''
488           Enable status page reachable from localhost on http://127.0.0.1/nginx_status.
489         '';
490       };
492       recommendedTlsSettings = mkOption {
493         default = false;
494         type = types.bool;
495         description = ''
496           Enable recommended TLS settings.
497         '';
498       };
500       recommendedOptimisation = mkOption {
501         default = false;
502         type = types.bool;
503         description = ''
504           Enable recommended optimisation settings.
505         '';
506       };
508       recommendedBrotliSettings = mkOption {
509         default = false;
510         type = types.bool;
511         description = ''
512           Enable recommended brotli settings.
513           Learn more about compression in Brotli format [here](https://github.com/google/ngx_brotli/).
515           This adds `pkgs.nginxModules.brotli` to `services.nginx.additionalModules`.
516         '';
517       };
519       recommendedGzipSettings = mkOption {
520         default = false;
521         type = types.bool;
522         description = ''
523           Enable recommended gzip settings.
524           Learn more about compression in Gzip format [here](https://docs.nginx.com/nginx/admin-guide/web-server/compression/).
525         '';
526       };
528       recommendedZstdSettings = mkOption {
529         default = false;
530         type = types.bool;
531         description = ''
532           Enable recommended zstd settings.
533           Learn more about compression in Zstd format [here](https://github.com/tokers/zstd-nginx-module).
535           This adds `pkgs.nginxModules.zstd` to `services.nginx.additionalModules`.
536         '';
537       };
539       recommendedProxySettings = mkOption {
540         default = false;
541         type = types.bool;
542         description = ''
543           Whether to enable recommended proxy settings if a vhost does not specify the option manually.
544         '';
545       };
547       proxyTimeout = mkOption {
548         type = types.str;
549         default = "60s";
550         example = "20s";
551         description = ''
552           Change the proxy related timeouts in recommendedProxySettings.
553         '';
554       };
556       defaultListen = mkOption {
557         type = with types; listOf (submodule {
558           options = {
559             addr = mkOption {
560               type = str;
561               description = "IP address.";
562             };
563             port = mkOption {
564               type = nullOr port;
565               description = "Port number.";
566               default = null;
567             };
568             ssl  = mkOption {
569               type = nullOr bool;
570               default = null;
571               description = "Enable SSL.";
572             };
573             proxyProtocol = mkOption {
574               type = bool;
575               description = "Enable PROXY protocol.";
576               default = false;
577             };
578             extraParameters = mkOption {
579               type = listOf str;
580               description = "Extra parameters of this listen directive.";
581               default = [ ];
582               example = [ "backlog=1024" "deferred" ];
583             };
584           };
585         });
586         default = [];
587         example = literalExpression ''
588           [
589             { addr = "10.0.0.12"; proxyProtocol = true; ssl = true; }
590             { addr = "0.0.0.0"; }
591             { addr = "[::0]"; }
592           ]
593         '';
594         description = ''
595           If vhosts do not specify listen, use these addresses by default.
596           This option takes precedence over {option}`defaultListenAddresses` and
597           other listen-related defaults options.
598         '';
599       };
601       defaultListenAddresses = mkOption {
602         type = types.listOf types.str;
603         default = [ "0.0.0.0" ] ++ optional enableIPv6 "[::0]";
604         defaultText = literalExpression ''[ "0.0.0.0" ] ++ lib.optional config.networking.enableIPv6 "[::0]"'';
605         example = literalExpression ''[ "10.0.0.12" "[2002:a00:1::]" ]'';
606         description = ''
607           If vhosts do not specify listenAddresses, use these addresses by default.
608           This is akin to writing `defaultListen = [ { addr = "0.0.0.0" } ]`.
609         '';
610       };
612       defaultHTTPListenPort = mkOption {
613         type = types.port;
614         default = 80;
615         example = 8080;
616         description = ''
617           If vhosts do not specify listen.port, use these ports for HTTP by default.
618         '';
619       };
621       defaultSSLListenPort = mkOption {
622         type = types.port;
623         default = 443;
624         example = 8443;
625         description = ''
626           If vhosts do not specify listen.port, use these ports for SSL by default.
627         '';
628       };
630       defaultMimeTypes = mkOption {
631         type = types.path;
632         default = "${pkgs.mailcap}/etc/nginx/mime.types";
633         defaultText = literalExpression "$''{pkgs.mailcap}/etc/nginx/mime.types";
634         example = literalExpression "$''{pkgs.nginx}/conf/mime.types";
635         description = ''
636           Default MIME types for NGINX, as MIME types definitions from NGINX are very incomplete,
637           we use by default the ones bundled in the mailcap package, used by most of the other
638           Linux distributions.
639         '';
640       };
642       package = mkOption {
643         default = pkgs.nginxStable;
644         defaultText = literalExpression "pkgs.nginxStable";
645         type = types.package;
646         apply = p: p.override {
647           modules = lib.unique (p.modules ++ cfg.additionalModules);
648         };
649         description = ''
650           Nginx package to use. This defaults to the stable version. Note
651           that the nginx team recommends to use the mainline version which
652           available in nixpkgs as `nginxMainline`.
653           Supported Nginx forks include `angie`, `openresty` and `tengine`.
654           For HTTP/3 support use `nginxQuic` or `angieQuic`.
655         '';
656       };
658       additionalModules = mkOption {
659         default = [];
660         type = types.listOf (types.attrsOf types.anything);
661         example = literalExpression "[ pkgs.nginxModules.echo ]";
662         description = ''
663           Additional [third-party nginx modules](https://www.nginx.com/resources/wiki/modules/)
664           to install. Packaged modules are available in `pkgs.nginxModules`.
665         '';
666       };
668       logError = mkOption {
669         default = "stderr";
670         type = types.str;
671         description = ''
672           Configures logging.
673           The first parameter defines a file that will store the log. The
674           special value stderr selects the standard error file. Logging to
675           syslog can be configured by specifying the “syslog:” prefix.
676           The second parameter determines the level of logging, and can be
677           one of the following: debug, info, notice, warn, error, crit,
678           alert, or emerg. Log levels above are listed in the order of
679           increasing severity. Setting a certain log level will cause all
680           messages of the specified and more severe log levels to be logged.
681           If this parameter is omitted then error is used.
682         '';
683       };
685       preStart =  mkOption {
686         type = types.lines;
687         default = "";
688         description = ''
689           Shell commands executed before the service's nginx is started.
690         '';
691       };
693       config = mkOption {
694         type = types.str;
695         default = "";
696         description = ''
697           Verbatim {file}`nginx.conf` configuration.
698           This is mutually exclusive to any other config option for
699           {file}`nginx.conf` except for
700           - [](#opt-services.nginx.appendConfig)
701           - [](#opt-services.nginx.httpConfig)
702           - [](#opt-services.nginx.logError)
704           If additional verbatim config in addition to other options is needed,
705           [](#opt-services.nginx.appendConfig) should be used instead.
706         '';
707       };
709       appendConfig = mkOption {
710         type = types.lines;
711         default = "";
712         description = ''
713           Configuration lines appended to the generated Nginx
714           configuration file. Commonly used by different modules
715           providing http snippets. {option}`appendConfig`
716           can be specified more than once and its value will be
717           concatenated (contrary to {option}`config` which
718           can be set only once).
719         '';
720       };
722       commonHttpConfig = mkOption {
723         type = types.lines;
724         default = "";
725         example = ''
726           resolver 127.0.0.1 valid=5s;
728           log_format myformat '$remote_addr - $remote_user [$time_local] '
729                               '"$request" $status $body_bytes_sent '
730                               '"$http_referer" "$http_user_agent"';
731         '';
732         description = ''
733           With nginx you must provide common http context definitions before
734           they are used, e.g. log_format, resolver, etc. inside of server
735           or location contexts. Use this attribute to set these definitions
736           at the appropriate location.
737         '';
738       };
740       httpConfig = mkOption {
741         type = types.lines;
742         default = "";
743         description = ''
744           Configuration lines to be set inside the http block.
745           This is mutually exclusive with the structured configuration
746           via virtualHosts and the recommendedXyzSettings configuration
747           options. See appendHttpConfig for appending to the generated http block.
748         '';
749       };
751       streamConfig = mkOption {
752         type = types.lines;
753         default = "";
754         example = ''
755           server {
756             listen 127.0.0.1:53 udp reuseport;
757             proxy_timeout 20s;
758             proxy_pass 192.168.0.1:53535;
759           }
760         '';
761         description = ''
762           Configuration lines to be set inside the stream block.
763         '';
764       };
766       eventsConfig = mkOption {
767         type = types.lines;
768         default = "";
769         description = ''
770           Configuration lines to be set inside the events block.
771         '';
772       };
774       appendHttpConfig = mkOption {
775         type = types.lines;
776         default = "";
777         description = ''
778           Configuration lines to be appended to the generated http block.
779           This is mutually exclusive with using config and httpConfig for
780           specifying the whole http block verbatim.
781         '';
782       };
784       enableReload = mkOption {
785         default = false;
786         type = types.bool;
787         description = ''
788           Reload nginx when configuration file changes (instead of restart).
789           The configuration file is exposed at {file}`/etc/nginx/nginx.conf`.
790           See also `systemd.services.*.restartIfChanged`.
791         '';
792       };
794       enableQuicBPF = mkOption {
795         default = false;
796         type = types.bool;
797         description = ''
798           Enables routing of QUIC packets using eBPF. When enabled, this allows
799           to support QUIC connection migration. The directive is only supported
800           on Linux 5.7+.
801           Note that enabling this option will make nginx run with extended
802           capabilities that are usually limited to processes running as root
803           namely `CAP_SYS_ADMIN` and `CAP_NET_ADMIN`.
804         '';
805       };
807       user = mkOption {
808         type = types.str;
809         default = "nginx";
810         description = "User account under which nginx runs.";
811       };
813       group = mkOption {
814         type = types.str;
815         default = "nginx";
816         description = "Group account under which nginx runs.";
817       };
819       serverTokens = mkOption {
820         type = types.bool;
821         default = false;
822         description = "Show nginx version in headers and error pages.";
823       };
825       clientMaxBodySize = mkOption {
826         type = types.str;
827         default = "10m";
828         description = "Set nginx global client_max_body_size.";
829       };
831       sslCiphers = mkOption {
832         type = types.nullOr types.str;
833         # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate
834         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";
835         description = "Ciphers to choose from when negotiating TLS handshakes.";
836       };
838       sslProtocols = mkOption {
839         type = types.str;
840         default = "TLSv1.2 TLSv1.3";
841         example = "TLSv1 TLSv1.1 TLSv1.2 TLSv1.3";
842         description = "Allowed TLS protocol versions.";
843       };
845       sslDhparam = mkOption {
846         type = types.nullOr types.path;
847         default = null;
848         example = "/path/to/dhparams.pem";
849         description = "Path to DH parameters file.";
850       };
852       proxyResolveWhileRunning = mkOption {
853         type = types.bool;
854         default = false;
855         description = ''
856           Resolves domains of proxyPass targets at runtime
857           and not only at start, you have to set
858           services.nginx.resolver, too.
859         '';
860       };
862       mapHashBucketSize = mkOption {
863         type = types.nullOr (types.enum [ 32 64 128 ]);
864         default = null;
865         description = ''
866             Sets the bucket size for the map variables hash tables. Default
867             value depends on the processor’s cache line size.
868           '';
869       };
871       mapHashMaxSize = mkOption {
872         type = types.nullOr types.ints.positive;
873         default = null;
874         description = ''
875             Sets the maximum size of the map variables hash tables.
876           '';
877       };
879       serverNamesHashBucketSize = mkOption {
880         type = types.nullOr types.ints.positive;
881         default = null;
882         description = ''
883             Sets the bucket size for the server names hash tables. Default
884             value depends on the processor’s cache line size.
885           '';
886       };
888       serverNamesHashMaxSize = mkOption {
889         type = types.nullOr types.ints.positive;
890         default = null;
891         description = ''
892             Sets the maximum size of the server names hash tables.
893           '';
894       };
896       typesHashMaxSize = mkOption {
897         type = types.ints.positive;
898         default = if cfg.defaultMimeTypes == "${pkgs.mailcap}/etc/nginx/mime.types" then 2688 else 1024;
899         defaultText = literalExpression ''if cfg.defaultMimeTypes == "''${pkgs.mailcap}/etc/nginx/mime.types" then 2688 else 1024'';
900         description = ''
901           Sets the maximum size of the types hash tables (`types_hash_max_size`).
902           It is recommended that the minimum size possible size is used.
903           If {option}`recommendedOptimisation` is disabled, nginx would otherwise
904           fail to start since the mailmap `mime.types` database has more entries
905           than the nginx default value 1024.
906         '';
907       };
909       proxyCachePath = mkOption {
910         type = types.attrsOf (types.submodule ({ ... }: {
911           options = {
912             enable = mkEnableOption "this proxy cache path entry";
914             keysZoneName = mkOption {
915               type = types.str;
916               default = "cache";
917               example = "my_cache";
918               description = "Set name to shared memory zone.";
919             };
921             keysZoneSize = mkOption {
922               type = types.str;
923               default = "10m";
924               example = "32m";
925               description = "Set size to shared memory zone.";
926             };
928             levels = mkOption {
929               type = types.str;
930               default = "1:2";
931               example = "1:2:2";
932               description = ''
933                 The levels parameter defines structure of subdirectories in cache: from
934                 1 to 3, each level accepts values 1 or 2. Сan be used any combination of
935                 1 and 2 in these formats: x, x:x and x:x:x.
936               '';
937             };
939             useTempPath = mkOption {
940               type = types.bool;
941               default = false;
942               example = true;
943               description = ''
944                 Nginx first writes files that are destined for the cache to a temporary
945                 storage area, and the use_temp_path=off directive instructs Nginx to
946                 write them to the same directories where they will be cached. Recommended
947                 that you set this parameter to off to avoid unnecessary copying of data
948                 between file systems.
949               '';
950             };
952             inactive = mkOption {
953               type = types.str;
954               default = "10m";
955               example = "1d";
956               description = ''
957                 Cached data that has not been accessed for the time specified by
958                 the inactive parameter is removed from the cache, regardless of
959                 its freshness.
960               '';
961             };
963             maxSize = mkOption {
964               type = types.str;
965               default = "1g";
966               example = "2048m";
967               description = "Set maximum cache size";
968             };
969           };
970         }));
971         default = {};
972         description = ''
973           Configure a proxy cache path entry.
974           See <https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path> for documentation.
975         '';
976       };
978       resolver = mkOption {
979         type = types.submodule {
980           options = {
981             addresses = mkOption {
982               type = types.listOf types.str;
983               default = [];
984               example = literalExpression ''[ "[::1]" "127.0.0.1:5353" ]'';
985               description = "List of resolvers to use";
986             };
987             valid = mkOption {
988               type = types.str;
989               default = "";
990               example = "30s";
991               description = ''
992                 By default, nginx caches answers using the TTL value of a response.
993                 An optional valid parameter allows overriding it
994               '';
995             };
996             ipv4 = mkOption {
997               type = types.bool;
998               default = true;
999               description = ''
1000                 By default, nginx will look up both IPv4 and IPv6 addresses while resolving.
1001                 If looking up of IPv4 addresses is not desired, the ipv4=off parameter can be
1002                 specified.
1003               '';
1004             };
1005             ipv6 = mkOption {
1006               type = types.bool;
1007               default = true;
1008               description = ''
1009                 By default, nginx will look up both IPv4 and IPv6 addresses while resolving.
1010                 If looking up of IPv6 addresses is not desired, the ipv6=off parameter can be
1011                 specified.
1012               '';
1013             };
1014           };
1015         };
1016         description = ''
1017           Configures name servers used to resolve names of upstream servers into addresses
1018         '';
1019         default = {};
1020       };
1022       upstreams = mkOption {
1023         type = types.attrsOf (types.submodule {
1024           options = {
1025             servers = mkOption {
1026               type = types.attrsOf (types.submodule {
1027                 freeformType = types.attrsOf (types.oneOf [ types.bool types.int types.str ]);
1028                 options = {
1029                   backup = mkOption {
1030                     type = types.bool;
1031                     default = false;
1032                     description = ''
1033                       Marks the server as a backup server. It will be passed
1034                       requests when the primary servers are unavailable.
1035                     '';
1036                   };
1037                 };
1038               });
1039               description = ''
1040                 Defines the address and other parameters of the upstream servers.
1041                 See [the documentation](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#server)
1042                 for the available parameters.
1043               '';
1044               default = {};
1045               example = lib.literalMD "see [](#opt-services.nginx.upstreams)";
1046             };
1047             extraConfig = mkOption {
1048               type = types.lines;
1049               default = "";
1050               description = ''
1051                 These lines go to the end of the upstream verbatim.
1052               '';
1053             };
1054           };
1055         });
1056         description = ''
1057           Defines a group of servers to use as proxy target.
1058         '';
1059         default = {};
1060         example = {
1061           "backend" = {
1062             servers = {
1063               "backend1.example.com:8080" = { weight = 5; };
1064               "backend2.example.com" = { max_fails = 3; fail_timeout = "30s"; };
1065               "backend3.example.com" = {};
1066               "backup1.example.com" = { backup = true; };
1067               "backup2.example.com" = { backup = true; };
1068             };
1069             extraConfig = ''
1070               keepalive 16;
1071             '';
1072           };
1073           "memcached" = {
1074             servers."unix:/run//memcached/memcached.sock" = {};
1075           };
1076         };
1077       };
1079       virtualHosts = mkOption {
1080         type = types.attrsOf (types.submodule (import ./vhost-options.nix {
1081           inherit config lib;
1082         }));
1083         default = {
1084           localhost = {};
1085         };
1086         example = literalExpression ''
1087           {
1088             "hydra.example.com" = {
1089               forceSSL = true;
1090               enableACME = true;
1091               locations."/" = {
1092                 proxyPass = "http://localhost:3000";
1093               };
1094             };
1095           };
1096         '';
1097         description = "Declarative vhost config";
1098       };
1099       validateConfigFile = lib.mkEnableOption "validating configuration with pkgs.writeNginxConfig" // {
1100         default = true;
1101       };
1102     };
1103   };
1105   imports = [
1106     (mkRemovedOptionModule [ "services" "nginx" "stateDir" ] ''
1107       The Nginx log directory has been moved to /var/log/nginx, the cache directory
1108       to /var/cache/nginx. The option services.nginx.stateDir has been removed.
1109     '')
1110     (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "inactive" ] [ "services" "nginx" "proxyCachePath" "" "inactive" ])
1111     (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "useTempPath" ] [ "services" "nginx" "proxyCachePath" "" "useTempPath" ])
1112     (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "levels" ] [ "services" "nginx" "proxyCachePath" "" "levels" ])
1113     (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "keysZoneSize" ] [ "services" "nginx" "proxyCachePath" "" "keysZoneSize" ])
1114     (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "keysZoneName" ] [ "services" "nginx" "proxyCachePath" "" "keysZoneName" ])
1115     (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "enable" ] [ "services" "nginx" "proxyCachePath" "" "enable" ])
1116   ];
1118   config = mkIf cfg.enable {
1119     warnings =
1120     let
1121       deprecatedSSL = name: config: optional config.enableSSL
1122       ''
1123         config.services.nginx.virtualHosts.<name>.enableSSL is deprecated,
1124         use config.services.nginx.virtualHosts.<name>.onlySSL instead.
1125       '';
1127     in flatten (mapAttrsToList deprecatedSSL virtualHosts);
1129     assertions =
1130     let
1131       hostOrAliasIsNull = l: l.root == null || l.alias == null;
1132     in [
1133       {
1134         assertion = all (host: all hostOrAliasIsNull (attrValues host.locations)) (attrValues virtualHosts);
1135         message = "Only one of nginx root or alias can be specified on a location.";
1136       }
1138       {
1139         assertion = all (host: with host;
1140           count id [ addSSL (onlySSL || enableSSL) forceSSL rejectSSL ] <= 1
1141         ) (attrValues virtualHosts);
1142         message = ''
1143           Options services.nginx.service.virtualHosts.<name>.addSSL,
1144           services.nginx.virtualHosts.<name>.onlySSL,
1145           services.nginx.virtualHosts.<name>.forceSSL and
1146           services.nginx.virtualHosts.<name>.rejectSSL are mutually exclusive.
1147         '';
1148       }
1150       {
1151         assertion = any (host: host.rejectSSL) (attrValues virtualHosts) -> versionAtLeast cfg.package.version "1.19.4";
1152         message = ''
1153           services.nginx.virtualHosts.<name>.rejectSSL requires nginx version
1154           1.19.4 or above; see the documentation for services.nginx.package.
1155         '';
1156       }
1158       {
1159         assertion = all (host: !(host.enableACME && host.useACMEHost != null)) (attrValues virtualHosts);
1160         message = ''
1161           Options services.nginx.service.virtualHosts.<name>.enableACME and
1162           services.nginx.virtualHosts.<name>.useACMEHost are mutually exclusive.
1163         '';
1164       }
1166       {
1167         assertion = cfg.package.pname != "nginxQuic" && cfg.package.pname != "angieQuic" -> !(cfg.enableQuicBPF);
1168         message = ''
1169           services.nginx.enableQuicBPF requires using nginxQuic package,
1170           which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;` or
1171           `services.nginx.package = pkgs.angieQuic;`.
1172         '';
1173       }
1175       {
1176         assertion = cfg.package.pname != "nginxQuic" && cfg.package.pname != "angieQuic" -> all (host: !host.quic) (attrValues virtualHosts);
1177         message = ''
1178           services.nginx.service.virtualHosts.<name>.quic requires using nginxQuic or angie packages,
1179           which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;` or
1180           `services.nginx.package = pkgs.angieQuic;`.
1181         '';
1182       }
1184       {
1185         # The idea is to understand whether there is a virtual host with a listen configuration
1186         # that requires ACME configuration but has no HTTP listener which will make deterministically fail
1187         # this operation.
1188         # Options' priorities are the following at the moment:
1189         # listen (vhost) > defaultListen (server) > listenAddresses (vhost) > defaultListenAddresses (server)
1190         assertion =
1191         let
1192           hasAtLeastHttpListener = listenOptions: any (listenLine: if listenLine ? proxyProtocol then !listenLine.proxyProtocol else true) listenOptions;
1193           hasAtLeastDefaultHttpListener = if cfg.defaultListen != [] then hasAtLeastHttpListener cfg.defaultListen else (cfg.defaultListenAddresses != []);
1194         in
1195           all (host:
1196             let
1197               hasAtLeastVhostHttpListener = if host.listen != [] then hasAtLeastHttpListener host.listen else (host.listenAddresses != []);
1198               vhostAuthority = host.listen != [] || (cfg.defaultListen == [] && host.listenAddresses != []);
1199             in
1200               # Either vhost has precedence and we need a vhost specific http listener
1201               # Either vhost set nothing and inherit from server settings
1202               host.enableACME -> ((vhostAuthority && hasAtLeastVhostHttpListener) || (!vhostAuthority && hasAtLeastDefaultHttpListener))
1203           ) (attrValues virtualHosts);
1204         message = ''
1205           services.nginx.virtualHosts.<name>.enableACME requires a HTTP listener
1206           to answer to ACME requests.
1207         '';
1208       }
1210       {
1211         assertion = cfg.resolver.ipv4 || cfg.resolver.ipv6;
1212         message = ''
1213           At least one of services.nginx.resolver.ipv4 and services.nginx.resolver.ipv6 must be true.
1214         '';
1215       }
1216     ] ++ map (name: mkCertOwnershipAssertion {
1217       inherit (cfg) group user;
1218       cert = config.security.acme.certs.${name};
1219       groups = config.users.groups;
1220     }) dependentCertNames;
1222     services.nginx.additionalModules = optional cfg.recommendedBrotliSettings pkgs.nginxModules.brotli
1223       ++ lib.optional cfg.recommendedZstdSettings pkgs.nginxModules.zstd;
1225     services.nginx.virtualHosts.localhost = mkIf cfg.statusPage {
1226       listenAddresses = lib.mkDefault ([
1227         "0.0.0.0"
1228       ] ++ lib.optional enableIPv6 "[::]");
1229       locations."/nginx_status" = {
1230         extraConfig = ''
1231           stub_status on;
1232           access_log off;
1233           allow 127.0.0.1;
1234           ${optionalString enableIPv6 "allow ::1;"}
1235           deny all;
1236         '';
1237       };
1238     };
1240     systemd.services.nginx = {
1241       description = "Nginx Web Server";
1242       wantedBy = [ "multi-user.target" ];
1243       wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) dependentCertNames);
1244       after = [ "network.target" ] ++ map (certName: "acme-selfsigned-${certName}.service") dependentCertNames;
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           ++ optionals ((cfg.package != pkgs.tengine) && (cfg.package != pkgs.openresty) && (!lib.any (mod: (mod.disableIPC or false)) cfg.package.modules)) [ "~@ipc" ];
1310       };
1311     };
1313     environment.etc."nginx/nginx.conf" = mkIf cfg.enableReload {
1314       source = configFile;
1315     };
1317     # This service waits for all certificates to be available
1318     # before reloading nginx configuration.
1319     # sslTargets are added to wantedBy + before
1320     # which allows the acme-finished-$cert.target to signify the successful updating
1321     # of certs end-to-end.
1322     systemd.services.nginx-config-reload = let
1323       sslServices = map (certName: "acme-${certName}.service") dependentCertNames;
1324       sslTargets = map (certName: "acme-finished-${certName}.target") dependentCertNames;
1325     in mkIf (cfg.enableReload || sslServices != []) {
1326       wants = optionals cfg.enableReload [ "nginx.service" ];
1327       wantedBy = sslServices ++ [ "multi-user.target" ];
1328       # Before the finished targets, after the renew services.
1329       # This service might be needed for HTTP-01 challenges, but we only want to confirm
1330       # certs are updated _after_ config has been reloaded.
1331       before = sslTargets;
1332       after = sslServices;
1333       restartTriggers = optionals cfg.enableReload [ configFile ];
1334       # Block reloading if not all certs exist yet.
1335       # Happens when config changes add new vhosts/certs.
1336       unitConfig.ConditionPathExists = optionals (sslServices != []) (map (certName: certs.${certName}.directory + "/fullchain.pem") dependentCertNames);
1337       serviceConfig = {
1338         Type = "oneshot";
1339         TimeoutSec = 60;
1340         ExecCondition = "/run/current-system/systemd/bin/systemctl -q is-active nginx.service";
1341         ExecStart = "/run/current-system/systemd/bin/systemctl reload nginx.service";
1342       };
1343     };
1345     security.acme.certs = let
1346       acmePairs = map (vhostConfig: let
1347         hasRoot = vhostConfig.acmeRoot != null;
1348       in nameValuePair vhostConfig.serverName {
1349         group = mkDefault cfg.group;
1350         # if acmeRoot is null inherit config.security.acme
1351         # Since config.security.acme.certs.<cert>.webroot's own default value
1352         # should take precedence set priority higher than mkOptionDefault
1353         webroot = mkOverride (if hasRoot then 1000 else 2000) vhostConfig.acmeRoot;
1354         # Also nudge dnsProvider to null in case it is inherited
1355         dnsProvider = mkOverride (if hasRoot then 1000 else 2000) null;
1356         extraDomainNames = vhostConfig.serverAliases;
1357       # Filter for enableACME-only vhosts. Don't want to create dud certs
1358       }) (filter (vhostConfig: vhostConfig.useACMEHost == null) acmeEnabledVhosts);
1359     in listToAttrs acmePairs;
1361     users.users = optionalAttrs (cfg.user == "nginx") {
1362       nginx = {
1363         group = cfg.group;
1364         isSystemUser = true;
1365         uid = config.ids.uids.nginx;
1366       };
1367     };
1369     users.groups = optionalAttrs (cfg.group == "nginx") {
1370       nginx.gid = config.ids.gids.nginx;
1371     };
1373     boot.kernelModules = optional (versionAtLeast config.boot.kernelPackages.kernel.version "4.17") "tls";
1375     # do not delete the default temp directories created upon nginx startup
1376     systemd.tmpfiles.rules = [
1377       "X /tmp/systemd-private-%b-nginx.service-*/tmp/nginx_*"
1378     ];
1380     services.logrotate.settings.nginx = mapAttrs (_: mkDefault) {
1381       files = "/var/log/nginx/*.log";
1382       frequency = "weekly";
1383       su = "${cfg.user} ${cfg.group}";
1384       rotate = 26;
1385       compress = true;
1386       delaycompress = true;
1387       postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";
1388     };
1389   };