A secure system is not a happy coincidence. It is the result of deliberate, structured friction.
We like to think our systems are safe because we chose a good password or hid behind a firewall. In reality, security is about reducing the surface area of execution until only what is strictly necessary remains. Hardening isn’t a single switch; it is a series of constraints designed to make exploitation too expensive to pursue.
If you don’t explicitly configure your system to resist, it won’t.
The kernel is the ultimate prize for any attacker. If they compromise the kernel, they own the machine. That is why we don’t leave kernel defaults untouched:
kernel.kptr_restrict to 2. This ensures that
kernel addresses stay hidden from unprivileged eyes.kernel.dmesg_restrict to 1. Unprivileged users
have no business reading kernel logs.kernel.yama.ptrace_scope to 1. A process
should not be allowed to spy on or attach to other non-child
processes.security.lockKernelModules. Once the system is up and
running, loading new kernel modules is forbidden. This blocks runtime
kernel exploitation.init_on_alloc and init_on_free to
1. This forces the system to write zeros to memory on
allocation and release, eliminating data leaks from freed memory.vm.mmap_rnd_bits = 32
and vm.mmap_rnd_compat_bits = 16 to prevent reliable buffer
overflows.kernel.unprivileged_bpf_disabled = 1), harden
the BPF JIT compiler (net.core.bpf_jit_harden = 2), and
restrict userfaultfd() to root
(vm.unprivileged_userfaultfd = 0) to eliminate heap
use-after-free exploits.rp_filter) on all interfaces to prevent IP spoofing, and
block ICMP redirects. Go further: disable packet forwarding, ignore
broadcast pings to avoid Smurf amplification attacks, disable TCP
timestamps to prevent host uptime leaks, and shield against RFC 1337
TIME-WAIT assassination attacks.fs.suid_dumpable = 0) and block
TOCTOU/symlink race attacks in world-writable directories by strictly
enforcing protected symlinks, hardlinks, FIFOs, and regular files.kernel.stack_erasing = 1), disable TTY line discipline
autoloading (dev.tty.ldisc_autoload = 0), and restrict
performance event monitoring
(kernel.perf_event_paranoid = 3) to block hardware
side-channel analysis.kernel.sysrq = 0) to prevent local
physical terminal hijacking, and limit max system PIDs
(kernel.pid_max = 65536) to mitigate fork-bomb
denial-of-service attempts.fips=1) and enable strict
system auditing (audit=1) with an expanded backlog. If it
happens in the kernel, it must be validated and logged. We deploy a
comprehensive Linux Audit framework with 24 audit rules monitoring
everything from kernel module loading and permission changes to all
process executions and identity modifications. If it happens on the
system, the audit daemon captures it.Most systems accumulate state like dust, leaving long-lived artifacts for attackers to discover. We reject filesystem longevity.
We use an ephemeral root filesystem. On every single boot, the root
directory (/) is wiped clean (using automated Btrfs
subvolume rollback), resetting the system back to its declared, pristine
configuration. If malware finds a way to write to a system directory, it
is obliterated on the next reboot.
Only explicitly declared, stateful paths are allowed to survive,
bound securely to our /persist volume. If it isn’t defined
in our impermanence configuration, it is temporary.
We complement this stateless root with strict operational boundaries:
systemd.coredump.enable = false) to block
physical dump reading and prevent RAM-level secret exposure.SystemMaxUse=100M) and retention durations
(MaxRetentionSec=1d) to mitigate log-filling
denial-of-service attacks.Securing the system is pointless if we allow user-facing applications to run unconstrained.
We enforce AppArmor system-wide. Every application must operate within a strict jail. If a web browser or media player is compromised, AppArmor ensures it cannot wander outside its designated profile to read private keys or scan your local network. We load default system profiles and cache them at boot to keep boundaries rigid without sacrificing performance.
To defend against active file compromise, we supplement AppArmor with
on-access antivirus scanning via ClamAV, continuously
monitoring user ingestion points like /home/user/Downloads
to neutralize threats the instant they hit the filesystem.
Sudo is a relic of an era when we trusted too much. It grants blanket access and leaves a massive footprint. We can do better.
In a modern, secure setup, we disable sudo entirely.
Instead, we use systemd’s run0 and Polkit
for fine-grained, policy-driven authorization. When you request elevated
privileges, the system should ask who you are. We enforce password
requirements for the wheel group on every elevation. If you
want root access, you write your password. No exceptions, no
shortcuts.
To cement the elimination of default administrative access, the root
user’s password login is completely disabled
(hashedPassword = null). Direct console access is blocked;
administrative access is strictly a policy-driven, authenticated
transition.
Brute-force is a basic threat, but it is highly effective against lazy configurations. We close that door by combining password quality with strict authentication policies:
difok=8).pam_faillock to monitor login attempts. If a user fails
three times within 15 minutes (fail_interval=900), lock
them out permanently (unlock_time=0). Unlocking requires
manual administrative intervention.0755 or tighter) to
prevent local user data snooping.On an ephemeral root filesystem, local user directories and static credentials are an operational liability and a security vector for configuration drift. We reject localized user databases.
We enforce a centralized, cryptographically secured identity model:
pam_u2f mapping, users must present
their hardware security token alongside their LDAP password to gain
system access. Because home directories are ephemeral, we store these
hardware token mappings in a write-protected, centralized directory on
the host, ensuring we never depend on persistent or writable local
dotfiles.SSH is your front door. If you leave it weak, someone will walk in. Hardening SSH is about eliminating legacy choices and forcing modern cryptographic standards:
authorizedKeysInHomedir = false. By looking up SSH public
keys from a centralized, unalterable system directory instead of
writeable user directories, we prevent attackers with local write access
from injecting their own backdoor keys.aes256-ctr, aes192-ctr,
aes128-ctr) and strong SHA-2 MACs
(hmac-sha2-512, hmac-sha2-256). Disable older,
weaker protocols.ClientAliveInterval 600 and
ClientAliveCountMax 1), close it. An unattended terminal is
a gift to an attacker.Cryptographic keys stored in software are only as secure as the system memory they live in. If memory is compromised, your identity is stolen.
We ground our identity in hardware. By enabling TPM 2.0 and PKCS#11 support, we bind cryptographic operations directly to physical silicon. Private keys are generated or stored on hardware security tokens and the motherboard’s security module, ensuring they can never be exported or read in plain text from disk or RAM.
We extend this hardware root of trust to physical authentication using Yubikeys:
sudo, modern systemd-run0, lock
screens like swaylock, waylock,
gtklock, gnome-screensaver, and graphical GDM
desktop logins) mandate a physical security token. We enforce U2F
authentication as a strict, mandatory control flow
(control = "required"), and configure interactive touch
cues (cue = true), forcing the system to actively prompt
the user to touch the physical device before authorizing any
actions.pcscd and deploy custom
udev rules matching Yubikey devices (Vendor ID
1050). If a Yubikey is unplugged from the machine, the
system immediately senses the removal and executes a highly secure
locking script. The physical console is locked via
loginctl lock-sessions the exact millisecond the key is
removed, ensuring physical security whenever you walk away.AES256) and robust hash algorithms
(SHA512).cert-digest-algo SHA512,
s2k-digest-algo SHA512,
s2k-cipher-algo AES256).no-comments,
no-emit-version, no-greeting), enforce secure
memory allocation (require-secmem), mandate
cross-certification to resist signature-spoofing
(require-cross-certification), and strip metadata by
throwing key IDs (throw-keyids) to obscure key
recipients.Traditional firewalls are often complex, brittle legacy iptables scripts. We reject this.
We deploy a declarative firewall using nftables as the
modern, high-performance packet-filtering backend. The default policy is
simple: drop everything except what is explicitly authorized. To support
mesh networking and internal services securely, we only dynamically
trust inbound traffic arriving over secure, authenticated
Tailscale VPN interfaces.
No amount of software-level hardening matters if a rogue device can be plugged directly into your machine. Physical ports are a major vector for DMA (Direct Memory Access) and BadUSB attacks.
We run USBGuard to block unauthorized devices at the
hardware bus-level. The policy defaults to blocking all incoming USB
connections. Only explicitly authorized internal, hardwired interfaces
(such as keyboards, mouse pads, and system-level Bluetooth modules) are
whitelisted, alongside physical Yubikeys matching Vendor ID
1050:* to ensure authentication tokens function seamlessly
while blocking all rogue peripherals instantly.
Hardening configurations is pointless if your secrets are committed in plain text to a Git repository. More importantly, we must ensure they never end up in the globally readable Nix store.
We integrate sops-nix to encrypt all system secrets
directly inside the source repository. Decryption happens dynamically at
activation time on the target host using host-specific private SSH keys
(e.g., ssh_host_ed25519_key) mapped natively via the modern
age format. Because decryption occurs at runtime rather
than during evaluation or build time, this completely prevents plaintext
secrets from being baked into and exposed in the world-readable
/nix/store.
To prevent misconfigurations, we embed custom assertions at compile time. The system will actively block deployment unless security preconditions are fully satisfied (for example, refusing build completion if a host secrets file is deployed without a master user password being securely declared).
Security is a dynamic state that must be continuously verified. Auditing provides the “proof of life” for your policies, ensuring that the intended hardening measures are actually enforced and have not drifted over time. A secure system must be verifiable. We treat logs not just as debugging tools, but as an append-only, tamper-evident audit trail.
By centralizing logs via a secure transport (e.g., encrypted syslog or remote journald forwarding), we ensure that even if an attacker gains root access to a local machine, they cannot scrub their footprint. The audit trail lives off-host, creating a definitive record of system events that exists independently of the hardened target.
In a declarative system like NixOS, auditing is shifted “left.” We do not just audit the running system; we audit the specification of the system. Our entire audit logging configuration is defined in Nix and automatically deployed across all production hosts. Our build pipeline automatically validates that security-critical sysctl settings, AppArmor profiles, and file permissions are defined exactly as intended. If a change deviates from the hard-coded security baseline, the CI/CD pipeline fails, preventing the deployment of a weakened configuration. Simultaneously, automated agents periodically reconcile the running state against the declared state. Any unauthorized manual change to system binaries or critical configuration files triggers an immediate alert and a forced revert to the pristine, defined state.
We deploy 24 audit rules across 9 categories of critical system
operations, each declaratively configured and automatically enabled on
all non-ephemeral hosts. Every process invocation via
execve is logged, capturing the complete command chain and
execution context—the forensic foundation for detecting malicious code
execution or unauthorized privilege escalation. All modifications to
file permissions (chmod, fchmod), ownership
(chown, fchown), and extended attributes
(setxattr, lsetxattr, etc.) are logged.
Critical system databases (/etc/passwd,
/etc/group, /etc/shadow) are monitored for
write access, and failed file access attempts (EACCES,
EPERM) are explicitly captured to detect brute-force or
permission-probing attacks. Kernel module load, finalize, and delete
operations are logged, with the /etc/audit/ directory and
/var/log/audit/ logs themselves under write-access
monitoring to prevent tampering. All login and session lifecycle events
are captured via /var/log/lastlog,
/var/run/utmp, /var/log/wtmp, and
/var/log/btmp. Hostname and domain name changes
(sethostname, setdomainname) are logged, along
with modifications to /etc/hosts and
/etc/issue. System-level mount and unmount operations are
logged to detect unauthorized filesystem operations or rootkit-level
attacks attempting to bind malicious filesystems.
Real-time observability auditing must also be actionable. We implement an “audit-to-alert” loop using the Linux Audit framework (auditd), monitoring sensitive system calls—such as execve, ptrace, and module loading—specifically looking for patterns that deviate from normal system activity. The audit daemon is configured with strict capacity management: if disk usage crosses 10% of available space, a syslog alert is generated. If usage approaches the administrative limit (5%), critical alerts ensure the audit system never silently fails due to space exhaustion. Because our identity is hardware-rooted (e.g., via Yubikeys), the audit subsystem correlates physical token removals with session lifecycle events. A security-critical event, such as a user elevation attempt followed by the physical removal of an authentication token, is treated as a high-severity incident, triggering an automated session lock and a security notification.
Audit logs are automatically rotated to prevent disk space exhaustion
while maintaining forensic history: the audit daemon rotates logs at
100MB per file, retaining 10 rotated archives. Audit logs persist across
reboots via our impermanence configuration, stored in
/var/log/. The systemd journal enforces strict retention
limits (1 day retention, 100MB maximum system journal, 50MB runtime
journal) to prevent log-based denial-of-service attacks while
maintaining recent operational history. Auditing provides the definitive
proof that our declared hardening policies are not merely
theoretical—they are operationally enforced, continuously verified, and
persistently recorded.
Hardening a host at rest is only half the battle. If a system closure contains packages with known vulnerabilities (CVEs), runtime constraints can only mitigate, not prevent, exploitation. Security auditing must be automated, declarative, and executed continuously before system closures ever reach physical hardware.
In our CI/CD pipeline, every host system configuration is compiled to
its complete top-level Nix store derivation
(.config.system.build.toplevel) and rigorously audited
prior to deployment:
vulnix, CI parses the entire built Nix closure and
evaluates all transitive dependencies against known CVE databases to
flag unpatched security advisories.vulnxscan (from sbomnix), the pipeline
performs comprehensive vulnerability analysis across the system
derivation’s complete bill of materials.withSbomnix), packages carry SBOM and supply
chain metadata directly within their derivations. Dependency graphs,
provenance, and vulnerability scans are first-class outputs of the build
system.By integrating continuous vulnerability scanning directly into the Flake deployment workflow, a system image with unresolved CVEs is rejected in CI before it can ever be booted on a machine.
Hardening is not a static checklist or a one-time configuration pass; it is an ongoing posture of deliberate friction.
A secure system demands defense in depth across every layer: from kernel sysctl constraints and ephemeral root filesystems, to hardware-backed identity, physical USB bus isolation, and automated supply chain auditing in CI. When every open service, unconstrained process, and legacy default is treated as an explicit policy decision, security stops being a matter of faith.
By enforcing these boundaries declaratively, we eliminate configuration drift and human error. Hardening is how we bring physical and operational reality into uncompromising alignment with our intentions.