Nginx

From NixOS Wiki
Revision as of 21:45, 29 February 2020 by imported>Exyi (Added basic proxy and HTTP auth examples)

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

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}\"";
      '';
  }

See more