A Static Webserver

This is a configuration for a webserver that serves static files from /srv/www/<subdomain>. The first part includes the configuration.nix itself and some hardware configuration for a system running on a Linode. The second part configures the actual webserver, and uses a little custom function in the process.

Part 1

# configuration.nix

{
  imports = [
    ./boot.nix
    ./fs.nix
    ./net.nix
    ./users.nix
    ./web
  ]

  services.openssh.enable = true;

  system.stateVersion = "20.09";
}
# boot.nix

{ modulesPath, ... }: {
  imports = [
    # some defaults for virtual machines
    (modulesPath + "/profiles/qemu-guest.nix")
  ];

  boot = {
    # serial console for Linode's web shell
    kernelParams = [ "console=ttyS0" ];
    
    # for SATA and SCSI
    initrd.availableKernelModules = [ "ahci" "sd_mod" ];

    loader.grub = {
      enable = true;

      # configure but don't install GRUB (Linode already manages it)
      device = "nodev";

      # put kernel in /boot
      copyKernels = true;

      # use partition labels, drive UUIDs can change
      fsIdentifier = "label";

      extraConfig = "serial; terminal_input serial; terminal_output serial";
    };
  };
}
# fs.nix

{
  fileSystems = {
    "/" = {
      device = "/dev/disk/by-label/nixos";
      fsType = "ext4";
    };

    "/srv" = {
      device = "/dev/disk/by-label/srv";
      fsType = "ext4";
    };
  };

  swapDevices = [
    { device = "/dev/disk/by-label/swap"; }
  ];
}
# net.nix

{
  networking = {
    useDHCP = false;

    # the physical location of the same network interface can change between
    # boots, use `ethX` instead of `enpXsY` for interface names
    usePredictableInterfaceNames = false;
    interfaces.eth0.useDHCP = true;
  };
}
# users.nix

{
  users = {
    mutableUsers = false;

    users.admin = {
      isNormalUser = true;
      extraGroups = [ "wheel" ];
      hashedPassword = "/*snip*/";

      # ssh auto-login with public key
      openssh.authorizedKeys.keys = [
        "/*snip*/"
      ];
    };
  };
}

Part 2

In this part we'll set up nginx to use TLS and host two subdomains: www and files. We'll write a function to help us configure both subdomains the same way without repeating ourselves.

# web/default.nix

{
  imports = [
    ./www.nix
    ./files.nix
  ];

  networking.firewall.allowedTCPPorts = [ 80 443 ];

  # use ACME to get certificates for TLS (with Let's Encrypt as the default CA)
  security.acme = {
    acceptTerms = true;
    email = "me@example.com";
  };

  services.nginx = {
    enable = true;

    # by default, nginx will restart instead of reload on config change
    enableReload = true;

    # some sane defaults
    recommendedOptimisation = true;
    recommendedTlsSettings = true;
    recommendedGzipSettings = true;
    recommendedProxySettings = true;
  };
}
# web/vhost.nix

# this is a function that returns the settings we want each subdomain to have by
# default, to help us not repeat ourselves
sub: let
  domain = "example.com";
  root = /srv/www;
# notice how we can nest `let ... in`. it's helpful for temp values.
# we need `domain` and `root` to define `subdomain` and `subroot` but we don't
# need them elsewhere
in let
  subdomain = if sub == null
    then domain
    else "${sub}.${domain}";
  subroot = if sub == null
    then root
    else "${root}/${sub}";
# using `rec` because `definition` references `name` and `settings`
in rec {
  name = subdomain;
  settings = {
    forceSSL = true;
    enableACME = true;
    root = "${subroot}";

    locations."/".tryFiles = "$uri $uri/ =404";
  };

  # set that looks like `services.nginx.virtualHosts` for convenience
  definition = { ${name} = settings; };
}
# web/www.nix

let
  vhost = import ./vhost.nix;
in let
  root = vhost null;
  www = vhost "www";
in {
  services.nginx.virtualHosts = www.definition // {
    # make `example.com` redirect to `www.example.com`
    "${root.name}" = root.settings // {
      locations."/".return = "301 https://${www.name}$request_uri";
    };
  };
}
# web/files.nix

{ services.nginx.virtualHosts = (import ./vhost.nix "files").definition; }