Hardening is Not an Accident
A secure system is not a happy coincidence. It is the result of deliberate, structured friction.
Firewalls and strong passwords are security theater if the kernel exposes unconstrained execution primitives. Security is the systematic reduction of attack surface 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 Core as a Fortress
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:
- Restrict pointer exposure. Set
kernel.kptr_restrictto2. This ensures that kernel addresses stay hidden from unprivileged eyes. - Restrict log access. Set
kernel.dmesg_restrictto1. Unprivileged users have no business reading kernel logs. - Limit debugging scope. Set
kernel.yama.ptrace_scopeto1. A process should not be allowed to spy on or attach to other non-child processes. - Lock modules after boot. Enable
security.lockKernelModules. Once the system is up and running, loading new kernel modules is forbidden. This blocks runtime kernel exploitation. - Scrub memory by default. Set
init_on_allocandinit_on_freeto1. This forces the system to write zeros to memory on allocation and release, eliminating data leaks from freed memory. - Randomize the playing field. Enable page allocator
shuffling and randomize the kernel stack offset on every system call.
Maximize standard ASLR entropy using
vm.mmap_rnd_bits = 32andvm.mmap_rnd_compat_bits = 16to prevent reliable buffer overflows. - Restrict execution primitives. Disable unprivileged
BPF access (
kernel.unprivileged_bpf_disabled = 1), harden the BPF JIT compiler (net.core.bpf_jit_harden = 2), and restrictuserfaultfd()to root (vm.unprivileged_userfaultfd = 0) to eliminate heap use-after-free exploits. - Harden the network stack. Enable TCP SYN cookies to
mitigate DoS attacks. Enable reverse path filtering
(
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. - Defend the local directory tree. Prevent core dumps
of setuid programs (
fs.suid_dumpable = 0) and block TOCTOU/symlink race attacks in world-writable directories by strictly enforcing protected symlinks, hardlinks, FIFOs, and regular files. - Curb information leakage. Erase kernel stack
variables upon return to user space
(
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. - Lock the physical console. Fully disable the Magic
SysRq key combo (
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. - Enforce audit and validation. Set kernel boot
parameters to force FIPS mode (
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.
Ephemeral State as a Shield
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:
- Suppress memory dumps. Completely disable systemd
coredumps (
systemd.coredump.enable = false) to block physical dump reading and prevent RAM-level secret exposure. - Constrain logging volumes. Enforce strict journald
storage limits (
SystemMaxUse=100M) and retention durations (MaxRetentionSec=1d) to mitigate log-filling denial-of-service attacks.
Sandbox the Execution
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.
The Privilege Fallacy
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.
The Gates Are Locked
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:
- Demand length and diversity. Passwords must be at
least 15 characters long, require uppercase, lowercase, digits, and
special characters, and pass strict dictionary checks. We enforce a
difference criteria of at least 8 changed characters compared to
previous passwords (
difok=8). - Lock accounts on failure. Use
pam_faillockto 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. - Limit session concurrency. Hard limit users to 10 concurrent logins. A compromised account should not be allowed to spawn unlimited sessions across the network.
- Restrict home directory access. Enforce strict
permissions on user home folders (
0755or tighter) to prevent local user data snooping.
Centralized Zero-Trust Identity
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:
- Federate accounts securely. We route system user management via SSSD (System Security Services Daemon) and secure LDAP over TLS. This guarantees that user accounts, group permissions, and status updates are instantly synchronized and revocable across our entire fleet over our secure VPN.
- Enforce hardware-bound verification. To prevent
centralized directory passwords from becoming a single point of failure,
we bind federated user accounts directly to physical hardware
authentication inside the PAM stack. By integrating central LDAP
credentials with local
pam_u2fmapping, 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.
No Whispers on the Wire
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:
- Kill password authentication. Force cryptographic keys. Disabling password and root logins entirely stops brute-force attacks in their tracks.
- Harden key lookup. Set
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. - Restrict ciphers and MACs. Only allow secure CTR
ciphers (
aes256-ctr,aes192-ctr,aes128-ctr) and strong SHA-2 MACs (hmac-sha2-512,hmac-sha2-256). Disable older, weaker protocols. - Disable SFTP and graphical forwarding. If you don’t need to transfer files or graphical displays, disable them. Both SFTP and X11 forwarding are completely dropped.
- Block agent forwarding. Keep credentials on your local machine. Agent forwarding is a classic vector for credential theft; we block it entirely.
- Drop idle connections. If a session is idle for ten
minutes (
ClientAliveInterval 600andClientAliveCountMax 1), close it. An unattended terminal is a gift to an attacker. - Automate active defense. Run Fail2ban to police authentication failures. If an IP repeatedly misbehaves, ban it at the firewall. We use exponential multipliers to increase ban times up to 48 hours, shutting down persistent brute-force engines.
Hardware-Rooted Identity
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:
- Enforce multi-factor system auth. We integrate PAM
with U2F/FIDO2. Standard authentication paths (including system logins,
standard
sudo, modernsystemd-run0, lock screens likeswaylock,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. - Bind physical presence to session state. We run the
smartcard middleware daemon
pcscdand deploy customudevrules matching Yubikey devices (Vendor ID1050). 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 vialoginctl lock-sessionsthe exact millisecond the key is removed, ensuring physical security whenever you walk away. - Harden the GnuPG & SSH keychain. We disable the
default SSH agent entirely. Instead, we use GnuPG’s agent to handle SSH
keys. The cryptographic environment is locked down to high-grade
algorithms:
- Personal preferences are constrained to strong symmetric ciphers
(
AES256) and robust hash algorithms (SHA512). - Self-signatures must use strong digest functions
(
cert-digest-algo SHA512,s2k-digest-algo SHA512,s2k-cipher-algo AES256). - Privacy options are strictly enabled: we suppress comments,
greetings, and versions (
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.
- Personal preferences are constrained to strong symmetric ciphers
(
Modern Declarative Firewalls
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.
Physical Boundaries & USB Protection
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.
Declarative Secrets & Deployment Safety
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).
Living Proof of Security
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.
Continuous CVE & Supply Chain Auditing
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:
- Automated Store Path Scanning. Using
vulnix, CI parses the entire built Nix closure and evaluates all transitive dependencies against known CVE databases to flag unpatched security advisories. - Deep Dependency Inspection. Using
vulnxscan(fromsbomnix), the pipeline performs comprehensive vulnerability analysis across the system derivation’s complete bill of materials. - Declarative SBOM Integration. With custom package
overlays (
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.
Respecting Reality
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.