Linux Security Hardening: A Comprehensive Guide to Fortifying Your System
Linux is widely regarded as one of the most secure operating systems available, but a default installation is far from impenetrable. Whether you are running a personal workstation, a server exposed to the internet, or a dedicated anonymity-focused machine, systematic hardening is essential to reduce the attack surface and protect against both local and remote threats. This guide covers the critical layers of Linux security hardening, from kernel-level protections and mandatory access controls to firewall configuration, full disk encryption, secure boot, and comprehensive audit logging. Each section provides actionable configuration steps, real-world commands, and references to authoritative upstream documentation so you can implement these protections on any major distribution.
Kernel Hardening
The Linux kernel is the foundation of every running system, and it is also the single largest target for privilege escalation exploits. Kernel hardening involves tuning compile-time and runtime parameters to restrict dangerous functionality, limit information leaks, and make exploitation of memory corruption vulnerabilities significantly more difficult. The upstream kernel development community maintains extensive documentation on security-relevant options at kernel.org.
Sysctl Runtime Parameters
The sysctl interface exposes hundreds of tunable kernel parameters at runtime. A hardened configuration should be placed in /etc/sysctl.d/99-hardening.conf and loaded with sysctl --system. The following parameters address the most common attack vectors:
# Restrict kernel pointer exposure
kernel.kptr_restrict = 2
# Restrict dmesg access to root
kernel.dmesg_restrict = 1
# Restrict eBPF to privileged users
kernel.unprivileged_bpf_disabled = 1
# Enable JIT hardening for BPF
net.core.bpf_jit_harden = 2
# Disable kexec to prevent loading replacement kernels
kernel.kexec_load_disabled = 1
# Restrict ptrace to parent processes only
kernel.yama.ptrace_scope = 2
# Restrict performance events
kernel.perf_event_paranoid = 3
# Disable SysRq key combinations
kernel.sysrq = 0
# Network hardening
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
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
net.ipv4.icmp_echo_ignore_all = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_timestamps = 0
The parameter kernel.kptr_restrict = 2 hides kernel symbol addresses from all users including root, which prevents attackers from using /proc/kallsyms to locate gadgets for return-oriented programming attacks. Setting kernel.yama.ptrace_scope = 2 prevents any process from attaching a debugger to another process unless it is a direct parent, which blocks a wide class of credential theft and injection attacks.
Kernel Boot Parameters
Additional hardening can be applied through the kernel command line in your bootloader configuration (GRUB or systemd-boot). These parameters are set once and cannot be changed at runtime:
# In /etc/default/grub, append to GRUB_CMDLINE_LINUX:
GRUB_CMDLINE_LINUX="slab_nomerge init_on_alloc=1 init_on_free=1 page_alloc.shuffle=1 pti=on randomize_kstack_offset=on vsyscall=none lockdown=confidentiality module.sig_enforce=1"
The slab_nomerge option prevents the kernel from merging memory allocator caches of similar size, which makes heap-based exploitation significantly harder. The init_on_alloc=1 and init_on_free=1 parameters zero-fill memory on allocation and deallocation respectively, preventing information leaks through uninitialized memory. The lockdown=confidentiality parameter enables kernel lockdown mode which prevents even root from modifying kernel memory, loading unsigned modules, or accessing raw I/O ports.
The kernel-hardening-checker tool on GitHub can audit your running kernel configuration and report which hardening options are enabled or missing. Run it against your /proc/config.gz or kernel config file to get a comprehensive report.
Mandatory Access Control: AppArmor and SELinux
Traditional Unix discretionary access control (DAC) relies on file ownership and permission bits, but this model is fundamentally limited. If an attacker compromises a process running as a particular user, they gain all the privileges of that user. Mandatory access control (MAC) systems add a second layer of enforcement that restricts processes to only the resources explicitly allowed by policy, regardless of the user context. The two dominant MAC frameworks on Linux are AppArmor and SELinux.
AppArmor
AppArmor is the default MAC system on Debian, Ubuntu, openSUSE, and their derivatives. It uses path-based profiles that define which files, capabilities, and network operations a confined process may access. Profiles can run in enforce mode, where violations are blocked, or complain mode, where violations are logged but permitted. The ArchWiki AppArmor page provides an excellent cross-distribution reference for profile management.
# Check AppArmor status
sudo aa-status
# Set a profile to enforce mode
sudo aa-enforce /etc/apparmor.d/usr.bin.firefox
# Generate a new profile interactively
sudo aa-genprof /usr/bin/application
# Set profile to complain mode for testing
sudo aa-complain /etc/apparmor.d/usr.bin.application
# Reload all profiles
sudo systemctl reload apparmor
A well-written AppArmor profile for a network-facing service should restrict file access to only the directories the service needs, deny raw network socket access unless required, deny ptrace capabilities, and limit which POSIX capabilities the process may hold. For services like Tor or a web server, creating tight AppArmor profiles is one of the most effective ways to contain a compromise.
SELinux
SELinux, developed originally by the NSA and maintained by Red Hat, uses label-based policies where every file, process, port, and resource is assigned a security context. SELinux is the default on Fedora, RHEL, CentOS, and their derivatives. It is significantly more complex than AppArmor but provides finer-grained control:
# Check SELinux status
sestatus
# View the security context of files
ls -Z /var/www/html/
# View the security context of running processes
ps auxZ | grep httpd
# Change a file's context
sudo chcon -t httpd_sys_content_t /var/www/html/index.html
# Restore default contexts recursively
sudo restorecon -Rv /var/www/html/
# Set SELinux to enforcing mode
sudo setenforce 1
# Make enforcing mode persistent
sudo sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
SELinux policies define type transitions, which automatically assign security contexts to new processes and files based on their parent context and the operation being performed. For example, when the init system starts the Apache web server, a type transition rule ensures the httpd process receives the httpd_t type, which is then confined to only access files labeled with types like httpd_sys_content_t. This level of granularity means even a full remote code execution vulnerability in Apache cannot access files belonging to other services.
Firewall Configuration: iptables and nftables
A properly configured host-based firewall is a fundamental requirement for any hardened Linux system. Even on machines behind a network firewall, host-based filtering provides defense in depth against lateral movement and misconfigured services. The two primary firewall frameworks are the legacy iptables and its modern replacement, nftables.
iptables Configuration
While iptables is being gradually replaced by nftables, it remains widely deployed and well understood. A minimal restrictive configuration for a workstation should follow a default-deny inbound policy:
#!/bin/bash
# Flush existing rules
iptables -F
iptables -X
iptables -t nat -F
iptables -t mangle -F
# Default policies: drop everything
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Allow loopback
iptables -A INPUT -i lo -j ACCEPT
# Allow established and related connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Drop invalid packets
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
# Rate-limit ICMP
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s --limit-burst 4 -j ACCEPT
# Log dropped packets
iptables -A INPUT -j LOG --log-prefix "IPT-DROP: " --log-level 4
iptables -A INPUT -j DROP
# Save rules
iptables-save > /etc/iptables/rules.v4
nftables Configuration
nftables is the modern replacement for iptables, offering a cleaner syntax, better performance, and unified handling of IPv4, IPv6, and other protocol families. The ArchWiki nftables page provides comprehensive configuration examples:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Allow loopback
iif "lo" accept
# Allow established/related
ct state established,related accept
# Drop invalid
ct state invalid drop
# ICMP rate limiting
ip protocol icmp icmp type echo-request limit rate 1/second accept
ip6 nexthdr icmpv6 icmpv6 type echo-request limit rate 1/second accept
# Log and drop everything else
log prefix "NFT-DROP: " drop
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
For systems that should only communicate through Tor, you can add output filtering that restricts all outbound traffic to only the Tor SOCKS port and the Tor process itself, effectively creating a transparent proxy configuration that prevents any application from leaking traffic outside the Tor network. This is the approach used by Whonix and Tails.
Full Disk Encryption with LUKS
Full disk encryption (FDE) protects data at rest against physical access threats, including stolen laptops, seized hardware, and evil maid attacks. The Linux Unified Key Setup (LUKS) is the standard encryption layer used by virtually all Linux distributions. LUKS operates on top of the dm-crypt kernel subsystem and supports multiple key slots, allowing different passphrases or key files to unlock the same volume.
Setting Up LUKS Encryption
# Create a LUKS2 encrypted partition with Argon2id KDF
sudo cryptsetup luksFormat --type luks2 --cipher aes-xts-plain64 \
--key-size 512 --hash sha512 --iter-time 5000 \
--pbkdf argon2id /dev/sda2
# Open the encrypted volume
sudo cryptsetup open /dev/sda2 cryptroot
# Create filesystem on the decrypted device
sudo mkfs.ext4 /dev/mapper/cryptroot
# Mount it
sudo mount /dev/mapper/cryptroot /mnt
# Add a backup key slot
sudo cryptsetup luksAddKey /dev/sda2
# View LUKS header information
sudo cryptsetup luksDump /dev/sda2
The choice of --pbkdf argon2id is critical for modern security. Argon2id is a memory-hard key derivation function that resists both GPU-based and ASIC-based brute force attacks by requiring significant amounts of memory during key derivation. The --iter-time 5000 parameter sets a target of 5 seconds for key derivation, which provides strong protection against offline passphrase cracking while remaining practical for daily use.
Hardening LUKS Configuration
Beyond the basic setup, several additional measures strengthen disk encryption. First, back up the LUKS header to a secure offline location, because a corrupted header means permanent data loss. Second, consider using a detached LUKS header stored on a separate USB device, which means the encrypted partition itself contains no identifying information. Third, use cryptsetup luksHeaderBackup and store the backup encrypted with a different key on offline media. Fourth, consider enabling the --integrity option in LUKS2 to add authenticated encryption using AEAD modes, which detects tampering with the ciphertext.
Secure Boot and Verified Boot Chain
Secure Boot ensures that only cryptographically signed code runs during the boot process, from the UEFI firmware through the bootloader to the kernel. Without Secure Boot, an attacker with physical access could replace the bootloader or kernel with a modified version that captures the disk encryption passphrase or installs a persistent rootkit.
On Linux, Secure Boot can be configured to use either the distribution's signing keys (which chain back to Microsoft's UEFI certificate authority) or your own custom keys for maximum control. The process of enrolling custom Secure Boot keys involves generating a Platform Key (PK), Key Exchange Key (KEK), and database (db) key, then signing your bootloader and kernel with these keys:
# Generate custom Secure Boot keys
openssl req -new -x509 -newkey rsa:2048 -keyout PK.key -out PK.crt \
-days 3650 -nodes -subj "/CN=Custom Platform Key/"
openssl req -new -x509 -newkey rsa:2048 -keyout KEK.key -out KEK.crt \
-days 3650 -nodes -subj "/CN=Custom Key Exchange Key/"
openssl req -new -x509 -newkey rsa:2048 -keyout db.key -out db.crt \
-days 3650 -nodes -subj "/CN=Custom Signature Database Key/"
# Sign a unified kernel image with sbsign
sudo sbsign --key db.key --cert db.crt --output /boot/efi/EFI/Linux/linux-signed.efi /boot/vmlinuz-linux
# Enroll keys using KeyTool or firmware setup
# Convert to ESL format for enrollment
cert-to-efi-sig-list -g "$(uuidgen)" PK.crt PK.esl
sign-efi-sig-list -k PK.key -c PK.crt PK PK.esl PK.auth
For a complete verified boot chain, combine Secure Boot with a Unified Kernel Image (UKI) that bundles the kernel, initramfs, and kernel command line into a single signed EFI binary. This prevents tampering with any component of the boot process. The Debian Securing Debian Manual covers distribution-specific Secure Boot configuration in detail.
Audit Logging with auditd
The Linux Audit Framework provides comprehensive system call logging that can record virtually any security-relevant event on the system. Unlike application-level logging, audit logs capture kernel-level events including file access, process execution, user authentication, network connections, and changes to system configuration. The audit daemon (auditd) is an essential component of any hardened system because it provides the evidence needed to detect intrusions and perform forensic analysis.
Configuring Audit Rules
# /etc/audit/rules.d/hardening.rules
# Delete all existing rules
-D
# Set buffer size
-b 8192
# Failure mode: 1 = printk, 2 = panic
-f 1
# Monitor authentication files
-w /etc/passwd -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/gshadow -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers
# Monitor SSH configuration
-w /etc/ssh/sshd_config -p wa -k sshd_config
-w /etc/ssh/sshd_config.d/ -p wa -k sshd_config
# Monitor network configuration
-w /etc/hosts -p wa -k hosts
-w /etc/resolv.conf -p wa -k dns_config
-w /etc/NetworkManager/ -p wa -k network_config
# Log all executions by root
-a always,exit -F arch=b64 -F euid=0 -S execve -k root_commands
-a always,exit -F arch=b32 -F euid=0 -S execve -k root_commands
# Monitor kernel module loading
-w /sbin/insmod -p x -k kernel_modules
-w /sbin/modprobe -p x -k kernel_modules
-a always,exit -F arch=b64 -S init_module,finit_module -k kernel_modules
-a always,exit -F arch=b64 -S delete_module -k kernel_modules
# Monitor mount operations
-a always,exit -F arch=b64 -S mount -k mounts
# Monitor time changes
-a always,exit -F arch=b64 -S adjtimex,settimeofday,clock_settime -k time_change
# Make audit config immutable (requires reboot to change)
-e 2
The final rule -e 2 is particularly important. It makes the audit configuration immutable at runtime, meaning even root cannot disable or modify audit rules without rebooting the system. This prevents an attacker who gains root access from silently disabling logging to cover their tracks.
Searching and Analyzing Audit Logs
# Search for all authentication events
ausearch -k identity -ts today
# Search for sudo configuration changes
ausearch -k sudoers -ts this-week
# Search for commands run as root
ausearch -k root_commands -ts recent
# Generate a summary report
aureport --summary
# Generate a detailed authentication report
aureport -au --summary
# Monitor audit events in real time
tail -f /var/log/audit/audit.log | aureport -i
Minimizing the Attack Surface
Every running service, open port, installed package, and enabled kernel module represents a potential entry point for an attacker. Attack surface reduction is the practice of systematically eliminating unnecessary components to reduce the number of potential vulnerabilities. This principle should be applied at every level of the system.
Service Minimization
Audit all running services and disable anything not strictly required. On a systemd-based system, list all active services and critically evaluate each one:
# List all active services
systemctl list-units --type=service --state=active
# Disable and mask unnecessary services
sudo systemctl disable --now avahi-daemon.service
sudo systemctl mask avahi-daemon.service
sudo systemctl disable --now cups.service
sudo systemctl mask cups.service
sudo systemctl disable --now bluetooth.service
sudo systemctl mask bluetooth.service
# Remove unnecessary packages (Debian/Ubuntu)
sudo apt purge telnet rsh-client rsh-server xinetd
# Remove unnecessary setuid binaries
sudo chmod u-s /usr/bin/chfn /usr/bin/chsh /usr/bin/newgrp
The mask operation is stronger than disable because it prevents the service from being started even as a dependency of another service. Use it for services you are certain should never run. Additionally, review setuid and setgid binaries on your system with find / -perm -4000 -o -perm -2000 and remove the special permissions from any that are not needed, as these are prime targets for privilege escalation exploits.
Kernel Module Restriction
Many kernel modules provide functionality that is unnecessary on a hardened system and can be exploited. Disable unused modules by blacklisting them:
# /etc/modprobe.d/hardening.conf
# Disable uncommon network protocols
install dccp /bin/false
install sctp /bin/false
install rds /bin/false
install tipc /bin/false
# Disable uncommon filesystems
install cramfs /bin/false
install freevxfs /bin/false
install jffs2 /bin/false
install hfs /bin/false
install hfsplus /bin/false
install squashfs /bin/false
install udf /bin/false
# Disable Firewire and Thunderbolt (DMA attack vectors)
install firewire-core /bin/false
install thunderbolt /bin/false
# Disable USB storage if not needed
install usb-storage /bin/false
# Disable webcam if not needed
install uvcvideo /bin/false
Disabling Firewire and Thunderbolt modules is particularly important for physical security because these interfaces support Direct Memory Access (DMA), which can be exploited to read or write arbitrary system memory, bypassing all software security controls including disk encryption passwords held in memory.
SSH Hardening
If SSH access is required, the server configuration should be significantly tightened from default settings. The following configuration in /etc/ssh/sshd_config implements current best practices:
# Authentication
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no
AuthenticationMethods publickey
MaxAuthTries 3
MaxSessions 2
LoginGraceTime 30
# Cryptographic settings
KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
HostKeyAlgorithms ssh-ed25519
# Restrict access
AllowUsers specificuser
ClientAliveInterval 300
ClientAliveCountMax 2
# Disable unnecessary features
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
PermitTunnel no
Banner none
Automated hardening scripts can help ensure consistent configuration across systems. The konstruktoid/hardening repository on GitHub provides a comprehensive, well-maintained set of hardening scripts for Ubuntu and Debian systems that implement many of the practices described in this guide. For broader multi-distribution support, the trimstray/the-practical-linux-hardening-guide is an excellent reference.
Additional Hardening Measures
Beyond the major categories covered above, several additional measures contribute to a thoroughly hardened system. Configure automatic security updates to ensure patches are applied promptly. On Debian-based systems, install and configure unattended-upgrades to automatically install security updates. Set up file integrity monitoring with tools like AIDE (Advanced Intrusion Detection Environment) to detect unauthorized changes to critical system files. Configure /tmp and /var/tmp as separate partitions with noexec,nosuid,nodev mount options to prevent execution of malicious files placed in world-writable directories. Restrict core dumps by setting * hard core 0 in /etc/security/limits.conf and fs.suid_dumpable = 0 in sysctl to prevent sensitive data from being written to disk in crash dumps.
For users specifically concerned with anonymity, these hardening measures should be combined with network-level protections covered in our articles on DNS privacy and leak prevention and browser fingerprinting defense. A hardened operating system forms the foundation upon which all other privacy and anonymity tools depend. Without it, even the strongest encryption and the most carefully configured Tor setup can be undermined by a compromised kernel or a rogue service leaking data outside the protected channel. The key principle is defense in depth: no single measure is sufficient, but each layer adds meaningful resistance to attack, and an adversary must overcome all of them to achieve a complete compromise.