1 { config, lib, pkgs, ... }:
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:
13 serverName = if vhostConfig.serverName != null
14 then vhostConfig.serverName
16 certName = if vhostConfig.useACMEHost != null
17 then vhostConfig.useACMEHost
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";
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
36 "application/atom+xml"
37 "application/geo+json"
38 "application/javascript" # Deprecated by IETF RFC 9239, but still widely used
41 "application/manifest+json"
43 "application/vnd.ms-fontobject"
45 "application/x-rss+xml"
46 "application/x-web-app-manifest+json"
47 "application/xhtml+xml"
48 "application/xliff+xml"
55 "image/vnd.microsoft.icon"
64 "text/vnd.rim.location.xloc"
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";
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;
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}"
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: ''
124 ${toString (flip mapAttrsToList upstream.servers (name: server: ''
125 server ${name} ${concatStringsSep " " (mapAttrsToList toUpstreamParameter server)};
127 ${upstream.extraConfig}
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;
143 if cfg.validateConfigFile
144 then pkgs.writers.writeNginxConfig
147 pid /run/nginx/nginx.pid;
148 error_log ${cfg.logError};
151 ${optionalString cfg.enableQuicBPF ''
157 ${optionalString (cfg.eventsConfig != "" || cfg.config == "") ''
163 ${optionalString (cfg.httpConfig == "" && cfg.config == "") ''
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"};
172 ${optionalString cfg.recommendedOptimisation ''
177 keepalive_timeout 65;
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;
197 ssl_stapling_verify on;
200 ${optionalString cfg.recommendedBrotliSettings ''
205 brotli_min_length 256;
206 brotli_types ${lib.concatStringsSep " " compressMimeTypes};
209 ${optionalString cfg.recommendedGzipSettings
210 # https://docs.nginx.com/nginx/admin-guide/web-server/compression/
217 gzip_proxied expired no-cache no-store private auth;
218 gzip_types ${lib.concatStringsSep " " compressMimeTypes};
221 ${optionalString cfg.recommendedZstdSettings ''
226 zstd_types ${lib.concatStringsSep " " compressMimeTypes};
229 ${optionalString cfg.recommendedProxySettings ''
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};
241 ${optionalString (cfg.mapHashBucketSize != null) ''
242 map_hash_bucket_size ${toString cfg.mapHashBucketSize};
245 ${optionalString (cfg.mapHashMaxSize != null) ''
246 map_hash_max_size ${toString cfg.mapHashMaxSize};
249 ${optionalString (cfg.serverNamesHashBucketSize != null) ''
250 server_names_hash_bucket_size ${toString cfg.serverNamesHashBucketSize};
253 ${optionalString (cfg.serverNamesHashMaxSize != null) ''
254 server_names_hash_max_size ${toString cfg.serverNamesHashMaxSize};
257 # $connection_upgrade is used for websocket proxying
258 map $http_upgrade $connection_upgrade {
262 client_max_body_size ${cfg.clientMaxBodySize};
264 server_tokens ${if cfg.serverTokens then "on" else "off"};
266 ${cfg.commonHttpConfig}
268 ${proxyCachePathConfig}
272 ${cfg.appendHttpConfig}
275 ${optionalString (cfg.httpConfig != "") ''
281 ${optionalString (cfg.streamConfig != "") ''
290 configPath = if cfg.enableReload
291 then "/etc/nginx/nginx.conf"
294 execCommand = "${cfg.package}/bin/nginx -c '${configPath}'";
296 vhosts = concatStringsSep "\n" (mapAttrsToList (vhostName: vhost:
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));
315 if vhost.listen != [] then vhost.listen
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)
322 let addrs = if vhost.listenAddresses != [] then vhost.listenAddresses else cfg.defaultListenAddresses;
323 in mkDefaultListenVhost (map (addr: { inherit addr; }) addrs);
328 then filter (x: x.ssl) 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))
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)
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).
362 location ^~ /.well-known/acme-challenge/ {
363 ${optionalString (vhost.acmeFallbackHost != null) "try_files $uri @acme-fallback;"}
364 ${optionalString (vhost.acmeRoot != null) "root ${vhost.acmeRoot};"}
368 ${optionalString (vhost.acmeFallbackHost != null) ''
369 location @acme-fallback {
372 proxy_pass http://${vhost.acmeFallbackHost};
378 ${optionalString vhost.forceSSL ''
380 ${concatMapStringsSep "\n" listenString redirectListen}
382 server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
385 return ${toString vhost.redirectCode} https://$host$request_uri;
392 ${concatMapStringsSep "\n" listenString hostListen}
393 server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
394 ${optionalString (hasSSL && vhost.http2 && !oldHTTP2) ''
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"};
401 ${optionalString hasSSL ''
402 ssl_certificate ${vhost.sslCertificate};
403 ssl_certificate_key ${vhost.sslCertificateKey};
405 ${optionalString (hasSSL && vhost.sslTrustedCertificate != null) ''
406 ssl_trusted_certificate ${vhost.sslTrustedCertificate};
408 ${optionalString vhost.rejectSSL ''
409 ssl_reject_handshake on;
411 ${optionalString (hasSSL && vhost.kTLS) ''
412 ssl_conf_command Options KTLS;
415 ${mkBasicAuth vhostName vhost}
417 ${optionalString (vhost.root != null) "root ${vhost.root};"}
419 ${optionalString (vhost.globalRedirect != null) ''
421 return ${toString vhost.redirectCode} http${optionalString hasSSL "s"}://${vhost.globalRedirect}$request_uri;
425 ${mkLocations vhost.locations}
431 mkLocations = locations: concatStringsSep "\n" (map (config: ''
432 location ${config.location} {
433 ${optionalString (config.proxyPass != null && !cfg.proxyResolveWhileRunning)
434 "proxy_pass ${config.proxyPass};"
436 ${optionalString (config.proxyPass != null && cfg.proxyResolveWhileRunning) ''
437 set $nix_proxy_target "${config.proxyPass}";
438 proxy_pass $nix_proxy_target;
440 ${optionalString config.proxyWebsockets ''
441 proxy_http_version 1.1;
442 proxy_set_header Upgrade $http_upgrade;
443 proxy_set_header Connection $connection_upgrade;
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}
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;
466 auth_basic_user_file ${auth_file};
468 mkHtpasswd = name: authDef: pkgs.writeText "${name}.htpasswd" (
469 concatStringsSep "\n" (mapAttrsToList (user: password: ''
470 ${user}:{PLAIN}${password}
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"));
482 enable = mkEnableOption "Nginx Web Server";
484 statusPage = mkOption {
488 Enable status page reachable from localhost on http://127.0.0.1/nginx_status.
492 recommendedTlsSettings = mkOption {
496 Enable recommended TLS settings.
500 recommendedOptimisation = mkOption {
504 Enable recommended optimisation settings.
508 recommendedBrotliSettings = mkOption {
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`.
519 recommendedGzipSettings = mkOption {
523 Enable recommended gzip settings.
524 Learn more about compression in Gzip format [here](https://docs.nginx.com/nginx/admin-guide/web-server/compression/).
528 recommendedZstdSettings = mkOption {
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`.
539 recommendedProxySettings = mkOption {
543 Whether to enable recommended proxy settings if a vhost does not specify the option manually.
547 proxyTimeout = mkOption {
552 Change the proxy related timeouts in recommendedProxySettings.
556 defaultListen = mkOption {
557 type = with types; listOf (submodule {
561 description = "IP address.";
565 description = "Port number.";
571 description = "Enable SSL.";
573 proxyProtocol = mkOption {
575 description = "Enable PROXY protocol.";
578 extraParameters = mkOption {
580 description = "Extra parameters of this listen directive.";
582 example = [ "backlog=1024" "deferred" ];
587 example = literalExpression ''
589 { addr = "10.0.0.12"; proxyProtocol = true; ssl = true; }
590 { addr = "0.0.0.0"; }
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.
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::]" ]'';
607 If vhosts do not specify listenAddresses, use these addresses by default.
608 This is akin to writing `defaultListen = [ { addr = "0.0.0.0" } ]`.
612 defaultHTTPListenPort = mkOption {
617 If vhosts do not specify listen.port, use these ports for HTTP by default.
621 defaultSSLListenPort = mkOption {
626 If vhosts do not specify listen.port, use these ports for SSL by default.
630 defaultMimeTypes = mkOption {
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";
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
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);
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`.
658 additionalModules = mkOption {
660 type = types.listOf (types.attrsOf types.anything);
661 example = literalExpression "[ pkgs.nginxModules.echo ]";
663 Additional [third-party nginx modules](https://www.nginx.com/resources/wiki/modules/)
664 to install. Packaged modules are available in `pkgs.nginxModules`.
668 logError = mkOption {
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.
685 preStart = mkOption {
689 Shell commands executed before the service's nginx is started.
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.
709 appendConfig = mkOption {
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).
722 commonHttpConfig = mkOption {
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"';
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.
740 httpConfig = mkOption {
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.
751 streamConfig = mkOption {
756 listen 127.0.0.1:53 udp reuseport;
758 proxy_pass 192.168.0.1:53535;
762 Configuration lines to be set inside the stream block.
766 eventsConfig = mkOption {
770 Configuration lines to be set inside the events block.
774 appendHttpConfig = mkOption {
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.
784 enableReload = mkOption {
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`.
794 enableQuicBPF = mkOption {
798 Enables routing of QUIC packets using eBPF. When enabled, this allows
799 to support QUIC connection migration. The directive is only supported
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`.
810 description = "User account under which nginx runs.";
816 description = "Group account under which nginx runs.";
819 serverTokens = mkOption {
822 description = "Show nginx version in headers and error pages.";
825 clientMaxBodySize = mkOption {
828 description = "Set nginx global client_max_body_size.";
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.";
838 sslProtocols = mkOption {
840 default = "TLSv1.2 TLSv1.3";
841 example = "TLSv1 TLSv1.1 TLSv1.2 TLSv1.3";
842 description = "Allowed TLS protocol versions.";
845 sslDhparam = mkOption {
846 type = types.nullOr types.path;
848 example = "/path/to/dhparams.pem";
849 description = "Path to DH parameters file.";
852 proxyResolveWhileRunning = mkOption {
856 Resolves domains of proxyPass targets at runtime
857 and not only at start, you have to set
858 services.nginx.resolver, too.
862 mapHashBucketSize = mkOption {
863 type = types.nullOr (types.enum [ 32 64 128 ]);
866 Sets the bucket size for the map variables hash tables. Default
867 value depends on the processor’s cache line size.
871 mapHashMaxSize = mkOption {
872 type = types.nullOr types.ints.positive;
875 Sets the maximum size of the map variables hash tables.
879 serverNamesHashBucketSize = mkOption {
880 type = types.nullOr types.ints.positive;
883 Sets the bucket size for the server names hash tables. Default
884 value depends on the processor’s cache line size.
888 serverNamesHashMaxSize = mkOption {
889 type = types.nullOr types.ints.positive;
892 Sets the maximum size of the server names hash tables.
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'';
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.
909 proxyCachePath = mkOption {
910 type = types.attrsOf (types.submodule ({ ... }: {
912 enable = mkEnableOption "this proxy cache path entry";
914 keysZoneName = mkOption {
917 example = "my_cache";
918 description = "Set name to shared memory zone.";
921 keysZoneSize = mkOption {
925 description = "Set size to shared memory zone.";
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.
939 useTempPath = mkOption {
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.
952 inactive = mkOption {
957 Cached data that has not been accessed for the time specified by
958 the inactive parameter is removed from the cache, regardless of
967 description = "Set maximum cache size";
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.
978 resolver = mkOption {
979 type = types.submodule {
981 addresses = mkOption {
982 type = types.listOf types.str;
984 example = literalExpression ''[ "[::1]" "127.0.0.1:5353" ]'';
985 description = "List of resolvers to use";
992 By default, nginx caches answers using the TTL value of a response.
993 An optional valid parameter allows overriding it
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
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
1017 Configures name servers used to resolve names of upstream servers into addresses
1022 upstreams = mkOption {
1023 type = types.attrsOf (types.submodule {
1025 servers = mkOption {
1026 type = types.attrsOf (types.submodule {
1027 freeformType = types.attrsOf (types.oneOf [ types.bool types.int types.str ]);
1033 Marks the server as a backup server. It will be passed
1034 requests when the primary servers are unavailable.
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.
1045 example = lib.literalMD "see [](#opt-services.nginx.upstreams)";
1047 extraConfig = mkOption {
1051 These lines go to the end of the upstream verbatim.
1057 Defines a group of servers to use as proxy target.
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; };
1074 servers."unix:/run//memcached/memcached.sock" = {};
1079 virtualHosts = mkOption {
1080 type = types.attrsOf (types.submodule (import ./vhost-options.nix {
1086 example = literalExpression ''
1088 "hydra.example.com" = {
1092 proxyPass = "http://localhost:3000";
1097 description = "Declarative vhost config";
1099 validateConfigFile = lib.mkEnableOption "validating configuration with pkgs.writeNginxConfig" // {
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.
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" ])
1118 config = mkIf cfg.enable {
1121 deprecatedSSL = name: config: optional config.enableSSL
1123 config.services.nginx.virtualHosts.<name>.enableSSL is deprecated,
1124 use config.services.nginx.virtualHosts.<name>.onlySSL instead.
1127 in flatten (mapAttrsToList deprecatedSSL virtualHosts);
1131 hostOrAliasIsNull = l: l.root == null || l.alias == null;
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.";
1139 assertion = all (host: with host;
1140 count id [ addSSL (onlySSL || enableSSL) forceSSL rejectSSL ] <= 1
1141 ) (attrValues virtualHosts);
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.
1151 assertion = any (host: host.rejectSSL) (attrValues virtualHosts) -> versionAtLeast cfg.package.version "1.19.4";
1153 services.nginx.virtualHosts.<name>.rejectSSL requires nginx version
1154 1.19.4 or above; see the documentation for services.nginx.package.
1159 assertion = all (host: !(host.enableACME && host.useACMEHost != null)) (attrValues virtualHosts);
1161 Options services.nginx.service.virtualHosts.<name>.enableACME and
1162 services.nginx.virtualHosts.<name>.useACMEHost are mutually exclusive.
1167 assertion = cfg.package.pname != "nginxQuic" && cfg.package.pname != "angieQuic" -> !(cfg.enableQuicBPF);
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;`.
1176 assertion = cfg.package.pname != "nginxQuic" && cfg.package.pname != "angieQuic" -> all (host: !host.quic) (attrValues virtualHosts);
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;`.
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
1188 # Options' priorities are the following at the moment:
1189 # listen (vhost) > defaultListen (server) > listenAddresses (vhost) > defaultListenAddresses (server)
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 != []);
1197 hasAtLeastVhostHttpListener = if host.listen != [] then hasAtLeastHttpListener host.listen else (host.listenAddresses != []);
1198 vhostAuthority = host.listen != [] || (cfg.defaultListen == [] && host.listenAddresses != []);
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);
1205 services.nginx.virtualHosts.<name>.enableACME requires a HTTP listener
1206 to answer to ACME requests.
1211 assertion = cfg.resolver.ipv4 || cfg.resolver.ipv6;
1213 At least one of services.nginx.resolver.ipv4 and services.nginx.resolver.ipv6 must be true.
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 ([
1228 ] ++ lib.optional enableIPv6 "[::]");
1229 locations."/nginx_status" = {
1234 ${optionalString enableIPv6 "allow ::1;"}
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;
1255 startLimitIntervalSec = 60;
1257 ExecStart = execCommand;
1260 "${pkgs.coreutils}/bin/kill -HUP $MAINPID"
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";
1278 ProtectProc = "invisible";
1279 # New file permissions
1280 UMask = "0027"; # 0640 / 0750
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" ];
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;
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;
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" ];
1313 environment.etc."nginx/nginx.conf" = mkIf cfg.enableReload {
1314 source = configFile;
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);
1340 ExecCondition = "/run/current-system/systemd/bin/systemctl -q is-active nginx.service";
1341 ExecStart = "/run/current-system/systemd/bin/systemctl reload nginx.service";
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") {
1364 isSystemUser = true;
1365 uid = config.ids.uids.nginx;
1369 users.groups = optionalAttrs (cfg.group == "nginx") {
1370 nginx.gid = config.ids.gids.nginx;
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_*"
1380 services.logrotate.settings.nginx = mapAttrs (_: mkDefault) {
1381 files = "/var/log/nginx/*.log";
1382 frequency = "weekly";
1383 su = "${cfg.user} ${cfg.group}";
1386 delaycompress = true;
1387 postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";