Nginx
Nginx is a lightweight webserver. Configuration is handled using the services.nginx.
options.
Let's Encrypt certificates
The nginx module for NixOS has native support for Let's encrypt certificates; services.nginx.+acme
. The NixOS Manual, Chapter 20. SSL/TLS Certificates with ACME explains it in detail.
Minimal Example
Assuming that myhost.org
resolves to the ip address of your host and port 80 and 443 has been opened.
services.nginx.enable = true;
services.nginx.virtualHosts."myhost.org" = {
addSSL = true;
enableACME = true;
root = "/var/www/myhost.org";
};
This will set up nginx to serve files for myhost.org
, automatically request an ACME SSL Certificate and will configure systemd timers to renew the certificate if required.
Troubleshooting
Rate limiting
The ACME server for Let's encrypt has rate limits. There is a known issue[1] with how NixOS handles automatic certificate generation wherein it is trivial to hit the limits when enabling multiple domains or sub-domains at once.
When hitting the limit, the logs will show as follows:
Mar 30 14:07:38 HOSTNAME systemd[1]: Failed to start Renew ACME Certificate for example.com. ... Mar 30 14:08:10 HOSTNAME acme-example.com-start[25915]: 2018-03-30 18:08:10,566:DEBUG:acme.client:540: JWS payload: Mar 30 14:08:10 HOSTNAME acme-example.com-start[25915]: { Mar 30 14:08:10 HOSTNAME acme-example.com-start[25915]: "resource": "new-reg" Mar 30 14:08:10 HOSTNAME acme-example.com-start[25915]: } ... Mar 30 14:08:10 HOSTNAME acme-example.com-start[25915]: Connection: close Mar 30 14:08:10 HOSTNAME acme-example.com-start[25915]: { Mar 30 14:08:10 HOSTNAME acme-example.com-start[25915]: "type": "urn:acme:error:rateLimited", Mar 30 14:08:10 HOSTNAME acme-example.com-start[25915]: "detail": "Error creating new registration :: too many registrations for this IP: see https://letsencrypt.org/docs/rate-limits/", Mar 30 14:08:10 HOSTNAME acme-example.com-start[25915]: "status": 429 Mar 30 14:08:10 HOSTNAME acme-example.com-start[25915]: }
See #38144 for the current status.
General
Nginx is run as SystemD service nginx, so systemctl status nginx
may say something useful. If you have a problem with configuration, you can find the configuration location in the systemctl status
, it should be at /nix/store/*-nginx.conf
.
Sample setups
Static blog with ssl enforced in configuration.nix
services.nginx = {
enable = true;
virtualHosts."blog.example.com" = {
enableACME = true;
forceSSL = true;
root = "/var/www/blog";
};
};
# Optional: You can configure the email address used with Let's Encrypt.
# This way you get renewal reminders (automated by NixOS) as well as expiration emails.
security.acme.certs = {
"blog.example.com".email = "youremail@address.com";
};
LEMP stack
(Nginx/MySQL/PHP) in configuration.nix
{ config, ...}: {
services.nginx = {
enable = true;
virtualHosts."blog.example.com" = {
enableACME = true;
forceSSL = true;
root = "/var/www/blog";
locations."~ \.php$".extraConfig = ''
fastcgi_pass unix:${config.services.phpfpm.pools.mypool.socket};
fastcgi_index index.php;
'';
};
};
services.mysql = {
enable = true;
package = pkgs.mariadb;
};
services.phpfpm.pools.mypool = {
user = "nobody";
settings = {
pm = "dynamic";
"listen.owner" = config.services.nginx.user;
"pm.max_children" = 5;
"pm.start_servers" = 2;
"pm.min_spare_servers" = 1;
"pm.max_spare_servers" = 3;
"pm.max_requests" = 500;
};
};
HTTP Authentication
Nginx can require users to login using HTTP Basic Authentication. In NixOS, this is set using the `basicAuth` option:
services.nginx = {
virtualHosts."example.com" = {
basicAuth = { user = "password"; anotherUser = "..."; };
...
};
};
TLS reverse proxy
This is a "minimal" example in terms of security, see below for more tips.
services.nginx = {
enable = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
# other Nginx options
virtualHosts."example.com" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "https://127.0.0.1:12345";
proxyWebsockets = true; # needed if you need to use WebSocket
extraConfig =
# required when the target is also TLS server with multiple hosts
"proxy_ssl_server_name on;" +
# required when the server wants to use HTTP Authentication
"proxy_pass_header Authorization;"
;
};
};
};
Hardened setup with TLS and HSTS preloading
For testing your TLS configuration, you might want to visit [1]. If you configured preloading and want to apply for being included in the preloading list, check out [2]. Please read enough about preloading to understand the consequences, as it takes some effort to be removed from the list.
services.nginx = {
enable = true;
# Use recommended settings
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
# Only allow PFS-enabled ciphers with AES256
sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL";
commonHttpConfig = ''
# Add HSTS header with preloading to HTTPS requests.
# Adding this header to HTTP requests is discouraged
map $scheme $hsts_header {
https "max-age=31536000; includeSubdomains; preload";
}
add_header Strict-Transport-Security $hsts_header;
# Enable CSP for your services.
#add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always;
# Minimize information leaked to other domains
add_header 'Referrer-Policy' 'origin-when-cross-origin';
# Disable embedding as a frame
add_header X-Frame-Options DENY;
# Prevent injection of code in other mime types (XSS Attacks)
add_header X-Content-Type-Options nosniff;
# Enable XSS protection of the browser.
# May be unnecessary when CSP is configured properly (see above)
add_header X-XSS-Protection "1; mode=block";
# This might create errors
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
'';
# Add any further config to match your needs, e.g.:
virtualHosts = let
base = locations: {
inherit locations;
forceSSL = true;
enableACME = true;
};
proxy = port: base {
"/".proxyPass = "http://127.0.0.1:" + toString(port) + "/";
};
in {
# Define example.com as reverse-proxied service on 127.0.0.1:3000
"example.com" = proxy 3000 // { default = true; };
};
};