This part follows Linux security hardening with some system-level protections: kernel hardening parameters, SELinux access control, and audit logging that tracks everything that is happening on your system.

SELinux Mandatory Access Control
SELinux can be complicated but it’s a great kernel security feature that keeps blocking unauthorized actions on your system.
Even if it can be challenging to configure, SELinux has great defaults for most common applications, so for 90% of use cases you won’t even notice SELinux running. If SELinux is blocking something in a default configuration, you an add a policy exception to allow the action. Do not disable SELinux.
Check SELinux Status
First, verify SELinux is actually enabled and enforcing.
# Check current mode
getenforce
# Should show "Enforcing"
# Detailed status
sestatus
# Shows: SELinux status, policy type, mode
# Check if SELinux is denying anything
ausearch -m avc -ts recent
Enforcing mode means SELinux actively blocks unauthorized actions. Permissive mode logs violations but doesn’t block them, which is useful for debugging but provides no actual protection.
Understanding SELinux Contexts
Every file, process, port, and user in SELinux has a security context (also called a “label”). These contexts determine what can access what. The format is user:role:type:level, but for most cases, you only care about the type field.
For example, web server files should have httpd_sys_content_t type, and the httpd process runs with httpd_t type. SELinux policy allows httpd_t processes to read httpd_sys_content_t files, but not user_home_t files (which are in user home directories). This means even if httpd is compromised and running as root, it still can’t access SSH keys in /root/.ssh/ because the SELinux policy denies it.
View file and process contexts.
# Show file context
ls -Z /var/www/html/index.html
# Output: unconfined_u:object_r:httpd_sys_content_t:s0
# Show process context
ps -eZ | grep httpd
# Output: system_u:system_r:httpd_t:s0 1234 ? 00:00:00 httpd
# Check context of running services
systemctl status httpd
When you create or move files, they might inherit the wrong context. This is the most common cause of SELinux denials. For instance, if you copy a file from your home directory to /var/www/html/, it might keep the user_home_t context instead of getting httpd_sys_content_t.
Fixing Context Issues
The restorecon command restores default contexts based on file location.
# Restore context for a single file
restorecon /var/www/html/index.html
# Restore recursively for a directory
restorecon -Rv /var/www/html/
# Preview what would change without applying
restorecon -Rvn /var/www/html/
For permanent custom contexts (like if you’re hosting web content from a non-standard location), use semanage fcontext.
# Add persistent context rule for custom web directory
semanage fcontext -a -t httpd_sys_content_t "/web/custom(/.*)?"
# Apply the new rule (restores the policy)
restorecon -Rv /web/custom/
# View custom context rules
semanage fcontext -l | grep custom
SELinux Booleans
SELinux uses booleans to toggle specific behaviors without rewriting policies so you can turn them on and off for specific scenarios. The -P option makes changes persistent across reboots.
# List all booleans
getsebool -a
# Check specific boolean
getsebool httpd_can_network_connect
# Example
# Allow httpd to make outbound network connections (for reverse proxies, API calls)
setsebool -P httpd_can_network_connect on
Troubleshooting SELinux Denials
When SELinux blocks something, it logs an “AVC denial” in the audit log. These messages are cryptic, but audit2why translates them into human-readable explanations.
# View recent denials
ausearch -m avc -ts recent
# Explain why access was denied
ausearch -m avc -ts recent | audit2why
Important: Don’t just blindly run audit2allow and install whatever policy it generates. Read the denial, understand what’s being blocked, and determine if the application should actually be allowed to do that. Sometimes SELinux is protecting you from a misconfigured or compromised application.
For example, if you see denials for httpd trying to execute files in /tmp/, the correct fix isn’t to allow it. The correct fix is to figure out why httpd is trying to execute temp files and fix the application configuration.
Audit System Activity
I’ts important to check what is happening on your system. What ports are opened, what is running, what is being accessed.
Check Listening Network Services
Check what services are exposed on the network. Any listening port is a potential attack vector.
# Show all listening TCP/UDP ports with process info
ss -tulpn
# Alternative using lsof
lsof -i -P -n | grep LISTEN
# Show only TCP listening ports
ss -tln
# Show listening ports with numeric addresses (no DNS lookups)
netstat -tuln
The output shows which ports are bound to which interfaces. Pay attention to:
- Ports listening on
0.0.0.0(all interfaces) vs127.0.0.1(localhost only) - Unexpected services you don’t remember installing
- High port numbers that might be backdoors or misconfigurations
For each listening port, verify:
- What service owns it (
ss -tulpnshows the process) - Whether you actually need it exposed
- If it should be restricted to localhost instead of all interfaces
If you find services listening that you don’t recognize, investigate before disabling.
# Identify which package owns the binary
rpm -qf $(which servicename)
# Check what the service does
systemctl status servicename
man servicename
Auditd for System Event Logging
The standard system logging with journald captures service messages and errors, but it doesn’t track detailed security events like file accesses or authentication attempts. That’s what auditd does.
Auditd works at the kernel level. It can track virtually any system activity (file accesses, system calls, network connections, user commands).
Install and enable auditd.
# Should already be installed on RHEL/Fedora
dnf install audit
# Enable and start service
systemctl enable --now auditd
# Check status
systemctl status auditd
Configure Audit Rules
Audit rules define what events to log. You can monitor specific files, directories, system calls, or user actions. Rules are defined in /etc/audit/rules.d/audit.rules or /etc/audit/audit.rules.
Some useful audit rules.
# Monitor changes to system authentication files
-w /etc/passwd -p wa -k passwd_changes
-w /etc/shadow -p wa -k shadow_changes
-w /etc/group -p wa -k group_changes
-w /etc/sudoers -p wa -k sudoers_changes
# Monitor SSH configuration
-w /etc/ssh/sshd_config -p wa -k sshd_config_changes
# Track sudo usage
-a always,exit -F arch=b64 -S execve -F auid>=1000 -F auid!=-1 -F key=sudo_commands
# Monitor file deletion by users
-a always,exit -F arch=b64 -S unlink -S unlinkat -S rename -S renameat -F auid>=1000 -F auid!=-1 -k file_deletion
# Watch critical directories
-w /etc/ -p wa -k etc_changes
-w /boot/ -p wa -k boot_changes
The -w flag watches a file or directory. The -p flag specifies permissions to monitor:
r- readw- writex- executea- attribute change
Add your custom rules to /etc/audit/rules.d/custom.rules.
# Create custom rules file
vim /etc/audit/rules.d/custom.rules
# Reload audit rules
augenrules --load
# Or restart auditd
service auditd restart
# Verify rules are loaded
auditctl -l
Kernel Hardening
You can configure the Linux kernel with sysctl to improve its security, networking, and performance.
Memory Protection
The /proc filesystem exposes kernel memory addresses and other sensitive information. By default, any process can read these, which helps attackers bypass address space layout randomization.
Hide Kernel Pointers
The kernel.kptr_restrict parameter controls access to kernel pointer addresses.
# 0 = Show kernel pointers (default, insecure)
# 1 = Hide from unprivileged users, show to root
# 2 = Hide from everyone, including root
kernel.kptr_restrict=2
Setting this to 2 hides kernel pointers from everyone, which is most secure. However, some performance monitoring tools (like perf) need access to kernel symbols to function properly. If you run into issues with debugging tools, set it to 1 instead, which hides pointers from regular users but allows root access.
Restrict Access to Kernel Logs
Kernel logs (accessible via dmesg) can leak sensitive information about hardware, kernel addresses, and system configuration. Restrict access to root only.
# Prevent regular users from reading kernel ring buffer
kernel.dmesg_restrict=1
This should be enabled by default on modern systems, but it’s worth verifying, especially on older installations or custom kernels.
Network Security Parameters
Several kernel networking parameters improve security against common network-based attacks:
SYN Flood Protection
SYN floods are a type of denial-of-service attack that exploits the TCP three-way handshake. Enabling SYN cookies allows the kernel to handle SYN floods without maintaining state for every connection attempt.
# Enable SYN cookies
net.ipv4.tcp_syncookies=1
This is usually enabled by default, but verify it’s set. There’s no real downside to SYN cookies in modern kernels.
Reverse Path Filtering (Anti-Spoofing)
Reverse path filtering verifies that incoming packets actually came from the network they claim to. This prevents IP spoofing attacks.
# Enable strict reverse path filtering on all interfaces
net.ipv4.conf.all.rp_filter=1
net.ipv4.conf.default.rp_filter=1
Mode 1 is strict (it drops packets if the return path doesn’t match the incoming interface. Mode 2 is loose) it only checks if the source IP is routable. For most systems, strict mode is appropriate. If you’re doing asymmetric routing (traffic comes in one interface and goes out another), you might need loose mode.
Disable IP Forwarding
Unless your system is a router, IP forwarding should be disabled.
# Disable IPv4 forwarding
net.ipv4.ip_forward=0
# Disable IPv6 forwarding
net.ipv6.conf.all.forwarding=0
This prevents your system from routing packets between network interfaces, which could be exploited to bypass firewall rules or conduct man-in-the-middle attacks.
Disable IPv6 (If Not Using It)
If you’re not using IPv6, disable it entirely to reduce attack surface.
# Disable IPv6 on all interfaces
net.ipv6.conf.all.disable_ipv6=1
net.ipv6.conf.default.disable_ipv6=1
Be aware this breaks applications that try to bind to IPv6 addresses. Most applications handle this gracefully, but some (like certain database connectors) might fail. Test thoroughly before deploying this in production.
Ignore ICMP Redirects
ICMP redirects tell hosts to use a different route for certain destinations. Accepting these from untrusted networks can be used for man-in-the-middle attacks.
# Ignore ICMP redirect messages
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
# Don't send ICMP redirects (if not a router)
net.ipv4.conf.all.send_redirects=0
net.ipv4.conf.default.send_redirects=0
Apply Sysctl Settings
Create a custom sysctl configuration file for all your hardening parameters.
# Create hardening configuration
sudo tee /etc/sysctl.d/99-hardening.conf << EOF
# Memory protection
kernel.kptr_restrict=2
kernel.dmesg_restrict=1
# Network security
net.ipv4.tcp_syncookies=1
net.ipv4.conf.all.rp_filter=1
net.ipv4.conf.default.rp_filter=1
net.ipv4.ip_forward=0
net.ipv4.conf.all.accept_redirects=0
net.ipv4.conf.default.accept_redirects=0
net.ipv4.conf.all.send_redirects=0
net.ipv4.conf.default.send_redirects=0
# IPv6 security (if using IPv6)
net.ipv6.conf.all.forwarding=0
net.ipv6.conf.all.accept_redirects=0
net.ipv6.conf.default.accept_redirects=0
EOF
# Load the new settings
sudo sysctl -p /etc/sysctl.d/99-hardening.conf
# Verify settings are applied
sysctl -a | grep kptr_restrict
sysctl -a | grep dmesg_restrict
sysctl -a | grep tcp_syncookies
Settings in /etc/sysctl.d/ persist across reboots. The 99- prefix ensures this file loads after other sysctl configs, so your settings take precedence. This is a good approach because we add it in the drop-in directory instead of writing it directly to /etc/sysctl.
Always Use a VPN!
Always use a paid (or self-hosted) VPN for all your connections.
Commercial VPN Services
When using a VPN you are trusting the VPN provider instead of your ISP. Providers like Mullvad or ProtonVPN are good choices as they are zero-knowledge and privacy friendly.
Self-Hosted VPN (Tailscale/Headscale)
For some connections I also recommend Headscale or Tailscale, which creates a mesh VPN between all my devices. Its purpose is not to hide traffic but to create a secure connections between my devices regardless of what network they’re on.
What’s Next?
These are the most common ways to secure a Linux system, however security is a never-ending process and there is always new vulnerabilities and changes happening so keep this practice up-to-date.
Perfect security doesn’t exist, so keep at it.