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 vhostCertNames = unique (map (hostOpts: hostOpts.certName) acmeEnabledVhosts);
11 dependentCertNames = filter (cert: certs.${cert}.dnsProvider == null) vhostCertNames; # those that might depend on the HTTP server
12 independentCertNames = filter (cert: certs.${cert}.dnsProvider != null) vhostCertNames; # those that don't depend on the HTTP server
13 virtualHosts = mapAttrs (vhostName: vhostConfig:
15 serverName = if vhostConfig.serverName != null
16 then vhostConfig.serverName
18 certName = if vhostConfig.useACMEHost != null
19 then vhostConfig.useACMEHost
23 inherit serverName certName;
24 } // (optionalAttrs (vhostConfig.enableACME || vhostConfig.useACMEHost != null) {
25 sslCertificate = "${certs.${certName}.directory}/fullchain.pem";
26 sslCertificateKey = "${certs.${certName}.directory}/key.pem";
27 sslTrustedCertificate = if vhostConfig.sslTrustedCertificate != null
28 then vhostConfig.sslTrustedCertificate
29 else "${certs.${certName}.directory}/chain.pem";
32 inherit (config.networking) enableIPv6;
34 # Mime.types values are taken from brotli sample configuration - https://github.com/google/ngx_brotli
35 # and Nginx Server Configs - https://github.com/h5bp/server-configs-nginx
36 # "text/html" is implicitly included in {brotli,gzip,zstd}_types
38 "application/atom+xml"
39 "application/geo+json"
40 "application/javascript" # Deprecated by IETF RFC 9239, but still widely used
43 "application/manifest+json"
45 "application/vnd.ms-fontobject"
47 "application/x-rss+xml"
48 "application/x-web-app-manifest+json"
49 "application/xhtml+xml"
50 "application/xliff+xml"
57 "image/vnd.microsoft.icon"
66 "text/vnd.rim.location.xloc"
72 defaultFastcgiParams = {
73 SCRIPT_FILENAME = "$document_root$fastcgi_script_name";
74 QUERY_STRING = "$query_string";
75 REQUEST_METHOD = "$request_method";
76 CONTENT_TYPE = "$content_type";
77 CONTENT_LENGTH = "$content_length";
79 SCRIPT_NAME = "$fastcgi_script_name";
80 REQUEST_URI = "$request_uri";
81 DOCUMENT_URI = "$document_uri";
82 DOCUMENT_ROOT = "$document_root";
83 SERVER_PROTOCOL = "$server_protocol";
84 REQUEST_SCHEME = "$scheme";
85 HTTPS = "$https if_not_empty";
87 GATEWAY_INTERFACE = "CGI/1.1";
88 SERVER_SOFTWARE = "nginx/$nginx_version";
90 REMOTE_ADDR = "$remote_addr";
91 REMOTE_PORT = "$remote_port";
92 SERVER_ADDR = "$server_addr";
93 SERVER_PORT = "$server_port";
94 SERVER_NAME = "$server_name";
96 REDIRECT_STATUS = "200";
99 recommendedProxyConfig = pkgs.writeText "nginx-recommended-proxy-headers.conf" ''
100 proxy_set_header Host $host;
101 proxy_set_header X-Real-IP $remote_addr;
102 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
103 proxy_set_header X-Forwarded-Proto $scheme;
104 proxy_set_header X-Forwarded-Host $host;
105 proxy_set_header X-Forwarded-Server $host;
108 proxyCachePathConfig = concatStringsSep "\n" (mapAttrsToList (name: proxyCachePath: ''
109 proxy_cache_path ${concatStringsSep " " [
110 "/var/cache/nginx/${name}"
111 "keys_zone=${proxyCachePath.keysZoneName}:${proxyCachePath.keysZoneSize}"
112 "levels=${proxyCachePath.levels}"
113 "use_temp_path=${if proxyCachePath.useTempPath then "on" else "off"}"
114 "inactive=${proxyCachePath.inactive}"
115 "max_size=${proxyCachePath.maxSize}"
117 '') (filterAttrs (name: conf: conf.enable) cfg.proxyCachePath));
119 toUpstreamParameter = key: value:
120 if builtins.isBool value
121 then lib.optionalString value key
122 else "${key}=${toString value}";
124 upstreamConfig = toString (flip mapAttrsToList cfg.upstreams (name: upstream: ''
126 ${toString (flip mapAttrsToList upstream.servers (name: server: ''
127 server ${name} ${concatStringsSep " " (mapAttrsToList toUpstreamParameter server)};
129 ${upstream.extraConfig}
133 commonHttpConfig = ''
134 # Load mime types and configure maximum size of the types hash tables.
135 include ${cfg.defaultMimeTypes};
136 types_hash_max_size ${toString cfg.typesHashMaxSize};
138 include ${cfg.package}/conf/fastcgi.conf;
139 include ${cfg.package}/conf/uwsgi_params;
141 default_type application/octet-stream;
145 if cfg.validateConfigFile
146 then pkgs.writers.writeNginxConfig
149 pid /run/nginx/nginx.pid;
150 error_log ${cfg.logError};
153 ${optionalString cfg.enableQuicBPF ''
159 ${optionalString (cfg.eventsConfig != "" || cfg.config == "") ''
165 ${optionalString (cfg.httpConfig == "" && cfg.config == "") ''
169 ${optionalString (cfg.resolver.addresses != []) ''
170 resolver ${toString cfg.resolver.addresses} ${optionalString (cfg.resolver.valid != "") "valid=${cfg.resolver.valid}"} ${optionalString (!cfg.resolver.ipv4) "ipv4=off"} ${optionalString (!cfg.resolver.ipv6) "ipv6=off"};
174 ${optionalString cfg.recommendedOptimisation ''
179 keepalive_timeout 65;
182 ssl_protocols ${cfg.sslProtocols};
183 ${optionalString (cfg.sslCiphers != null) "ssl_ciphers ${cfg.sslCiphers};"}
184 ${optionalString (cfg.sslDhparam != null) "ssl_dhparam ${cfg.sslDhparam};"}
186 ${optionalString cfg.recommendedTlsSettings ''
187 # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate
189 ssl_session_timeout 1d;
190 ssl_session_cache shared:SSL:10m;
191 # Breaks forward secrecy: https://github.com/mozilla/server-side-tls/issues/135
192 ssl_session_tickets off;
193 # We don't enable insecure ciphers by default, so this allows
194 # clients to pick the most performant, per https://github.com/mozilla/server-side-tls/issues/260
195 ssl_prefer_server_ciphers off;
199 ssl_stapling_verify on;
202 ${optionalString cfg.recommendedBrotliSettings ''
207 brotli_min_length 256;
208 brotli_types ${lib.concatStringsSep " " compressMimeTypes};
211 ${optionalString cfg.recommendedGzipSettings
212 # https://docs.nginx.com/nginx/admin-guide/web-server/compression/
219 gzip_proxied expired no-cache no-store private auth;
220 gzip_types ${lib.concatStringsSep " " compressMimeTypes};
223 ${optionalString cfg.recommendedZstdSettings ''
228 zstd_types ${lib.concatStringsSep " " compressMimeTypes};
231 ${optionalString cfg.recommendedProxySettings ''
233 proxy_connect_timeout ${cfg.proxyTimeout};
234 proxy_send_timeout ${cfg.proxyTimeout};
235 proxy_read_timeout ${cfg.proxyTimeout};
236 proxy_http_version 1.1;
237 # don't let clients close the keep-alive connection to upstream. See the nginx blog for details:
238 # https://www.nginx.com/blog/avoiding-top-10-nginx-configuration-mistakes/#no-keepalives
239 proxy_set_header "Connection" "";
240 include ${recommendedProxyConfig};
243 ${optionalString (cfg.mapHashBucketSize != null) ''
244 map_hash_bucket_size ${toString cfg.mapHashBucketSize};
247 ${optionalString (cfg.mapHashMaxSize != null) ''
248 map_hash_max_size ${toString cfg.mapHashMaxSize};
251 ${optionalString (cfg.serverNamesHashBucketSize != null) ''
252 server_names_hash_bucket_size ${toString cfg.serverNamesHashBucketSize};
255 ${optionalString (cfg.serverNamesHashMaxSize != null) ''
256 server_names_hash_max_size ${toString cfg.serverNamesHashMaxSize};
259 # $connection_upgrade is used for websocket proxying
260 map $http_upgrade $connection_upgrade {
264 client_max_body_size ${cfg.clientMaxBodySize};
266 server_tokens ${if cfg.serverTokens then "on" else "off"};
268 ${cfg.commonHttpConfig}
270 ${proxyCachePathConfig}
274 ${cfg.appendHttpConfig}
277 ${optionalString (cfg.httpConfig != "") ''
283 ${optionalString (cfg.streamConfig != "") ''
292 configPath = if cfg.enableReload
293 then "/etc/nginx/nginx.conf"
296 execCommand = "${cfg.package}/bin/nginx -c '${configPath}'";
298 vhosts = concatStringsSep "\n" (mapAttrsToList (vhostName: vhost:
300 onlySSL = vhost.onlySSL || vhost.enableSSL;
301 hasSSL = onlySSL || vhost.addSSL || vhost.forceSSL;
303 # First evaluation of defaultListen based on a set of listen lines.
304 mkDefaultListenVhost = listenLines:
305 # If this vhost has SSL or is a SSL rejection host.
306 # We enable a TLS variant for lines without explicit ssl or ssl = true.
307 optionals (hasSSL || vhost.rejectSSL)
308 (map (listen: { port = cfg.defaultSSLListenPort; ssl = true; } // listen)
309 (filter (listen: !(listen ? ssl) || listen.ssl) listenLines))
310 # If this vhost is supposed to serve HTTP
311 # We provide listen lines for those without explicit ssl or ssl = false.
312 ++ optionals (!onlySSL)
313 (map (listen: { port = cfg.defaultHTTPListenPort; ssl = false; } // listen)
314 (filter (listen: !(listen ? ssl) || !listen.ssl) listenLines));
317 if vhost.listen != [] then vhost.listen
319 if cfg.defaultListen != [] then mkDefaultListenVhost
320 # Cleanup nulls which will mess up with //.
321 # TODO: is there a better way to achieve this? i.e. mergeButIgnoreNullPlease?
322 (map (listenLine: filterAttrs (_: v: (v != null)) listenLine) cfg.defaultListen)
324 let addrs = if vhost.listenAddresses != [] then vhost.listenAddresses else cfg.defaultListenAddresses;
325 in mkDefaultListenVhost (map (addr: { inherit addr; }) addrs);
330 then filter (x: x.ssl) defaultListen
333 listenString = { addr, port, ssl, proxyProtocol ? false, extraParameters ? [], ... }:
334 # UDP listener for QUIC transport protocol.
335 (optionalString (ssl && vhost.quic) ("
336 listen ${addr}${optionalString (port != null) ":${toString port}"} quic "
337 + optionalString vhost.default "default_server "
338 + optionalString vhost.reuseport "reuseport "
339 + optionalString (extraParameters != []) (concatStringsSep " "
340 (let inCompatibleParameters = [ "accept_filter" "backlog" "deferred" "fastopen" "http2" "proxy_protocol" "so_keepalive" "ssl" ];
341 isCompatibleParameter = param: !(any (p: lib.hasPrefix p param) inCompatibleParameters);
342 in filter isCompatibleParameter extraParameters))
345 listen ${addr}${optionalString (port != null) ":${toString port}"} "
346 + optionalString (ssl && vhost.http2 && oldHTTP2) "http2 "
347 + optionalString ssl "ssl "
348 + optionalString vhost.default "default_server "
349 + optionalString vhost.reuseport "reuseport "
350 + optionalString proxyProtocol "proxy_protocol "
351 + optionalString (extraParameters != []) (concatStringsSep " " extraParameters)
354 redirectListen = filter (x: !x.ssl) defaultListen;
356 # The acme-challenge location doesn't need to be added if we are not using any automated
357 # certificate provisioning and can also be omitted when we use a certificate obtained via a DNS-01 challenge
358 acmeName = if vhost.useACMEHost != null then vhost.useACMEHost else vhost.serverName;
359 acmeLocation = optionalString ((vhost.enableACME || vhost.useACMEHost != null) && config.security.acme.certs.${acmeName}.dnsProvider == null)
360 # Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx)
361 # We use ^~ here, so that we don't check any regexes (which could
362 # otherwise easily override this intended match accidentally).
364 location ^~ /.well-known/acme-challenge/ {
365 ${optionalString (vhost.acmeFallbackHost != null) "try_files $uri @acme-fallback;"}
366 ${optionalString (vhost.acmeRoot != null) "root ${vhost.acmeRoot};"}
370 ${optionalString (vhost.acmeFallbackHost != null) ''
371 location @acme-fallback {
374 proxy_pass http://${vhost.acmeFallbackHost};
380 ${optionalString vhost.forceSSL ''
382 ${concatMapStringsSep "\n" listenString redirectListen}
384 server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
387 return ${toString vhost.redirectCode} https://$host$request_uri;
394 ${concatMapStringsSep "\n" listenString hostListen}
395 server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
396 ${optionalString (hasSSL && vhost.http2 && !oldHTTP2) ''
399 ${optionalString (hasSSL && vhost.quic) ''
400 http3 ${if vhost.http3 then "on" else "off"};
401 http3_hq ${if vhost.http3_hq then "on" else "off"};
403 ${optionalString hasSSL ''
404 ssl_certificate ${vhost.sslCertificate};
405 ssl_certificate_key ${vhost.sslCertificateKey};
407 ${optionalString (hasSSL && vhost.sslTrustedCertificate != null) ''
408 ssl_trusted_certificate ${vhost.sslTrustedCertificate};
410 ${optionalString vhost.rejectSSL ''
411 ssl_reject_handshake on;
413 ${optionalString (hasSSL && vhost.kTLS) ''
414 ssl_conf_command Options KTLS;
417 ${mkBasicAuth vhostName vhost}
419 ${optionalString (vhost.root != null) "root ${vhost.root};"}
421 ${optionalString (vhost.globalRedirect != null) ''
423 return ${toString vhost.redirectCode} http${optionalString hasSSL "s"}://${vhost.globalRedirect}$request_uri;
427 ${mkLocations vhost.locations}
433 mkLocations = locations: concatStringsSep "\n" (map (config: ''
434 location ${config.location} {
435 ${optionalString (config.proxyPass != null && !cfg.proxyResolveWhileRunning)
436 "proxy_pass ${config.proxyPass};"
438 ${optionalString (config.proxyPass != null && cfg.proxyResolveWhileRunning) ''
439 set $nix_proxy_target "${config.proxyPass}";
440 proxy_pass $nix_proxy_target;
442 ${optionalString config.proxyWebsockets ''
443 proxy_http_version 1.1;
444 proxy_set_header Upgrade $http_upgrade;
445 proxy_set_header Connection $connection_upgrade;
447 ${concatStringsSep "\n"
448 (mapAttrsToList (n: v: ''fastcgi_param ${n} "${v}";'')
449 (optionalAttrs (config.fastcgiParams != {})
450 (defaultFastcgiParams // config.fastcgiParams)))}
451 ${optionalString (config.index != null) "index ${config.index};"}
452 ${optionalString (config.tryFiles != null) "try_files ${config.tryFiles};"}
453 ${optionalString (config.root != null) "root ${config.root};"}
454 ${optionalString (config.alias != null) "alias ${config.alias};"}
455 ${optionalString (config.return != null) "return ${toString config.return};"}
456 ${config.extraConfig}
457 ${optionalString (config.proxyPass != null && config.recommendedProxySettings) "include ${recommendedProxyConfig};"}
458 ${mkBasicAuth "sublocation" config}
460 '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)));
462 mkBasicAuth = name: zone: optionalString (zone.basicAuthFile != null || zone.basicAuth != {}) (let
463 auth_file = if zone.basicAuthFile != null
464 then zone.basicAuthFile
465 else mkHtpasswd name zone.basicAuth;
468 auth_basic_user_file ${auth_file};
470 mkHtpasswd = name: authDef: pkgs.writeText "${name}.htpasswd" (
471 concatStringsSep "\n" (mapAttrsToList (user: password: ''
472 ${user}:{PLAIN}${password}
476 mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix lib;
478 oldHTTP2 = (versionOlder cfg.package.version "1.25.1" && !(cfg.package.pname == "angie" || cfg.package.pname == "angieQuic"));
484 enable = mkEnableOption "Nginx Web Server";
486 statusPage = mkOption {
490 Enable status page reachable from localhost on http://127.0.0.1/nginx_status.
494 recommendedTlsSettings = mkOption {
498 Enable recommended TLS settings.
502 recommendedOptimisation = mkOption {
506 Enable recommended optimisation settings.
510 recommendedBrotliSettings = mkOption {
514 Enable recommended brotli settings.
515 Learn more about compression in Brotli format [here](https://github.com/google/ngx_brotli/).
517 This adds `pkgs.nginxModules.brotli` to `services.nginx.additionalModules`.
521 recommendedGzipSettings = mkOption {
525 Enable recommended gzip settings.
526 Learn more about compression in Gzip format [here](https://docs.nginx.com/nginx/admin-guide/web-server/compression/).
530 recommendedZstdSettings = mkOption {
534 Enable recommended zstd settings.
535 Learn more about compression in Zstd format [here](https://github.com/tokers/zstd-nginx-module).
537 This adds `pkgs.nginxModules.zstd` to `services.nginx.additionalModules`.
541 recommendedProxySettings = mkOption {
545 Whether to enable recommended proxy settings if a vhost does not specify the option manually.
549 proxyTimeout = mkOption {
554 Change the proxy related timeouts in recommendedProxySettings.
558 defaultListen = mkOption {
559 type = with types; listOf (submodule {
563 description = "IP address.";
567 description = "Port number.";
573 description = "Enable SSL.";
575 proxyProtocol = mkOption {
577 description = "Enable PROXY protocol.";
580 extraParameters = mkOption {
582 description = "Extra parameters of this listen directive.";
584 example = [ "backlog=1024" "deferred" ];
589 example = literalExpression ''
591 { addr = "10.0.0.12"; proxyProtocol = true; ssl = true; }
592 { addr = "0.0.0.0"; }
597 If vhosts do not specify listen, use these addresses by default.
598 This option takes precedence over {option}`defaultListenAddresses` and
599 other listen-related defaults options.
603 defaultListenAddresses = mkOption {
604 type = types.listOf types.str;
605 default = [ "0.0.0.0" ] ++ optional enableIPv6 "[::0]";
606 defaultText = literalExpression ''[ "0.0.0.0" ] ++ lib.optional config.networking.enableIPv6 "[::0]"'';
607 example = literalExpression ''[ "10.0.0.12" "[2002:a00:1::]" ]'';
609 If vhosts do not specify listenAddresses, use these addresses by default.
610 This is akin to writing `defaultListen = [ { addr = "0.0.0.0" } ]`.
614 defaultHTTPListenPort = mkOption {
619 If vhosts do not specify listen.port, use these ports for HTTP by default.
623 defaultSSLListenPort = mkOption {
628 If vhosts do not specify listen.port, use these ports for SSL by default.
632 defaultMimeTypes = mkOption {
634 default = "${pkgs.mailcap}/etc/nginx/mime.types";
635 defaultText = literalExpression "$''{pkgs.mailcap}/etc/nginx/mime.types";
636 example = literalExpression "$''{pkgs.nginx}/conf/mime.types";
638 Default MIME types for NGINX, as MIME types definitions from NGINX are very incomplete,
639 we use by default the ones bundled in the mailcap package, used by most of the other
645 default = pkgs.nginxStable;
646 defaultText = literalExpression "pkgs.nginxStable";
647 type = types.package;
648 apply = p: p.override {
649 modules = lib.unique (p.modules ++ cfg.additionalModules);
652 Nginx package to use. This defaults to the stable version. Note
653 that the nginx team recommends to use the mainline version which
654 available in nixpkgs as `nginxMainline`.
655 Supported Nginx forks include `angie`, `openresty` and `tengine`.
656 For HTTP/3 support use `nginxQuic` or `angieQuic`.
660 additionalModules = mkOption {
662 type = types.listOf (types.attrsOf types.anything);
663 example = literalExpression "[ pkgs.nginxModules.echo ]";
665 Additional [third-party nginx modules](https://www.nginx.com/resources/wiki/modules/)
666 to install. Packaged modules are available in `pkgs.nginxModules`.
670 logError = mkOption {
675 The first parameter defines a file that will store the log. The
676 special value stderr selects the standard error file. Logging to
677 syslog can be configured by specifying the “syslog:” prefix.
678 The second parameter determines the level of logging, and can be
679 one of the following: debug, info, notice, warn, error, crit,
680 alert, or emerg. Log levels above are listed in the order of
681 increasing severity. Setting a certain log level will cause all
682 messages of the specified and more severe log levels to be logged.
683 If this parameter is omitted then error is used.
687 preStart = mkOption {
691 Shell commands executed before the service's nginx is started.
699 Verbatim {file}`nginx.conf` configuration.
700 This is mutually exclusive to any other config option for
701 {file}`nginx.conf` except for
702 - [](#opt-services.nginx.appendConfig)
703 - [](#opt-services.nginx.httpConfig)
704 - [](#opt-services.nginx.logError)
706 If additional verbatim config in addition to other options is needed,
707 [](#opt-services.nginx.appendConfig) should be used instead.
711 appendConfig = mkOption {
715 Configuration lines appended to the generated Nginx
716 configuration file. Commonly used by different modules
717 providing http snippets. {option}`appendConfig`
718 can be specified more than once and its value will be
719 concatenated (contrary to {option}`config` which
720 can be set only once).
724 commonHttpConfig = mkOption {
728 resolver 127.0.0.1 valid=5s;
730 log_format myformat '$remote_addr - $remote_user [$time_local] '
731 '"$request" $status $body_bytes_sent '
732 '"$http_referer" "$http_user_agent"';
735 With nginx you must provide common http context definitions before
736 they are used, e.g. log_format, resolver, etc. inside of server
737 or location contexts. Use this attribute to set these definitions
738 at the appropriate location.
742 httpConfig = mkOption {
746 Configuration lines to be set inside the http block.
747 This is mutually exclusive with the structured configuration
748 via virtualHosts and the recommendedXyzSettings configuration
749 options. See appendHttpConfig for appending to the generated http block.
753 streamConfig = mkOption {
758 listen 127.0.0.1:53 udp reuseport;
760 proxy_pass 192.168.0.1:53535;
764 Configuration lines to be set inside the stream block.
768 eventsConfig = mkOption {
772 Configuration lines to be set inside the events block.
776 appendHttpConfig = mkOption {
780 Configuration lines to be appended to the generated http block.
781 This is mutually exclusive with using config and httpConfig for
782 specifying the whole http block verbatim.
786 enableReload = mkOption {
790 Reload nginx when configuration file changes (instead of restart).
791 The configuration file is exposed at {file}`/etc/nginx/nginx.conf`.
792 See also `systemd.services.*.restartIfChanged`.
796 enableQuicBPF = mkOption {
800 Enables routing of QUIC packets using eBPF. When enabled, this allows
801 to support QUIC connection migration. The directive is only supported
803 Note that enabling this option will make nginx run with extended
804 capabilities that are usually limited to processes running as root
805 namely `CAP_SYS_ADMIN` and `CAP_NET_ADMIN`.
812 description = "User account under which nginx runs.";
818 description = "Group account under which nginx runs.";
821 serverTokens = mkOption {
824 description = "Show nginx version in headers and error pages.";
827 clientMaxBodySize = mkOption {
830 description = "Set nginx global client_max_body_size.";
833 sslCiphers = mkOption {
834 type = types.nullOr types.str;
835 # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate
836 default = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305";
837 description = "Ciphers to choose from when negotiating TLS handshakes.";
840 sslProtocols = mkOption {
842 default = "TLSv1.2 TLSv1.3";
843 example = "TLSv1 TLSv1.1 TLSv1.2 TLSv1.3";
844 description = "Allowed TLS protocol versions.";
847 sslDhparam = mkOption {
848 type = types.nullOr types.path;
850 example = "/path/to/dhparams.pem";
851 description = "Path to DH parameters file.";
854 proxyResolveWhileRunning = mkOption {
858 Resolves domains of proxyPass targets at runtime and not only at startup.
859 This can be used as a workaround if nginx fails to start because of not-yet-working DNS.
862 `services.nginx.resolver` must be set for this option to work.
867 mapHashBucketSize = mkOption {
868 type = types.nullOr (types.enum [ 32 64 128 ]);
871 Sets the bucket size for the map variables hash tables. Default
872 value depends on the processor’s cache line size.
876 mapHashMaxSize = mkOption {
877 type = types.nullOr types.ints.positive;
880 Sets the maximum size of the map variables hash tables.
884 serverNamesHashBucketSize = mkOption {
885 type = types.nullOr types.ints.positive;
888 Sets the bucket size for the server names hash tables. Default
889 value depends on the processor’s cache line size.
893 serverNamesHashMaxSize = mkOption {
894 type = types.nullOr types.ints.positive;
897 Sets the maximum size of the server names hash tables.
901 typesHashMaxSize = mkOption {
902 type = types.ints.positive;
903 default = if cfg.defaultMimeTypes == "${pkgs.mailcap}/etc/nginx/mime.types" then 2688 else 1024;
904 defaultText = literalExpression ''if config.services.nginx.defaultMimeTypes == "''${pkgs.mailcap}/etc/nginx/mime.types" then 2688 else 1024'';
906 Sets the maximum size of the types hash tables (`types_hash_max_size`).
907 It is recommended that the minimum size possible size is used.
908 If {option}`recommendedOptimisation` is disabled, nginx would otherwise
909 fail to start since the mailmap `mime.types` database has more entries
910 than the nginx default value 1024.
914 proxyCachePath = mkOption {
915 type = types.attrsOf (types.submodule ({ ... }: {
917 enable = mkEnableOption "this proxy cache path entry";
919 keysZoneName = mkOption {
922 example = "my_cache";
923 description = "Set name to shared memory zone.";
926 keysZoneSize = mkOption {
930 description = "Set size to shared memory zone.";
938 The levels parameter defines structure of subdirectories in cache: from
939 1 to 3, each level accepts values 1 or 2. Сan be used any combination of
940 1 and 2 in these formats: x, x:x and x:x:x.
944 useTempPath = mkOption {
949 Nginx first writes files that are destined for the cache to a temporary
950 storage area, and the use_temp_path=off directive instructs Nginx to
951 write them to the same directories where they will be cached. Recommended
952 that you set this parameter to off to avoid unnecessary copying of data
953 between file systems.
957 inactive = mkOption {
962 Cached data that has not been accessed for the time specified by
963 the inactive parameter is removed from the cache, regardless of
972 description = "Set maximum cache size";
978 Configure a proxy cache path entry.
979 See <https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path> for documentation.
983 resolver = mkOption {
984 type = types.submodule {
986 addresses = mkOption {
987 type = types.listOf types.str;
989 example = literalExpression ''[ "[::1]" "127.0.0.1:5353" ]'';
990 description = "List of resolvers to use";
997 By default, nginx caches answers using the TTL value of a response.
998 An optional valid parameter allows overriding it
1005 By default, nginx will look up both IPv4 and IPv6 addresses while resolving.
1006 If looking up of IPv4 addresses is not desired, the ipv4=off parameter can be
1014 By default, nginx will look up both IPv4 and IPv6 addresses while resolving.
1015 If looking up of IPv6 addresses is not desired, the ipv6=off parameter can be
1022 Configures name servers used to resolve names of upstream servers into addresses
1027 upstreams = mkOption {
1028 type = types.attrsOf (types.submodule {
1030 servers = mkOption {
1031 type = types.attrsOf (types.submodule {
1032 freeformType = types.attrsOf (types.oneOf [ types.bool types.int types.str ]);
1038 Marks the server as a backup server. It will be passed
1039 requests when the primary servers are unavailable.
1045 Defines the address and other parameters of the upstream servers.
1046 See [the documentation](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#server)
1047 for the available parameters.
1050 example = lib.literalMD "see [](#opt-services.nginx.upstreams)";
1052 extraConfig = mkOption {
1056 These lines go to the end of the upstream verbatim.
1062 Defines a group of servers to use as proxy target.
1068 "backend1.example.com:8080" = { weight = 5; };
1069 "backend2.example.com" = { max_fails = 3; fail_timeout = "30s"; };
1070 "backend3.example.com" = {};
1071 "backup1.example.com" = { backup = true; };
1072 "backup2.example.com" = { backup = true; };
1079 servers."unix:/run/memcached/memcached.sock" = {};
1084 virtualHosts = mkOption {
1085 type = types.attrsOf (types.submodule (import ./vhost-options.nix {
1091 example = literalExpression ''
1093 "hydra.example.com" = {
1097 proxyPass = "http://localhost:3000";
1102 description = "Declarative vhost config";
1104 validateConfigFile = lib.mkEnableOption "validating configuration with pkgs.writeNginxConfig" // {
1111 (mkRemovedOptionModule [ "services" "nginx" "stateDir" ] ''
1112 The Nginx log directory has been moved to /var/log/nginx, the cache directory
1113 to /var/cache/nginx. The option services.nginx.stateDir has been removed.
1115 (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "inactive" ] [ "services" "nginx" "proxyCachePath" "" "inactive" ])
1116 (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "useTempPath" ] [ "services" "nginx" "proxyCachePath" "" "useTempPath" ])
1117 (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "levels" ] [ "services" "nginx" "proxyCachePath" "" "levels" ])
1118 (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "keysZoneSize" ] [ "services" "nginx" "proxyCachePath" "" "keysZoneSize" ])
1119 (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "keysZoneName" ] [ "services" "nginx" "proxyCachePath" "" "keysZoneName" ])
1120 (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "enable" ] [ "services" "nginx" "proxyCachePath" "" "enable" ])
1123 config = mkIf cfg.enable {
1126 deprecatedSSL = name: config: optional config.enableSSL
1128 config.services.nginx.virtualHosts.<name>.enableSSL is deprecated,
1129 use config.services.nginx.virtualHosts.<name>.onlySSL instead.
1132 in flatten (mapAttrsToList deprecatedSSL virtualHosts);
1136 hostOrAliasIsNull = l: l.root == null || l.alias == null;
1139 assertion = all (host: all hostOrAliasIsNull (attrValues host.locations)) (attrValues virtualHosts);
1140 message = "Only one of nginx root or alias can be specified on a location.";
1144 assertion = all (host: with host;
1145 count id [ addSSL (onlySSL || enableSSL) forceSSL rejectSSL ] <= 1
1146 ) (attrValues virtualHosts);
1148 Options services.nginx.service.virtualHosts.<name>.addSSL,
1149 services.nginx.virtualHosts.<name>.onlySSL,
1150 services.nginx.virtualHosts.<name>.forceSSL and
1151 services.nginx.virtualHosts.<name>.rejectSSL are mutually exclusive.
1156 assertion = all (host: !(host.enableACME && host.useACMEHost != null)) (attrValues virtualHosts);
1158 Options services.nginx.service.virtualHosts.<name>.enableACME and
1159 services.nginx.virtualHosts.<name>.useACMEHost are mutually exclusive.
1164 assertion = cfg.package.pname != "nginxQuic" && cfg.package.pname != "angieQuic" -> !(cfg.enableQuicBPF);
1166 services.nginx.enableQuicBPF requires using nginxQuic package,
1167 which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;` or
1168 `services.nginx.package = pkgs.angieQuic;`.
1173 assertion = cfg.package.pname != "nginxQuic" && cfg.package.pname != "angieQuic" -> all (host: !host.quic) (attrValues virtualHosts);
1175 services.nginx.service.virtualHosts.<name>.quic requires using nginxQuic or angie packages,
1176 which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;` or
1177 `services.nginx.package = pkgs.angieQuic;`.
1182 # The idea is to understand whether there is a virtual host with a listen configuration
1183 # that requires ACME configuration but has no HTTP listener which will make deterministically fail
1185 # Options' priorities are the following at the moment:
1186 # listen (vhost) > defaultListen (server) > listenAddresses (vhost) > defaultListenAddresses (server)
1189 hasAtLeastHttpListener = listenOptions: any (listenLine: if listenLine ? proxyProtocol then !listenLine.proxyProtocol else true) listenOptions;
1190 hasAtLeastDefaultHttpListener = if cfg.defaultListen != [] then hasAtLeastHttpListener cfg.defaultListen else (cfg.defaultListenAddresses != []);
1194 hasAtLeastVhostHttpListener = if host.listen != [] then hasAtLeastHttpListener host.listen else (host.listenAddresses != []);
1195 vhostAuthority = host.listen != [] || (cfg.defaultListen == [] && host.listenAddresses != []);
1197 # Either vhost has precedence and we need a vhost specific http listener
1198 # Either vhost set nothing and inherit from server settings
1199 host.enableACME -> ((vhostAuthority && hasAtLeastVhostHttpListener) || (!vhostAuthority && hasAtLeastDefaultHttpListener))
1200 ) (attrValues virtualHosts);
1202 services.nginx.virtualHosts.<name>.enableACME requires a HTTP listener
1203 to answer to ACME requests.
1208 assertion = cfg.resolver.ipv4 || cfg.resolver.ipv6;
1210 At least one of services.nginx.resolver.ipv4 and services.nginx.resolver.ipv6 must be true.
1213 ] ++ map (name: mkCertOwnershipAssertion {
1214 cert = config.security.acme.certs.${name};
1215 groups = config.users.groups;
1216 services = [ config.systemd.services.nginx ] ++ lib.optional (cfg.enableReload || vhostCertNames != []) config.systemd.services.nginx-config-reload;
1219 services.nginx.additionalModules = optional cfg.recommendedBrotliSettings pkgs.nginxModules.brotli
1220 ++ lib.optional cfg.recommendedZstdSettings pkgs.nginxModules.zstd;
1222 services.nginx.virtualHosts.localhost = mkIf cfg.statusPage {
1223 serverAliases = [ "127.0.0.1" ] ++ lib.optional config.networking.enableIPv6 "[::1]";
1224 listenAddresses = lib.mkDefault ([
1226 ] ++ lib.optional enableIPv6 "[::]");
1227 locations."/nginx_status" = {
1232 ${optionalString enableIPv6 "allow ::1;"}
1238 systemd.services.nginx = {
1239 description = "Nginx Web Server";
1240 wantedBy = [ "multi-user.target" ];
1241 wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) vhostCertNames);
1242 after = [ "network.target" ]
1243 ++ map (certName: "acme-selfsigned-${certName}.service") vhostCertNames
1244 ++ map (certName: "acme-${certName}.service") independentCertNames; # avoid loading self-signed key w/ real cert, or vice-versa
1245 # Nginx needs to be started in order to be able to request certificates
1246 # (it's hosting the acme challenge after all)
1247 # This fixes https://github.com/NixOS/nixpkgs/issues/81842
1248 before = map (certName: "acme-${certName}.service") dependentCertNames;
1249 stopIfChanged = false;
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" ];
1312 environment.etc."nginx/nginx.conf" = mkIf cfg.enableReload {
1313 source = configFile;
1316 # This service waits for all certificates to be available
1317 # before reloading nginx configuration.
1318 # sslTargets are added to wantedBy + before
1319 # which allows the acme-finished-$cert.target to signify the successful updating
1320 # of certs end-to-end.
1321 systemd.services.nginx-config-reload = let
1322 sslServices = map (certName: "acme-${certName}.service") vhostCertNames;
1323 sslTargets = map (certName: "acme-finished-${certName}.target") vhostCertNames;
1324 in mkIf (cfg.enableReload || vhostCertNames != []) {
1325 wants = optionals cfg.enableReload [ "nginx.service" ];
1326 wantedBy = sslServices ++ [ "multi-user.target" ];
1327 # Before the finished targets, after the renew services.
1328 # This service might be needed for HTTP-01 challenges, but we only want to confirm
1329 # certs are updated _after_ config has been reloaded.
1330 before = sslTargets;
1331 after = sslServices;
1332 restartTriggers = optionals cfg.enableReload [ configFile ];
1333 # Block reloading if not all certs exist yet.
1334 # Happens when config changes add new vhosts/certs.
1335 unitConfig.ConditionPathExists = optionals (sslServices != []) (map (certName: certs.${certName}.directory + "/fullchain.pem") vhostCertNames);
1339 ExecCondition = "/run/current-system/systemd/bin/systemctl -q is-active nginx.service";
1340 ExecStart = "/run/current-system/systemd/bin/systemctl reload nginx.service";
1344 security.acme.certs = let
1345 acmePairs = map (vhostConfig: let
1346 hasRoot = vhostConfig.acmeRoot != null;
1347 in nameValuePair vhostConfig.serverName {
1348 group = mkDefault cfg.group;
1349 # if acmeRoot is null inherit config.security.acme
1350 # Since config.security.acme.certs.<cert>.webroot's own default value
1351 # should take precedence set priority higher than mkOptionDefault
1352 webroot = mkOverride (if hasRoot then 1000 else 2000) vhostConfig.acmeRoot;
1353 # Also nudge dnsProvider to null in case it is inherited
1354 dnsProvider = mkOverride (if hasRoot then 1000 else 2000) null;
1355 extraDomainNames = vhostConfig.serverAliases;
1356 # Filter for enableACME-only vhosts. Don't want to create dud certs
1357 }) (filter (vhostConfig: vhostConfig.useACMEHost == null) acmeEnabledVhosts);
1358 in listToAttrs acmePairs;
1360 users.users = optionalAttrs (cfg.user == "nginx") {
1363 isSystemUser = true;
1364 uid = config.ids.uids.nginx;
1368 users.groups = optionalAttrs (cfg.group == "nginx") {
1369 nginx.gid = config.ids.gids.nginx;
1372 boot.kernelModules = optional (versionAtLeast config.boot.kernelPackages.kernel.version "4.17") "tls";
1374 # do not delete the default temp directories created upon nginx startup
1375 systemd.tmpfiles.rules = [
1376 "X /tmp/systemd-private-%b-nginx.service-*/tmp/nginx_*"
1379 services.logrotate.settings.nginx = mapAttrs (_: mkDefault) {
1380 files = [ "/var/log/nginx/*.log" ];
1381 frequency = "weekly";
1382 su = "${cfg.user} ${cfg.group}";
1385 delaycompress = true;
1386 postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";