Hardening NixOS
Click to Expand Table of Contents
Securing your NixOS system begins with a philosophy of minimalism, explicit configuration, and proactive control.
⚠️ Warning: I am not a security expert, this is meant to show some of your options when hardening NixOS. You will have to judge for yourself if something fits your needs or is unnecessary for your setup. Always do your own research, hardening and isolating processes can naturally cause some issues. There are also performance tradeoffs with added protection. Take what you find useful and leave the rest, there is a lot to cover so it's easy for it to get convoluted.
Containers and VMs are beyond the scope of this chapter but can also enhance security if configured correctly.
Minimal Installation with LUKS
Begin with NixOS’s minimal installation image. This gives you a base system with only essential tools and no extras that could introduce vulnerabilities.
Use LUKS encryption to protect your data at rest, the following guide is a minimal disko encrypted installation: Encrypted Install
Secure Boot
Secure Boot helps ensure only signed, trusted kernels and bootloaders are executed at startup.
Useful Resources:
Practical Lanzaboote Secure Boot setup for NixOS: Guide:Secure Boot on NixOS with Lanzaboote
Encrypted Secrets
Never store secrets in plain text in repositories. Use something like sops-nix, which lets you keep encrypted secrets under version control declaratively.
Protect your sectets, the following guide is on setting up Sops on NixOS: Sops Encrypted Secrets
Hardening the Kernel
Given the kernel's central role, it's a frequent target for malicious actors, making robust hardening essential.
NixOS provides a hardened
profile that applies a set of security-focused
kernel and system configurations. This profile is defined in
nixpkgs/nixos/modules/profiles/hardened.nix
For users of the NixOS unstable channel, the following is applied by default:
profiles.hardened.enable = true;
Note on Future Changes:
-
It's important to be aware that the status of the hardened profile is under active discussion within the NixOS community. There is a proposal to deprecate or remove it in future releases, as discussed in this: Discourse thread
-
There is an open Pull Request regarding the above thread: PR#383438
You can also use the hardened kernel:
boot.kernelPackages = pkgs.linuxPackages_latest_hardened;
sysctl
is a tool that allows you to view or modify kernel settings and
enable/disable different features.
Check all sysctl
parameters (long output):
sysctl -a
Or a specific parameter:
sysctl -a | grep "kernel.kptr_restrict"
Check Active Linux Security Modules:
cat /sys/kernel/security/lsm
Check Kernel Configuration Options:
zcat /proc/config.gz | grep CONFIG_SECURITY_SELINUX
zcat /proc/config.gz | grep CONFIG_HARDENED_USERCOPY
zcat /proc/config.gz | grep CONFIG_STACKPROTECTOR
Using sysctl on an existing kernel
Or you can harden the kernel you're using sysctl
, the following parameters
come from the madaidans-insecurities guide with a few optimizations:
boot.kernel.sysctl = {
"fs.suid_dumpable" = false;
# prevent pointer leaks
"kernel.kptr_restrict" = 2;
# restrict kernel log to CAP_SYSLOG capability
"kernel.dmesg_restrict" = 1;
# Note: certian container runtimes or browser sandboxes might rely on the following
# restrict eBPF to the CAP_BPF capability
"kernel.unprivileged_bpf_disabled" = 1;
# should be enabled along with bpf above
# "net.core.bpf_jit_harden" = 2;
# restrict loading TTY line disciplines to the CAP_SYS_MODULE
"dev.tty.ldisk_autoload" = 0;
# prevent exploit of use-after-free flaws
"vm.unprivileged_userfaultfd" = 0;
# kexec is used to boot another kernel during runtime and can be abused
"kernel.kexec_load_disabled" = 1;
# Kernel self-protection
# SysRq exposes a lot of potentially dangerous debugging functionality to unprivileged users
# 4 makes it so a user can only use the secure attention key. A value of 0 would disable completely
"kernel.sysrq" = 4;
# disable unprivileged user namespaces, Note: Docker, NH, and other apps may need this
# "kernel.unprivileged_userns_clone" = 0; # commented out because it makes NH and other programs fail
# restrict all usage of performance events to the CAP_PERFMON capability
"kernel.perf_event_paranoid" = 3;
# Network
# protect against SYN flood attacks (denial of service attack)
"net.ipv4.tcp_syncookies" = 1;
# protection against TIME-WAIT assassination
"net.ipv4.tcp_rfc1337" = 1;
# enable source validation of packets received (prevents IP spoofing)
"net.ipv4.conf.default.rp_filter" = 1;
"net.ipv4.conf.all.rp_filter" = 1;
"net.ipv4.conf.all.accept_redirects" = 0;
"net.ipv4.conf.default.accept_redirects" = 0;
"net.ipv4.conf.all.secure_redirects" = 0;
"net.ipv4.conf.default.secure_redirects" = 0;
# Protect against IP spoofing
"net.ipv6.conf.all.accept_redirects" = 0;
"net.ipv6.conf.default.accept_redirects" = 0;
"net.ipv4.conf.all.send_redirects" = 0;
"net.ipv4.conf.default.send_redirects" = 0;
# prevent man-in-the-middle attacks
"net.ipv4.icmp_echo_ignore_all" = 1;
# ignore ICMP request, helps avoid Smurf attacks
"net.ipv4.conf.all.forwarding" = 0;
"net.ipv4.conf.default.accept_source_route" = 0;
"net.ipv4.conf.all.accept_source_route" = 0;
"net.ipv6.conf.all.accept_source_route" = 0;
"net.ipv6.conf.default.accept_source_route" = 0;
# Reverse path filtering causes the kernel to do source validation of
"net.ipv6.conf.all.forwarding" = 0;
"net.ipv6.conf.all.accept_ra" = 0;
"net.ipv6.conf.default.accept_ra" = 0;
## TCP hardening
# Prevent bogus ICMP errors from filling up logs.
"net.ipv4.icmp_ignore_bogus_error_responses" = 1;
# Disable TCP SACK
"net.ipv4.tcp_sack" = 0;
"net.ipv4.tcp_dsack" = 0;
"net.ipv4.tcp_fack" = 0;
# Userspace
# restrict usage of ptrace
"kernel.yama.ptrace_scope" = 2;
# ASLR memory protection (64-bit systems)
"vm.mmap_rnd_bits" = 32;
"vm.mmap_rnd_compat_bits" = 16;
# only permit symlinks to be followed when outside of a world-writable sticky directory
"fs.protected_symlinks" = 1;
"fs.protected_hardlinks" = 1;
# Prevent creating files in potentially attacker-controlled environments
"fs.protected_fifos" = 2;
"fs.protected_regular" = 2;
# Randomize memory
"kernel.randomize_va_space" = 2;
# Exec Shield (Stack protection)
"kernel.exec-shield" = 1;
## TCP optimization
# TCP Fast Open is a TCP extension that reduces network latency by packing
# data in the sender’s initial TCP SYN. Setting 3 = enable TCP Fast Open for
# both incoming and outgoing connections:
"net.ipv4.tcp_fastopen" = 3;
# Bufferbloat mitigations + slight improvement in throughput & latency
"net.ipv4.tcp_congestion_control" = "bbr";
"net.core.default_qdisc" = "cake";
};
Note: The above settings are fairly aggressive and can break common programs, I left comment warnings. The following guide explains kernel hardening and many of the parameters above: Linux Hardening Guide
Hardening Boot Parameters
boot.kernelParams
can be used to set additional kernel command line arguments
at boot time. It can only be used for built-in modules.
For example to add a few of the recommendations from madaidans-insecurities
you could add the following:
# boot.nix
boot.kernelParams = [
# ... snip ...
# prevent heap exploitations
"slab_nomerge"
# mitigate use-after-free vulnerabilities
"init_on_alloc=1"
"init_on_free=1"
# randomises page allocator freelists
"page_alloc.shuffel=1"
];
There are many more recommendations in the Linux Hardening Guide
There are also some boot parameters that are recommended to blacklist, to do so you would use:
boot.blacklistedKernelModules = [
# Datagram Congestion Control Protocol
"dccp"
# Stream Control Transmission Protocol
"sctp"
];
As with the kernelParameters
above, there are much more suggestions in the
guide.
Hardening Systemd
systemd
is the core "init system" and service manager that controls how
services, daemons, and basic system processes are started, stopped and
supervised on modern Linux distributions, including NixOS.
systemd
is a suite of basic building blocks for a Linux system. It provides a
system and service manager that runs as PID 1
and starts the rest of the
system.
Because it launches and supervises almost all system services, hardening systemd means raising the baseline security of your entire system.
dbus-broker
is generally considered more secure and robust but isn't the
default as of yet. To set dbus-broker
as the default:
users.groups.netdev = {};
services = {
usbguard.enable = false;
dbus.implementation = "broker";
logrotate.enable = true;
journald = {
storage = "volatile"; # Store logs in memory
upload.enable = false; # Disable remote log upload (the default)
extraConfig = ''
SystemMaxUse=500M
SystemMaxFileSize=50M
'';
};
};
-
dbus-broker
is more resilient to resource exhaustion attacks and integrates better with Linux security features. -
Setting
storage = "volatile"
tells journald to keep log data only in memory. There is a tradeoff though, If you need long-term auditing or troubleshooting after a reboot, this will not preserve system logs. -
upload.enable
is for forwarding log messages to remote servers, setting this to false prevents accidental leaks of potentially sensitive or internal system information. -
Enabling
logrotate
prevents your disk from filling with excessive legacy/service log files. These are the classic plain-text logs. -
Systemd uses
journald
which stores logs in a binary format which we take care of with theextraConfig
settings.
You can check the security status with:
systemd-analyze security
# or for a detailed view of individual services security posture
systemd-analyze security NetworkManager
Further reading on systemd:
The following is a repo containing many of the Systemd hardening settings in NixOS format:
For example, to harden bluetooth you could add the following to your
configuration.nix
or equivalent:
systemd.services = {
bluetooth.serviceConfig = {
ProtectKernelTunables = lib.mkDefault true;
ProtectKernelModules = lib.mkDefault true;
ProtectKernelLogs = lib.mkDefault true;
ProtectHostname = true;
ProtectControlGroups = true;
ProtectProc = "invisible";
SystemCallFilter = [
"~@obsolete"
"~@cpu-emulation"
"~@swap"
"~@reboot"
"~@mount"
];
SystemCallArchitectures = "native";
};
}
As you can see from above, you typically use the serviceConfig
attribute to
harden settings for systemd services.
systemd-analyze security bluetooth
→ Overall exposure level for bluetooth.service: 3.3 OK 🙂
Lynis and other tools
Lynis is a security auditing tool for systems based on UNIX like Linux, macOS, BSD, and others.--lynis repo
Installation:
environment.systemPackages = [
pkgs.lynis
pkgs.chkrootkit
pkgs.clamav
pkgs.aide
];
Usage:
sudo lynis show commands
sudo lynis audit system
Lynis security scan details:
Hardening index : 78 [############### ]
Tests performed : 231
Plugins enabled : 0
Components:
- Firewall [V]
- Malware scanner [V]
- Lynis will give you more recommendations for securing your system as well.
Example cron job for chkrootkit
:
{pkgs, ...}: {
services.cron = {
enable = true;
# messages.enable = true;
systemCronJobs = [
# Every Sunday at 2:10 AM, run chkrootkit as root, log output for review
"10 2 * * 0 root ${pkgs.chkrootkit}/bin/chkrootkit | logger -t chkrootkit"
];
};
}
The above cron job will use chkrootkit
to automatically scan for known rootkit
signatures. It can detect hidden processes and network connections.
I got the recommendation for clamav
from the Paranoid NixOS blog post and the
others help with compliance for lynis
.
Securing SSH
Security information: Changing SSH configuration settings can significantly impact the security of your system(s). It is crucial to have a solid understanding of what you are doing before making any adjustments. Avoid blindly copying and pasting examples, including those from this Wiki page, without conducting a thorough analysis. Failure to do so may compromise the security of your system(s) and lead to potential vulnerabilities. Take the time to comprehend the implications of your actions and ensure that any changes made are done thoughtfully and with care. --NixOS Wiki
First of all, if you don't use SSH don't enable it in the first place. If you do use SSH, it's important to understand what that opens you up to.
The following are some recommendations from Mozilla on OpenSSH:
The following OpenSSH setup is based on the above guidelines with strong algorithms, and best practices:
{config, ...}: {
config = {
services = {
fail2ban = {
enable = true;
maxretry = 5;
bantime = "1h";
# ignoreIP = [
# "172.16.0.0/12"
# "192.168.0.0/16"
# "2601:881:8100:8de0:31e6:ac52:b5be:462a"
# "matrix.org"
# "app.element.io" # don't ratelimit matrix users
# ];
bantime-increment = {
enable = true; # Enable increment of bantime after each violation
multipliers = "1 2 4 8 16 32 64 128 256";
maxtime = "168h"; # Do not ban for more than 1 week
overalljails = true; # Calculate the bantime based on all the violations
};
};
openssh = {
enable = true;
settings = {
PasswordAuthentication = false;
PermitEmptyPasswords = false;
PermitTunnel = false;
UseDns = false;
KbdInteractiveAuthentication = false;
X11Forwarding = config.services.xserver.enable;
MaxAuthTries = 3;
MaxSessions = 2;
ClientAliveInterval = 300;
ClientAliveCountMax = 0;
AllowUsers = ["your-user"];
TCPKeepAlive = false;
AllowTcpForwarding = false;
AllowAgentForwarding = false;
LogLevel = "VERBOSE";
PermitRootLogin = "no";
KexAlgorithms = [
"curve25519-sha256@libssh.org"
"ecdh-sha2-nistp521"
"ecdh-sha2-nistp384"
"ecdh-sha2-nistp256"
"diffie-hellman-group-exchange-sha256"
];
Ciphers = [
"chacha20-poly1305@openssh.com"
"aes256-gcm@openssh.com"
"aes128-gcm@openssh.com"
"aes256-ctr"
"aes192-ctr"
"aes128-ctr"
];
Macs = [
"hmac-sha2-512-etm@openssh.com"
"hmac-sha2-256-etm@openssh.com"
"umac-128-etm@openssh.com"
"hmac-sha2-512"
"hmac-sha2-256"
"umac-128@openssh.com"
];
};
hostKeys = [
{
path = "/etc/ssh/ssh_host_ed25519_key";
type = "ed25519";
}
];
};
};
};
}
Fail2Ban is an intrusion prevention software framework. It's designed to prevent brute-force attacks by scanning log files for suspicious activity, such as repeated failed login attempts.
OpenSSH is the primary tool for secure remote access for NixOS. Enabling it activates the OpenSSH server on the system, allowing incoming SSH connections.
The above configuration is a robust setup for securing an SSH server by:
-
Preventing brute-force attacks with Fail2Ban
-
Eliminating password authentication in favor of more secure SSH keys
-
Restricting user access and preventing root login
-
Disabling potentially risky forwarding features (tunnel, TCP, agent)
-
Enforce the use of strong, modern cryptographic algorithms for all SSH communications.
-
Enhanced logging for better auditing.
Further Reading:
✔️ Click to expand `auditd` example
To enable the Linux Audit Daemon (auditd
) and define a very basic rule set,
you can use the following NixOS configuration. This example demonstrates how to
log every program execution (execve
) on a 64-bit architecture.
# modules/security/auditd-minimal.nix (or directly in configuration.nix)
{
boot.kernelParams = ["audit=1"];
security.auditd.enable = true;
security.audit.enable = true;
security.audit.rules = [
# Log all program executions on 64-bit architecture
"-a exit,always -F arch=b64 -S execve"
];
}
-
audit=1
Enables auditing at the kernel level very early in the boot process. Without this, some events could be missed. -
security.auditd.enable = true;
Ensures theauditd
userspace daemon is started. -
While often enabled together,
security.audit.enable
specifically refers to enabling the NixOS module for audit rules generation. -
execve
(program executions)
USB Port Protection
It's important to protect your USB ports to prevent BadUSB attacks, data exfiltration, unauthorized device access, malware injection, etc.
Further Reading:
Doas over sudo
For a more minimalist version of sudo
with a smaller codebase and attack
surface, consider doas
. Replace userName
with your username:
# doas.nix
{
lib,
config,
pkgs, # Add pkgs if you need to access user information
...
}: let
cfg = config.custom.security.doas;
in {
options.custom.security.doas = {
enable = lib.mkEnableOption "doas";
};
config = lib.mkIf cfg.enable {
# Disable sudo
security.sudo.enable = false;
# Enable and configure `doas`.
security.doas = {
enable = true;
extraRules = [
{
# Grant doas access specifically to your user
users = ["userName"]; # <--- Only give access to your user
# persist = true; # Convenient but less secure
# noPass = true; # Convenient but even less secure
keepEnv = true; # Often necessary
# Optional: You can also specify which commands they can run, e.g.:
# cmd = "ALL"; # Allows running all commands (default if not specified)
# cmd = "/run/current-system/sw/bin/nixos-rebuild"; # Only allow specific command
}
];
};
# Add an alias to the shell for backward-compat and convenience.
environment.shellAliases = {
sudo = "doas";
};
};
}
You would then import this into your configuration.nix
and enable/disable it
with the following:
# configuration.nix
imports = [
./doas.nix
];
custom.security.doas.enable = true;
❗ NOTE:
Firejail
Firejail is a SUID program that reduces the risk of security breaches by restricting the running environment of untrusted applications using Linux namespaces and seccomp-bpf--Firejail Security Sandbox
It provides sandboxing and access restriction per application, much like what AppArmor/SELinux does at a kernel level. However, it's not as secure or comprehensive as kernel-enforced MAC systems (AppArmor/SELinux), since it's a userspace tool and can potentially be bypassed by privilege escalation exploits.
SeLinux/AppArmor MAC (Mandatory Access Control)
AppArmor is available on NixOS, but is still in a somewhat experimental and evolving state. There are only a few profiles that have been adapted to NixOS, see here Discourse on default-profiles Which guides you here apparmor/includes.nix where you can see some of the abstractions and tunables to follow progress.
SELinux: Experimental, not fully integrated, recent progress for advanced/curious users; expect rough edges and manual intervention if you want to try it. Most find SELinux more complex to configure and maintain than AppArmor.
This isn't meant to be a comprehensive guide, more to get people thinking about security on NixOS.
Resources
Advanced Hardening with nix-mineral
(Community Project)
✔️ Click to Expand section on `nix-mineral`
For users seeking a more comprehensive and opinionated approach to system
hardening beyond the built-in hardened
profile, the community project
nix-mineral
offers a declarative
NixOS module.
nix-mineral
aims to apply a wide array of security configurations, focusing on
tweaking kernel parameters, system settings, and file permissions to reduce the
attack surface. Its features include, but are not limited to: hardened sysctl
options, boot parameter adjustments, root login restrictions, privacy
enhancements (MAC randomization, Whonix machine-id), comprehensive module
blacklisting, firewall configuration, AppArmor integration, and USBGuard
enablement.
Important Considerations:
- Community Project Status:
nix-mineral
is a community-maintained project and is not officially part of the Nixpkgs repository or NixOS documentation. Its development status is explicitly stated as "Alpha software," meaning it may introduce stability issues or unexpected behavior. - Opinionated Configuration: It applies a broad set of hardening measures that might impact system functionality or compatibility with certain applications. Users should thoroughly review its source code and test its effects in a non-critical environment before deploying.
- Complementary to Core Hardening: While comprehensive, it's a layer on top
of NixOS's inherent security benefits and the
profiles.hardened
option.
For detailed information on nix-mineral
's capabilities and current status,
refer directly to its
GitHub repository.