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