Hardening your NAS Linux kernel to CIS Level 2 standards

Hardening your NAS Linux kernel to CIS Level 2 standards

·6 min read·Updated on January 27, 2026

Beyond the firewall

A firewall and fail2ban only cover the surface. The Linux kernel itself — permissive defaults, unused modules loaded in memory, mount points without restrictions — remains wide open, and those attack vectors are all documented and exploitable.

The CIS benchmarks (Center for Internet Security) offer two levels: Level 1 for standard servers, Level 2 for sensitive environments. For a NAS holding family photos, backups and home automation data, it's Level 2. Reference setup here: a TerraMaster F4-424 running Debian 13 (Trixie).

The devsec.hardening collection

Applying 200+ CIS recommendations by hand makes no sense when the devsec.hardening Ansible collection already does it, maintained and community-validated.

The setup splits it into specialised CIS roles — cis_permissions, cis_accounts, cis_cron, cis_sudo, cis_tcpwrappers, cis_logging, cis_services, cis_ntp — each tagged and independent. Running just the logging role is one Ansible command.

Over 40 sysctl parameters

/etc/sysctl.d/99-hardening.conf holds the bulk of it.

Memory and kernel protections

# Disable io_uring (frequent attack vector)
kernel.io_uring_disabled = 2

# Forbid dynamic kernel loading (kexec)
kernel.kexec_load_disabled = 1

# Full ASLR (address space randomisation)
kernel.randomize_va_space = 2

# Restrict kernel pointer exposure in logs
kernel.kptr_restrict = 2

# Restrict dmesg to root
kernel.dmesg_restrict = 1

# Forbid core dumps for SUID programs
fs.suid_dumpable = 0

# Restrict access to perf events
kernel.perf_event_paranoid = 3

# Disable vsyscalls (ROP vector)
# (configured via GRUB: vsyscall=none)

Network protections

# Reverse path filtering (anti-spoofing)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0

# Ignore broadcast ICMP requests
net.ipv4.icmp_echo_ignore_broadcasts = 1

# SYN flood protection
net.ipv4.tcp_syncookies = 1

# Forbid source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0

# Disable IPv6 forwarding (the NAS is not a router)
net.ipv6.conf.all.forwarding = 0

# Log martian packets
net.ipv4.conf.all.log_martians = 1

Around forty parameters in total, each documented in the CIS benchmark and closing a specific vector.

GRUB: boot parameters

Some hardening has to be applied as the kernel loads:

GRUB_CMDLINE_LINUX="slab_nomerge pti=on randomize_kstack_offset=on vsyscall=none debugfs=off"
  • slab_nomerge: prevents slab cache merging, limiting type-confusion attacks
  • pti=on: Page Table Isolation, the Spectre and Meltdown mitigation
  • randomize_kstack_offset: the kernel stack is randomised on every syscall
  • vsyscall=none: eliminates a known ROP gadget
  • debugfs=off: closes the kernel debug filesystem

Blacklisting unused modules

In /etc/modprobe.d/hardening.conf:

# Exotic filesystems
install cramfs /bin/true
install freevxfs /bin/true
install jffs2 /bin/true
install hfs /bin/true
install hfsplus /bin/true
install squashfs /bin/true
install udf /bin/true

# Rarely used network protocols
install dccp /bin/true
install sctp /bin/true
install rds /bin/true
install tipc /bin/true

# USB storage (disabled on a headless NAS)
install usb-storage /bin/true

The install <module> /bin/true trick is sturdier than a plain blacklist: blacklists can be bypassed through dependencies, whereas "install /bin/true" silently does nothing. A lone blacklist squashfs still lets the module load.

Mount point hardening

In /etc/fstab:

tmpfs  /tmp      tmpfs  defaults,noexec,nosuid,nodev  0 0
tmpfs  /dev/shm  tmpfs  defaults,noexec,nosuid,nodev  0 0
  • noexec: no binary can execute, so no dropped-then-run payload
  • nosuid: SUID/SGID bits ignored
  • nodev: device file creation forbidden

For /proc, hidepid=2 stops regular users from seeing other users' processes:

proc  /proc  proc  defaults,hidepid=2  0 0

AppArmor in enforce mode

AppArmor provides MAC (Mandatory Access Control): even when compromised, a process can only touch what its profile allows. On Debian 13, AppArmor is enabled by default but often in "complain" mode (logging only). Hardening switches it to enforce:

aa-enforce /etc/apparmor.d/*

One command, but it's the difference between "we know what happened" and "we prevented it from happening".

Password policies (NIST SP 800-63B)

# /etc/security/pwquality.conf
minlen = 15          # 15 characters minimum
minclass = 3         # At least 3 character classes
maxrepeat = 3        # Max 3 identical consecutive characters
# /etc/login.defs
PASS_MAX_DAYS  365   # Expiry at 365 days
PASS_MIN_DAYS  1     # 1 day minimum between changes
LOGIN_RETRIES  3     # 3 login attempts
LOGIN_TIMEOUT  60    # 60-second timeout
UMASK          077   # Restrictive default permissions

Plus account lockout after 3 failures with a 15-minute delay via PAM (pam_faillock), and core dumps globally disabled in /etc/security/limits.conf.

Automatic updates

unattended-upgrades applies security patches daily, with an automatic reboot at 4am if needed:

# /etc/apt/apt.conf.d/50unattended-upgrades
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

An unpatched NAS eventually gets compromised. The 4am window avoids interrupting real usage.

AIDE: file integrity monitoring

AIDE (Advanced Intrusion Detection Environment) compares system state against a baseline and flags everything that changed:

# Initialise the database
aide --init
mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db

# Daily check (via cron)
aide --check

If a system binary changes without a legitimate reason — and there are few — AIDE sees it.

Audit logging with auditd

auditd traces security events, here with over 40 rules:

# /etc/audit/rules.d/hardening.rules

# Watch authentication changes
-w /etc/passwd -p wa -k auth_changes
-w /etc/shadow -p wa -k auth_changes
-w /etc/group -p wa -k auth_changes

# Watch sudo
-w /etc/sudoers -p wa -k sudo_changes
-w /etc/sudoers.d/ -p wa -k sudo_changes

# Kernel module loading
-a always,exit -F arch=b64 -S init_module -S delete_module -k modules

# Network changes
-a always,exit -F arch=b64 -S sethostname -S setdomainname -k network

# Watch packages
-w /usr/bin/dpkg -p x -k packages
-w /usr/bin/apt -p x -k packages

# Watch systemd
-w /etc/systemd/ -p wa -k systemd
-w /usr/lib/systemd/ -p wa -k systemd

These logs are what let you establish, after the fact, who did what, when and with which tool — the starting point of any investigation.

What to take away

CIS Level 2 on a home NAS can look excessive, but each parameter closes a real, documented vector. Thanks to devsec.hardening and Ansible roles, applying it is one command and maintaining it takes no effort. The real work is understanding what each rule does: applying a benchmark without reading it protects very little.

ShareLinkedInXBluesky

Related articles