Nginx

From NixOS Wiki
Revision as of 17:10, 26 July 2018 by imported>Ledettwy (Add entry for serving static files from /nix/store over nginx and having caching work correctly)

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.

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.

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

services.nginx = {
  enable = true;
  virtualHosts."blog.example.com" = {
    enableACME = true;
    forceSSL = true;
    root = "/var/www/blog";
    locations."~ \.php$".extraConfig = ''
      fastcgi_pass 127.0.0.1:9000;
      fastcgi_index index.php;
    '';
  };
};
services.mysql = {
  enable = true;
  package = pkgs.mariadb;
};
services.phpfpm.poolConfigs.mypool = ''
  listen = 127.0.0.1:9000
  user = nobody
  pm = dynamic
  pm.max_children = 5
  pm.start_servers = 2 
  pm.min_spare_servers = 1 
  pm.max_spare_servers = 3
  pm.max_requests = 500
'';


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; };
    };
};

Correct Caching when Serving Static Files from /nix/store

Since the dates for all files in /nix/store are set to 1 second after the unix epoch, attempting to serve them over nginx can result in caching issues. etags can be used to resolve this, but nginx's built-in etags depend on the file modification time and size, which isn't good enough for us. To have caching work reliably, we'll construct our own etag:

locations."/my/static/file.txt" =
  let file = pkgs.writeText "file.txt" "this is a static file!";
  in 
  { alias = file;
    extraConfig = ''
      etag off;
      add_header etag "\"${builtins.substring 11 32 file.outPath}\"";
      '';
  }