nix defaults
Jan 30, 2026 - ⧖ 15 minPublished as part of 'nix' series.
Sensible Nix Defaults
If you're using Nix you are probably more accustomed to nonsense, and I am no exception. In an effort to remain sane, I use flakes to create NixOS modules which I can resuse across my systems, and I'd like to share a very basic one today: the "defaults" flake. To clarify, this is used when building machines a la nixpkgs.lib.nixosSystem and not for packaging programs.
You can find this flake mirrored here:
Okay, let's first setup our flake, nothing too fancy here. If you're not familiar with NixOS and Flakes this post will not be explaining them.
The nix-index-database is included so we can use the , syntax to run programs.
The Flake
Here it is in all it's glory. You can skip ahead to the bottom (or read the README.md in repo) if you just want to know how to install and configure it.
https://github.com/xvrqt/defaults-flake/
# flake.nix
{
inputs = {
# Used to keep the other inputs in lock-step
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
# Used to track options
nix-index-database.url = "github:nix-community/nix-index-database";
nix-index-database.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, nix-index-database, ... }:
let
forAllSystems = function:
nixpkgs.lib.genAttrs [
"x86_64-linux"
"aarch64-linux"
] (system: function nixpkgs.legacyPackages.${system});
in
{
nixosModules = forAllSystems
(pkgs: {
default = { lib, config, ... }: {
imports = [
nix-index-database.nixosModules.nix-index
(import ./nixosModule.nix {
inherit lib pkgs config;
})
];
};
});
};
}
The Module
All the interesting stuff happens in here, first, lets tell our system we're using Flakes lol.
NixOS Meta Config
Then only allow users with super user privileges to rebuild the system - for obvious reasons.
I also like to set up a daily garbage collection and optimization (optimisation??) job to prevent accumulating too much cruft over time. I do wish there wa a way to say "only keep the last N generations" but instead you must specify it by how old they are. It picked 30 days, but tweak to your heart's content.
Lastly I only ever want to go 10 generations back; otherwise a day of tinkering can bloat your system for a month until its cleaned up.
# nixosModule.nix
{ lib, pkgs, config, ... }: {
config = {
nix = {
settings = {
# Enable NixOS Flakes
experimental-features = [
"nix-command"
"flakes"
];
# Only allow those with admin privileges invoke Nix commands
allowed-users = [ "@wheel" ];
};
# Optimise the Nix-Store once a day
optimise = lib.mkDefault {
automatic = true;
dates = [ "daily" ];
};
# Automatically Clean-out the Nix-Store once a day
gc = lib.mkDefault {
automatic = true;
options = "--delete-older-than 30d";
dates = "daily";
};
};
# Only keep 10 generations maximum
boot.loader.systemd-boot.configurationLimit = lib.mkDefault 10;
};
}
Timezones and Units
I have servers all over the world and it's nice to set the their clocks, and other units to correspond with which I am familiar. Adjust these to your particular situation.
# nixosModule.nix
{ lib, pkgs, config, ... }: {
# Previous Sections
# - Nix Settings
# Timezone to the West Coast (Best Coast) by default
time.timeZone = lib.mkDefault "America/Los_Angeles";
# Select internationalisation properties.
i18n.defaultLocale = lib.mkDefault "en_US.UTF-8";
# Get specific with encoding
i18n.extraLocaleSettings = lib.mkDefault {
LC_ADDRESS = "en_US.UTF-8";
LC_IDENTIFICATION = "en_US.UTF-8";
LC_MEASUREMENT = "en_US.UTF-8";
LC_MONETARY = "en_US.UTF-8";
LC_NAME = "en_US.UTF-8";
LC_NUMERIC = "en_US.UTF-8";
LC_PAPER = "en_US.UTF-8";
LC_TELEPHONE = "en_US.UTF-8";
LC_TIME = "en_US.UTF-8";
};
}
Default Programs
I try to keep this lean, so these are mostly meta programs, or programs required on virtually every system.
nh1 stands for "nix-helper" and I like to use it to rebuild my system because the syntax is shorter (nh os switch .) and it pretty prints the dependency graph, and system diff both during and after it finishes running. It can make things a lot more clear when your system is taking a long time to rebuild.
comma2 allows you to run programs you don't have installed directly from the command line, and then vanishes them from you path. I find it indispensibly convenient, and way more ergnomic than nesting nix-shell -p <pkg> repreatedly. To use, simply $ , <pkg> and it will run it for you. Nix is so declaritive it can be annoying to have to do an entire rebuild just to use one program for the day, and this eases that pain. I have even used it to run a video editor so I could work on a project and then when I finished I could just close out and the program was gone from my system.
openssh is a must have, and I recompile it with all the compile time hardening flags on to help ensure this critical piece of security infrastructure is less likely to compromise me.
Lastly, please remember to install your shell (unless you're a sh user, good for you). I use zsh and I like it a lot. I will probably switch to fish one day, but it is not this day.
# nixosModule.nix
{ lib, pkgs, config, ... }: {
# Previous Sections
# - Nix Settings
# - Timezones & Units
programs = {
# Nix helper functions
nh = {
enable = lib.mkDefault true;
# Where I always mount my flake
flake = lib.mkDefault "/key/flake";
};
# Run programs without nix-shell
# Usage: $ , cowsay "Hello"
# It will install cowsay temporily, run the program in your shell, and then
# remove it from your PATH again afterwards.
nix-index-database.comma.enable = lib.mkDefault true;
# Set the ssh package to be the hardened version of itself
ssh.package = lib.mkDefault (harden pkgs.openssh);
# Ensure a shell is enabled
zsh.enable = lib.mkDefault true;
};
Environment
I hate the nano editor so I ensure that my environment variables are set such that they use my default editor of chose, hx3 which is written in Rust and is like nvim but with all my favorite nvim plugins preinstalled (and much snappier). I choose to recompile this for my machine since I spend most of my day using it - but I have provided an option to disable (e.g. on my low powered servers where I am rarely editing things directly, and don't want to spend an hour compiling a text editor).
I also include hyfetch4 which is like neofetch but gayer. All my shells print the system information on login to help me remember which one I ssh'd into.
I include the package for the shell we enabled above as the only permissible shell, and also clear out all other default packages (looking at you, nano).
# nixosModule.nix
{ lib, pkgs, config, ... }: {
# Previous Sections
# - Nix Settings
# - Timezones & Units
# - Programs
# Packages every system needs
environment = {
# So we don't get stuck with a shitty editor
variables = {
# We use mkOverried 990 because NixOS be default sets a default value
# (nano; priority 1000) to these environment variables
EDITOR = lib.mkOverride 990 "hx";
VISUAL = lib.mkOverride 990 "hx";
SUDO_EDITOR = lib.mkOverride 990 "hx";
};
# These packages are automatically available to all users
systemPackages = [
# Default text editor
(optimizeRust pkgs.helix)
# Pretty print system information upon shell login
(optimizeRust pkgs.hyfetch)
];
# Remove all other default packages so nothing sneaks in
defaultPackages = lib.mkDefault [ ];
# Permissible login shells (sh is implicitly included)
shells = lib.mkDefault [ pkgs.zsh ];
};
}
Services
The only service we run is openssh so we can login remotely. I actually use another module to auto configure this to allow my computers to talk with each other, but we can still set some sensible security defaults while we're here.
The usual stuff, like not allowing login as root, and only allowing authentication with keys. I also like to use DNS resolution since I run my own DNS and have my own search domains. If you hardcode all your IPs then I would disable this.
We don't set the package here, because by default it uses programs.ssh.package which we set above.
# nixosModule.nix
{ lib, pkgs, config, ... }: {
# Previous Sections
# - Nix Settings
# - Timezones & Units
# - Programs
# - Environment
services = {
openssh = {
enable = lib.mkDefault true;
settings = {
# Allows hostnames to be FQDN (sshd will check their DNS record matches)
UseDns = lib.mkDefault true;
# SSH should check the permissions of the identity files and directories
StrictModes = lib.mkDefault true;
# We don't need to log in as root
PermitRootLogin = lib.mkDefault "no";
# SSH Keys Only
PasswordAuthentication = lib.mkDefault false;
};
};
};
}
Security
A few final tweaks to security, such as enabling sudo and exempting my users from requiring a password each time I use it. Also, preventing anyone from logging in as root.
I also setup my preferred settings for syscall auditing, but this is disabled by default (i.e. I only enable it on servers, but not personal use computers). You can enable it with defaults.auditing = true; in your NixOS Module.
# nixosModule.nix
{ lib, pkgs, config, ... }: {
# Previous Sections
# - Nix Settings
# - Timezones & Units
# - Programs
# - Environment
# - Services
# Allow sudoers to not require a password
security = {
sudo = {
enable = lib.mkDefault true;
# Don't challenge memebers of 'wheel'
wheelNeedsPassword = lib.mkDefault false;
};
# Locked down root user as a default
users = {
users = {
root = lib.mkDefault {
# Default Shell
shell = pkgs.zsh;
# Added to the list of sudoers
extraGroups = [ "networkmanager" "wheel" ];
# Disable logging in as root
hashedPassword = lib.mkDefault "!";
initialHashedPassword = lib.mkDefault "!";
};
};
};
# Enable Linux Kernel Auditing
auditd = lib.mkIf auditCheck {
enable = lib.mkDefault true;
settings = {
# Number of log files to keep
num_logs = lib.mkDefault 8;
# Maximum logfile size, in MiB
max_log_file = lib.mkDefault 32;
# What to do when we're out of log files
max_log_file_action = lib.mkDefault "rotate";
};
};
audit = lib.mkIf auditCheck {
enable = lib.mkDefault true;
# -a exit,always -> Run audit when syscall is loaded, no matter what
# -F arch=b64 -> Only log syscalls made by 64bit processes
# -S execve -> Only monitor the execve system call flag
# Typically invoked by a shell, this monitors every attempt for a
# 64bit process to execute another program
rules = lib.mkDefault [
"-a exit,always -F arch=b64 -S execve"
];
};
};
}
Using the Flake
It's not difficult to use, just add it to your inputs, include the default NixOS Module in your modules list, and tweak the options as you see fit. If you fork and edit the default editor, you might want to change the function which optimizes it. I included on for gcc as well as the one I used for Rust, which you can find at the top of the nixosModule.nix file.
Installation
Add this flake to your NixOS Configuration list of modules flake inputs, and add its NixOS Module to the outputs:
{
inputs = {
nixpkgs.url = "github:NixOs/nixpkgs";
defaults.url = "github:xvrqt/defaults-flake";
# etc...
};
outputs = {defaults, ...} @ inputs: {
nixos-configuration = nixpkgs.lib.nixosSystem {
inherit pkgs;
specialArgs = { inherit inputs; };
modules = [
defaults.nixosModules.${system}.default # <-- Important Bit
./my-nix-configuration.nix
# etc...
];
};
};
}
Options
There are three options, which can you set using the following NixOS module.
{
defaults = {
# Whether or not to enable system wide auditing.
# Defaults to FALSE. I only enable it on servers.
auditing = false;
packages = {
# Whether to recompile your editor and hyfetch
# Defaults to TRUE.
optimize= true;
# Whether to recompile openssh with hardening options
# Defaults to TRUE.
hardening = true;
};
};
}