Hardening a Linux system is the process of systematically closing those doors. It is not a product you install — it is a posture you build, layer by layer, across the operating system. The process covers user access controls, network exposure, kernel behavior, service footprint, filesystem permissions, audit logging, and continuous monitoring. Each layer reinforces the others. None of them alone is sufficient.
This article walks through the core hardening disciplines that apply to enterprise Linux environments — whether you are running RHEL, Ubuntu Server, Debian, or any major distribution. The principles map directly to established compliance frameworks including CIS Benchmarks Level 1 and Level 2, DISA STIGs, and NIST 800-53. Where specific commands appear, treat them as reference points. Always test configuration changes before applying them in production, and always back up the system first.
Aggressive hardening can lock you out of a system. Before applying SSH restrictions, firewall rules, or SELinux policy changes, verify you have an alternate access method — such as out-of-band console access or a cloud provider's recovery console.
User Account Controls and Privilege Management
User accounts are one of the most common entry points attackers exploit after initial access. Misconfigured privileges, shared accounts, and stale credentials all create lateral movement opportunities once an attacker has a foothold. Hardening starts here.
The root account is the highest-value target on any Linux system. Direct root login should be disabled — both for SSH and for local console where policy permits. Instead, designated administrators should authenticate as their own named account and escalate privileges through sudo for specific operations. This creates an audit trail that root logins erase.
# Disable direct root login via SSH
# In /etc/ssh/sshd_config:
PermitRootLogin no
# Restrict sudo to a specific group
# In /etc/sudoers (use visudo):
%wheel ALL=(ALL) ALL
Beyond root, enforce the principle of least privilege across all accounts. Service accounts should be non-interactive — they should not have login shells or home directories unless operationally required. Review /etc/passwd periodically for accounts with shells assigned to services that do not need them. Lock or remove accounts that are no longer active.
Password policy also matters, even in environments moving toward key-based authentication. PAM (Pluggable Authentication Modules) controls password complexity, aging, and lockout behavior. Configure pam_faillock or pam_tally2 to lock accounts after repeated failed authentication attempts — a direct countermeasure against brute-force attacks.
CIS Benchmarks recommend locking accounts after no more than five failed login attempts. DISA STIGs set this threshold at three. Choose the value that matches your organization's risk posture and operational tolerance.
SSH Hardening
SSH is the administrative interface for nearly every Linux server in production. It is also a permanent target. The default sshd_config on many distributions ships with settings that prioritize broad compatibility over security — including support for older protocol behaviors and password-based authentication. Those defaults need to go.
The single highest-impact change is disabling password authentication entirely and requiring SSH key pairs. Password logins are vulnerable to brute force. Keys are not. Once key-based access is confirmed and tested, set PasswordAuthentication no in /etc/ssh/sshd_config and restart the service. From that point forward, only clients with a valid private key can authenticate.
# Recommended sshd_config hardening settings
Protocol 2
PermitRootLogin no
PasswordAuthentication no
PermitEmptyPasswords no
MaxAuthTries 4
LoginGraceTime 60
X11Forwarding no
IgnoreRhosts yes
HostbasedAuthentication no
PermitUserEnvironment no
ClientAliveInterval 300
ClientAliveCountMax 0
LogLevel INFO
AllowUsers admin_user deploy_user
The AllowUsers or AllowGroups directive creates an explicit allowlist. Anyone not on that list cannot authenticate via SSH regardless of their system account status. Combine this with MaxAuthTries 4 to limit brute-force attempts and ClientAliveInterval 300 to terminate idle sessions after five minutes of inactivity.
Fail2Ban complements these settings by monitoring authentication logs and dynamically blocking IP addresses that exceed failed login thresholds. It works at the firewall level, inserting temporary deny rules for offending source addresses. It does not replace SSH key enforcement — it adds an additional layer on top of it.
Changing the default SSH port from 22 reduces automated scanning noise but does not constitute a security control. Treat it as a minor friction measure, not a hardening step. — Common security operations guidance
Reducing the Attack Surface: Services and the Firewall
Every running service that is not operationally required is an unnecessary exposure. Services that have known vulnerabilities, accept network connections, or run with elevated privileges expand the attack surface without contributing to the system's purpose. The guidance is straightforward: identify what is running, determine what is required, and disable everything else.
# List all enabled services
systemctl list-unit-files --state=enabled
# Disable a service that is not required
systemctl disable --now cups
systemctl disable --now avahi-daemon
systemctl disable --now bluetooth
Once the service footprint is minimized, enforce network boundaries with a host-based firewall. On RHEL-family systems, firewalld is the standard tool. On Debian and Ubuntu, ufw provides a simplified interface over iptables. Either approach should default to a deny-all inbound posture with explicit allow rules for only the ports and protocols the system legitimately serves.
# UFW example: default deny inbound, allow SSH and HTTPS
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 443/tcp
ufw enable
ufw status verbose
Network-facing kernel parameters can further tighten exposure. The sysctl interface controls how the kernel handles network behavior. Key parameters to configure include disabling IP forwarding (unless the system is a router), disabling ICMP redirects, enabling SYN flood protection, and ignoring broadcast pings. These settings belong in /etc/sysctl.d/ as a persistent configuration file.
# /etc/sysctl.d/99-hardening.conf
net.ipv4.ip_forward = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.tcp_syncookies = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.conf.all.log_martians = 1
kernel.randomize_va_space = 2
kernel.randomize_va_space = 2 enables full Address Space Layout Randomization (ASLR). This makes it significantly harder for attackers to predict memory addresses when attempting exploitation, and should be set on every production system.
Mandatory Access Control: SELinux and AppArmor
Standard Linux permissions — the user/group/other model — control who can access a file. Mandatory Access Control (MAC) goes further: it controls what a process is allowed to do, regardless of the account it runs under. If an attacker exploits a vulnerability in a service, MAC policies constrain the blast radius to what that service is permitted to touch.
SELinux is the MAC framework used on RHEL, CentOS, AlmaLinux, and Rocky Linux. It ships in enforcing mode on these distributions by default, and it should stay there. Disabling SELinux because a service throws AVC denials is the wrong response — the right response is to audit the denial and write a policy that addresses the legitimate need without creating broad exceptions.
# Check SELinux status
getenforce
sestatus
# Never disable SELinux — set to permissive temporarily for troubleshooting only
setenforce 0 # permissive (audit only, do not use in production)
setenforce 1 # enforcing (correct production state)
# Audit SELinux denials
ausearch -m avc -ts recent
audit2why -a
AppArmor serves the same purpose on Ubuntu and Debian-based systems. It uses path-based profiles rather than labels, which makes it somewhat more approachable to configure. The Ubuntu default installation includes AppArmor profiles for common services. Verify that AppArmor is active and that profiles for running services are in enforce mode — not complain mode.
Setting SELINUX=disabled in /etc/selinux/config is one of the single largest security regressions you can make on a RHEL-family system. It removes an entire enforcement layer and is explicitly called out as a failure condition in DISA STIG audits. Do not do this to quiet a service complaint.
Patching, Audit Logging, and Continuous Monitoring
Hardening is not a one-time event. A system that was hardened twelve months ago and never touched again has accumulated unpatched CVEs, possibly drifted from its baseline, and may have had new services enabled without review. Maintaining a hardened posture requires ongoing processes, not just initial configuration.
Patch management is the most foundational of these. Unpatched vulnerabilities are the mechanism behind the majority of successful intrusions. Security patches should be applied promptly — ideally within 24 to 72 hours of release for critical and high-severity CVEs. Automated patching tools (dnf-automatic on RHEL, unattended-upgrades on Ubuntu) can handle security updates without requiring full maintenance windows. For kernel patches specifically, live-patching solutions allow updates to apply without reboots.
Audit logging through auditd provides the visibility needed to detect suspicious activity and support forensic analysis after an incident. Configure audit rules to capture authentication events, privilege escalation, file access to sensitive paths, and changes to system configuration files. The logs themselves should be protected — forwarded to a centralized SIEM or logging infrastructure where they cannot be tampered with by a compromised host.
# Key auditd rules for a hardened baseline
# Monitor changes to /etc/passwd and /etc/shadow
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
# Monitor sudo usage
-w /etc/sudoers -p wa -k scope
# Monitor SSH configuration changes
-w /etc/ssh/sshd_config -p wa -k sshd_config
# Capture privilege escalation
-a always,exit -F arch=b64 -S setuid -k privileged
Regular security audits close the loop. Tools like Lynis perform comprehensive host-based assessments and score the system against a hardening index, flagging areas of concern with remediation guidance. OpenSCAP evaluates the system directly against CIS Benchmark or STIG profiles and produces detailed compliance reports. Run these audits on a scheduled basis and treat regressions as incidents worth investigating.
Key Takeaways
- Default configurations are not secure configurations. Every production Linux system requires deliberate hardening before it is ready to handle sensitive workloads or face internet exposure.
- SSH is the administrative front door — treat it accordingly. Disable password authentication, restrict root login, limit allowed users, and enforce idle session timeouts. Add Fail2Ban for brute-force deterrence.
- Minimize your service footprint and enforce firewall rules. Every unnecessary service is unnecessary risk. Default-deny inbound firewall posture with explicit allow rules keeps exposure limited to what the system actually serves.
- Do not disable SELinux or AppArmor. When these enforcement layers produce alerts, investigate and write targeted policy. Disabling them trades a minor administrative inconvenience for a major security regression.
- Hardening requires maintenance. Patch promptly, forward audit logs to a centralized location, run periodic baseline assessments with Lynis or OpenSCAP, and investigate configuration drift before it becomes a breach.
A hardened Linux system is not an impenetrable one — no such thing exists. What it is, is a system where the attacker's job is meaningfully harder at every layer. Unnecessary services eliminated. Authentication paths tightened. Kernel behavior constrained. Mandatory access controls enforced. Audit logs preserved. That combination of controls, maintained consistently over time, is what transforms a default install into a defensible platform.