Adding Custom Nginx Options in NixOS
2024-04-22NixOS comes with a pretty good set of options for configuring nginx the nix way, but there are still plenty of things for which you have to fall back to using the extraConfig
option and write plain nginx config lines.
This is fine most of the time, but if you have a larger set of options that you're applying often, it can be a lot nicer to have proper nix options for it.
Luckily the NixOS option is flexible enough to allow you to define your own set of options to be added to the nginx. You can define your own services.nginx.virtualHosts
and services.nginx.virtualHosts.<name>.locations
submodules which nix will recursively merge into the upstream options. And to add the config lines from your custom option to the generated nginx configuration, you can set the extraConfig
value in the config
field of the submodule.
For example, here is a NixOS module that adds a boolean limitToLan
option both to the virtualHosts
and locations
submodules, which when enabled configures nginx to only allow clients from specific ip ranges to access the site.
{
config,
lib,
...
}:
with lib; let
hostOptions = {name, ...}: let
hostCfg = config.services.nginx.virtualHosts.name;
in {
options =
{
locations = mkOption {
type = types.attrsOf (types.submodule (locationOptions name));
};
}
// (addedOptions hostCfg);
config = addedConfig hostCfg;
};
locationOptions = host: {name, ...}: let
locationCfg = config.services.nginx.virtualHosts.host.locations.name;
in {
options = addedOptions;
config = addedConfig locationCfg;
};
addedOptions = cfg: {
limitToLan = mkOption {
type = types.bool;
default = false;
description = "Only allow lan/vpn clients";
};
extraConfig = mkOption {
apply = value: let
cfgLines = optionalString cfg.limitToLan ''
allow 10.0.8.0/24; # vpn
allow 127.0.0.0/8; # localhost
allow 172.16.0.0/12; # docker
allow 192.168.1.0/24; # lan
deny all;
'';
in
value + cfgLines;
};
};
addedConfig = cfg: {
extraConfig = optionalString cfg.limitToLan ''
allow 10.0.8.0/24; # vpn
allow 127.0.0.0/8; # localhost
allow 172.16.0.0/12; # docker
allow 192.168.1.0/24; # lan
deny all;
'';
};
in {
options = {
services.nginx.virtualHosts = mkOption {
# nix will merge the `type` we set with the `type` of the upstream options.
type = types.attrsOf (types.submodule hostOptions);
};
};
}
Having a separate config option reduces the clutter in the actual configuration and makes it easy to change the allow ip ranges for all sites that you want to have protected without having to update each site individually.