Webgateway (NGINX, HAProxy)

This role provides a stack of components that enables you to serve a web application via HTTP. In addition, you can do load balancing and configure failover support.

Versions

  • HAProxy: 2.8.x

  • Nginx: 1.24.x

Role architecture

The webgateway role uses:

  • the nginx web server

  • the HAProxy load balancer and proxy server

We provide basic config for both services. You will have to add custom configuration to serve your site.

Both services support config reload and changing the binary without downtime.

Note

Although we install nginx and HAProxy, there is no need to use them both. Since there is no connection between them w.r.t configuration, you can still use only one of them and leave the other one as is.

How we differ from what you are used to

Here is how we differ from what you already know from common Linux distributions and how you are used to configure, start, stop and maintain these packages.

  • configuration file locations:

    We do not edit files in /etc/nginx/* or /etc/haproxy/*, respectively. Since we use NixOS, files have to be edited in /etc/local, followed by a NixOS rebuild which copies them into the Nix store and activates the new configuration. To do so, run the command sudo fc-manage -b

  • service control:

    We use systemd to control processes. You can use familiar commands like sudo systemctl restart nginx to control services. However, remember that invoking sudo fc-manage -b is necessary to put configuration changes into effect. A simple restart is not sufficient. For further information, see Local Configuration.

HAProxy

Structured configuration using Nix expressions

All haproxy options are mapped into structured NixOS options. A basic example looks like this:

# /etc/local/nixos/myservice.nix
{ ... }:
{
  flyingcircus.services.haproxy = {
    enableStructuredConfig = true;

    listen."http-in" = {
      binds = [ "127.0.0.1:8002" "::1:8002" ];
      default_backend = "myapp";
    };

    backend."myapp" = {
      servers = [
        "appserver1 localhost:8001"
        "appserver2 localhost:8001"
        "appserver3 localhost:8001"
      ];
    };

  };
}

Unstructured configuration using config snippets

Put your HAProxy configuration in /etc/local/haproxy/haproxy.cfg. You can find an example config at /etc/local/haproxy/haproxy.cfg.example. Please refer to the official documentation for more details.

If you need more than just one centralized configuration file, you can use multiple files named *.cfg in the local configuration directory. They will get merged along in alphabetical order.

Changes to your custom config will cause haproxy to reload without downtime on the next fc-manage run.

The final haproxy config file can be shown with: haproxy-show-config.

nginx

We provide basic config. You have to configure at least one virtual host.

Changes to your custom config will cause nginx to reload without downtime on the next fc-manage run if the config is valid. It will display a warning if invalid settings are found in the nginx config.

Note that changes to listen directives that are incompatible with the running config may require a manual Nginx restart that drops connections. Using reuseport can avoid such situations (see below).

After building it with sudo fc-manage -b, the final nginx config file can be shown with: nginx-show-config

You can check if the config is valid with: nginx-check-config. The script also warns about potential security issues with your current config.

The recommended method is structured configuration via Nix code as described in the next section. We still support plain nginx config and structured JSON config in /etc/local/nginx.

Plain Configuration (old)

If you want to use plain Nginx configuration add the config file as /etc/local/nginx/nginx.conf. It has to contain at least one server block declaration as described in the official documentation. Your files will then be integrated with our nginx base config. Therefore, please omit the http clause. It is already set by the base config.

See /etc/local/nginx/example-configuration for an example and /etc/local/nginx/README.txt.

JSON Configuration (old)

Although not recommended anymore, JSON config can be added to /etc/local/nginx, alongside with plain nginx config files. Nix config should be used instead, as described above. JSON config supports the same options as Nix config so converting from JSON to Nix is basically just a syntax change.

See /etc/local/nginx/README.txt for an example and more info.

Logging

nginx’ access logs are stored by default in /var/log/nginx/access.log. Individual log files for virtual hosts can be defined in the corresponding configuration sections. We use the anonymized log format for GDPR conformance by default.

Add this to an extraConfig block in Nix config or your plain nginx config:

access_log /var/log/nginx/app.log;

nginx’ error logs go to systemd’s journal by default. To view them, use journalctl(1) as usual, e.g.:

$ journalctl --since -1h -u nginx

Basic auth with legacy password hashes

Starting with NixOS 23.05, nginx uses a version of libxcrypt which only supports algorithms marked as strong. You will encounter errors when password files for HTTP basic auth use algorithms like MD5 (hash prefix$1$) and SHA256 ($5$). Password hashes using these algorithms should be replaced as soon as possible.

If you still need them on 23.05/23.11, use the Nginx package which still supports all algorithms:

# /etc/local/nixos/nginx-legacy-crypt.nix
{ pkgs, ... }:
{
  services.nginx.package = pkgs.nginxLegacyCrypt;
}

A package alias under the name nginxLegacyCrypt is already available in our NixOS 22.11 release, enabling seamless platform upgrades with the same configuration.

Rate limiting

There are a few scenarios where you may need to rate limit connections going to nginx. One aspect can be DOS protection against some specific attacks like “rapid reset”. We generally keep nginx updated so that you will benefit from general counter measures against those kinds of attacks.

However, in some scenarios rate limiting may be necessary to fend off attackers. As rate limits need to be carefully adjusted to your specific application we do not enable rate limits on our platform by default.

The options available to control rate limiting in your nginx instance are:

# /etc/local/nixos/nginx-rate-limiting.nix
{ pkgs, ... }:
{
  flyingcircus.services.nginx.rateLimit = {
    enable = true;
    maxConcurrent = 200;
    maxRequestsPerSecond = 50;
    burst = 500;
  };
}