Building a Free and Minimal Unix System (Part 3)- Sensible Security Hardening
Security is often framed as something that must be added to a system: more daemons, more policies, more layers of complexity. In practice, the most reliable improvements usually come from the opposite direction — removing unnecessary functionality, tightening defaults, and making the system’s behaviour easier to understand.
This post documents the security hardening applied to my Artix Linux system, running runit, as part of an ongoing effort to build a free and minimal Unix environment. The emphasis here is not on exhaustive defence, but on sensible reduction of attack surface, applied deliberately and with an understanding of the trade-offs involved.
If you have read part 2 of this series, then after ensuring I have a free, secure encrypted operating system installed. I apply these next steps. I do this before installing any graphical environment. Then after this process is complete I move on to configuring my system.
Where appropriate, the linux-hardened kernel should be
used for maximum hardening, but in my experience it caused issues with
software and hardware. So I opted to add the hardening settings and
tools that I thought were of most importance.
Packages required
Before applying the hardening steps below, ensure the following packages are installed. This list reflects what is actually used on my system; you may not need every package depending on your hardware and workflow.
sudo pacman -S \
apparmor \
apparmor-utils \
audit \
ufw \
usbguard \
openssh \
pam \
libpwquality \
intel-ucodeNotes:
- apparmor / apparmor-utils — required for the AppArmor kernel parameters used later.
- audit — enables audit logging for
pam_faillock. - ufw — simple, auditable firewall frontend.
- usbguard — optional, but recommended on laptops.
- openssh — SSH client and server.
- pam / libpwquality — authentication and password policy hardening.
- intel-ucode — Intel CPU microcode updates (use
amd-ucodeon AMD systems).
If you are using the linux-hardened kernel, AppArmor
support is already enabled at build time.
Kernel hardening via sysctl
Kernel hardening is applied using small, purpose-specific files in
/etc/sysctl.d/. This keeps changes auditable and avoids a
single monolithic configuration that becomes difficult to reason about
over time.
Apply changes with:
sysctl --systemBelow are the exact files I use.
Disable core dumps
File: /etc/sysctl.d/coredump.conf
# disable core dumps
kernel.core_pattern=|/bin/falseFile:
/etc/sysctl.d/suid_dumpable.conf
# Prevent setuid programs from dumping memory.
fs.suid_dumpable=0Restrict kernel information leaks
File:
/etc/sysctl.d/dmesg_restrict.conf
# Only root can read kernel logs.
kernel.dmesg_restrict=1File:
/etc/sysctl.d/kptr_restrict.conf
# Hide kernel pointers (strong setting).
kernel.kptr_restrict=2Disable dangerous debug facilities
File: /etc/sysctl.d/sysrq.conf
# Disable SysRq.
kernel.sysrq=0File: /etc/sysctl.d/kexec.conf
# Disable kexec (prevents replacing the running kernel).
kernel.kexec_load_disabled=1Harden BPF
File: /etc/sysctl.d/harden_bpf.conf
# Disable unprivileged BPF and harden the JIT.
kernel.unprivileged_bpf_disabled=1
net.core.bpf_jit_harden=2Harden ASLR for mmap
File: /etc/sysctl.d/mmap_asir.conf
# Improve ASLR effectiveness for mmap.
vm.mmap_rnd_bits=32
vm.mmap_rnd_compat_bits=16Restrict process inspection (ptrace)
File:
/etc/sysctl.d/ptrace_scope.conf
# Restrict ptrace to processes with CAP_SYS_PTRACE.
kernel.yama.ptrace_scope=2Disable unprivileged user namespaces
File:
/etc/sysctl.d/unprivileged_userns_clone.conf
# Disable unprivileged user namespaces (reduces kernel attack surface).
kernel.unprivileged_userns_clone=0
# Note: may break some sandboxing tools (e.g. bubblewrap) unless adjusted.Swap behaviour
File: /etc/sysctl.d/ram-swap.conf
vm.swappiness=5TCP / network hardening
File:
/etc/sysctl.d/tcp_hardening.conf
# SYN flood mitigation
net.ipv4.tcp_syncookies=1
# Time-wait assassination protection
net.ipv4.tcp_rfc1337=1
# Source validation (anti-spoofing)
net.ipv4.conf.default.rp_filter=1
net.ipv4.conf.all.rp_filter=1
# Disable ICMP redirect acceptance
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
net.ipv6.conf.all.accept_redirects=0
net.ipv6.conf.default.accept_redirects=0
# Disable ICMP redirect sending (non-router)
net.ipv4.conf.all.send_redirects=0
net.ipv4.conf.default.send_redirects=0
# Optional: ignore ICMP echo requests (stealth, but reduces debuggability)
net.ipv4.icmp_echo_ignore_all=1Disable TCP SACK (optional, read the trade-off)
File: /etc/sysctl.d/tcp_sack.conf
# Disables TCP SACK. May reduce performance on lossy/high-latency links.
net.ipv4.tcp_sack=0IPv6 privacy extensions (optional)
File:
/etc/sysctl.d/ipv6_privacy.conf
net.ipv6.conf.all.use_tempaddr = 2
net.ipv6.conf.default.use_tempaddr = 2
net.ipv6.conf.wlan0.use_tempaddr = 2WireGuard forwarding (specific use case)
File:
/etc/sysctl.d/90-wireguard.conf
net.ipv4.ip_forward=1Reducing kernel attack surface with module blacklisting
Unused kernel modules represent unnecessary attack surface. Modules
are disabled using the install <module> /bin/true
mechanism so they cannot be loaded.
All files live in /etc/modprobe.d/.
Disable Bluetooth
File:
/etc/modprobe.d/blacklist-bluetooth.conf
install btusb /bin/true
install bluetooth /bin/trueDisable DMA-capable hardware
File:
/etc/modprobe.d/blacklist-dma.conf
install firewire-core /bin/true
install thunderbolt /bin/trueOptional: disable audio devices
File:
/etc/modprobe.d/blacklist-microphone-speakers.conf
# install snd_hda_intel /bin/trueDisable conntrack helpers
File:
/etc/modprobe.d/no-conntrack-helper.conf
options nf_conntrack nf_conntrack_helper=0Disable uncommon filesystems
File:
/etc/modprobe.d/uncommon-filesystems.conf
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/trueDisable uncommon / legacy network protocols
File:
/etc/modprobe.d/uncommon-network-protocols.conf
install dccp /bin/true
install sctp /bin/true
install rds /bin/true
install tipc /bin/true
install n-hdlc /bin/true
install ax25 /bin/true
install netrom /bin/true
install x25 /bin/true
install rose /bin/true
install decnet /bin/true
install econet /bin/true
install af_802154 /bin/true
install ipx /bin/true
install appletalk /bin/true
install psnap /bin/true
install p8023 /bin/true
install llc /bin/true
install p8022 /bin/trueAuthentication hardening with PAM
All authentication hardening is applied in
/etc/pam.d/system-auth.
PAM is order-sensitive; test changes in a second session before logging out.
Edit the file:
sudoedit /etc/pam.d/system-authAccount lockout (pam_faillock)
Ensure the auth section contains the following logic
in this order:
# FAILLOCK: Pre-authentication check
auth required pam_faillock.so preauth silent audit deny=3 unlock_time=900
# Validate password
auth [success=1 default=bad] pam_unix.so try_first_pass
# FAILLOCK: Handle failed attempts
auth [default=die] pam_faillock.so authfail deny=3 unlock_time=900
# FAILLOCK: Successful authentication
auth required pam_faillock.so authsuccPassword policy (pam_pwquality) and hashing
Locate the password section and ensure it contains:
password required pam_pwquality.so retry=3 minlen=12 difok=6 \
ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 enforce_for_rootImmediately after, ensure hashing uses strong defaults:
password sufficient pam_unix.so sha512 shadow try_first_pass \
use_authtok rounds=65536SSH hardening
SSH is often the only externally reachable service. The goal here is not to detect attacks, but to remove entire classes of them.
Edit:
sudoedit /etc/ssh/sshd_configSuggested baseline:
Protocol 2
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
PubkeyAuthentication yes
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
PermitTunnel no
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 2
LogLevel VERBOSEReload SSH (runit):
sudo sv restart sshdWith password authentication disabled, most automated SSH attacks become irrelevant.
Restricting root access
The root account exists, but it does not need to be usable all the time.
Lock root by default:
sudo passwd -l rootUnlock only when required:
sudo passwd -u rootRe-lock afterwards:
sudo passwd -l rootLocking root does not prevent administrative access via
sudo. Even if SSH were misconfigured, a locked root account
prevents the most damaging failure mode.
Firewalling with UFW (safe, boring defaults)
The firewall is configured to fail closed by default:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw default deny routedAllow SSH:
sudo ufw allow 22/tcpEnable and verify:
sudo ufw enable
sudo ufw status verboseRestrictive outbound firewalls often create fragile systems that fail in surprising ways when networks change. Simplicity here is a feature.
USB device control with USBGuard (brief)
USB devices represent a physical attack surface, especially on laptops.
Install:
sudo pacman -S usbguardGenerate an initial policy:
sudo usbguard generate-policy > /etc/usbguard/rules.confEnable the service (runit):
sudo sv up usbguard
sudo ln -s /etc/runit/sv/usbguard /run/runit/service/USBGuard blocks new USB devices by default and allows only explicitly authorised ones. This requires a short learning period.
Bootloader and early boot hardening (GRUB)
The bootloader is part of the trusted computing base. If an attacker can modify kernel parameters at boot, most OS-level hardening becomes irrelevant.
Kernel hardening parameters (GRUB)
File: /etc/default/grub
Edit:
sudoedit /etc/default/grubThese are the key hardening lines from my system (adjust to taste):
GRUB_CMDLINE_LINUX="apparmor=1 security=apparmor slab_nomerge init_on_alloc=1 init_on_free=1 page_alloc.shuffle=1 pti=on vsyscall=none oops=panic module.sig_enforce=1 lockdown=integrity mce=0 quiet loglevel=0"
GRUB_DISABLE_RECOVERY=true
# OS probing disabled for security (default on my setup)
# GRUB_DISABLE_OS_PROBER=falseNotes:
lockdown=integrityenables kernel lockdown in integrity mode.module.sig_enforce=1enforces signed kernel modules.oops=panicavoids limping onward after kernel faults.vsyscall=nonedisables legacy vsyscall support.
After editing, regenerate the GRUB config:
sudo grub-mkconfig -o /boot/grub/grub.cfgGRUB password protection (superuser)
This prevents casual tampering with kernel parameters and boot entries.
- Generate a PBKDF2 hash:
grub-mkpasswd-pbkdf2Copy the resulting grub.pbkdf2.sha512... string.
- Add it to GRUB custom config.
File: /etc/grub.d/40_custom
Edit:
sudoedit /etc/grub.d/40_customAdd (replace username with a username of your choice):
set superusers="username"
password_pbkdf2 username grub.pbkdf2.sha512.10000.007CD9056C3ADD76E502EB624AA0DEF96E3E947CA4021A30BCF2C8A1648A1C9D6C572AD5BFA21700B338F967A69931499B2155754DEC67153E3C8CE2D5872B24.876BC296F2D8EEACB499043994F3AF3D3D5F8C0B5AF7FD04C413AE17F6E6D545F552900AA846AD2953798845B7914DC9D040E987B97DC110E7BC9D1FCAC14C82- Regenerate GRUB config:
sudo grub-mkconfig -o /boot/grub/grub.cfgMicrocode updates (Intel)
CPU microcode updates patch real hardware vulnerabilities.
Install and integrate:
sudo pacman -S intel-ucode
sudo mkinitcpio -P
sudo grub-mkconfig -o /boot/grub/grub.cfgVerify after reboot:
dmesg | grep -i microcodeWhy this is enough (for my threat model, at least)
Security hardening is only meaningful when tied to a threat model.
This system is a single-user workstation, sometimes mobile, occasionally connected to untrusted networks. The goal is to remove unused functionality, prevent trivial escalation, and keep the system understandable over time.
I have not attempted to defend against supply-chain attacks, malicious hardware, or targeted physical compromise. Defending against those requires different tools and a different level of complexity.
This is not maximum security. It is sufficient, intentional security.
Conclusion
A secure system is not one with the most controls enabled, but one whose behaviour you understand when something breaks.