Linux Server Security Hardening

4 Nov 2026, 11:2828 min readby ITYantra Admin

Complete guide to hardening Linux servers including SSH security, firewall configuration, kernel hardening, audit logging, and compliance with CIS Linux benchmarks.

# Linux Server Security Hardening

Introduction

Linux powers the majority of internet-facing servers, cloud infrastructure, and enterprise workloads. This makes Linux servers a primary target for attackers. Security hardening reduces the attack surface by removing unnecessary services, configuring strict access controls, and implementing continuous monitoring.

This tutorial provides a comprehensive guide to hardening Linux servers against common attack vectors. The procedures apply to Ubuntu 24.04 LTS, RHEL 9, and Debian 12, with distribution-specific notes where applicable.

Learning Objectives

By the end of this tutorial, you will be able to:

  • Harden SSH configuration against brute-force and credential attacks
  • Configure netfilter/iptables or nftables firewalls with default-deny policies
  • Apply kernel hardening via sysctl parameters
  • Implement mandatory access control with SELinux or AppArmor
  • Configure auditd for comprehensive system auditing
  • Apply CIS Linux Benchmark recommendations
  • Automate security updates and patch management

Technical Background

Linux Security Architecture

Linux security operates at multiple layers:

1. **Discretionary Access Control (DAC):** Traditional Unix file permissions (owner/group/other) 2. **Mandatory Access Control (MAC):** SELinux, AppArmor, Smack enforce policy-based access 3. **Linux Security Modules (LSM):** Kernel framework supporting multiple MAC modules 4. **Namespaces and Cgroups:** Container isolation and resource control 5. **Capabilities:** Fine-grained privilege separation beyond root/non-root

Common Linux Attack Vectors

| Vector | Description | Mitigation | |--------|-------------|------------| | SSH brute-force | Automated password guessing against SSH | Key-based auth, fail2ban, port change | | Outdated software | Known vulnerabilities in unpatched packages | Automated security updates | | Misconfigured services | Services listening on all interfaces | Bind to localhost, firewall rules | | Privilege escalation | Kernel exploits, sudo misconfiguration | Kernel hardening, restricted sudo | | Weak file permissions | World-readable sensitive files | umask 077, strict file ownership |

Prerequisites

  • Root or sudo access to a Linux server
  • Familiarity with Linux command line and text editors (vim/nano)
  • Understanding of Linux file permissions and ownership
  • Basic networking knowledge (iptables/nftables, TCP/UDP ports)

Hardware and Software Requirements

| Component | Requirement | |-----------|-------------| | OS | Ubuntu 24.04 LTS, RHEL 9, AlmaLinux 9, or Debian 12 | | SSH | OpenSSH server | | Firewall | UFW (Ubuntu), firewalld (RHEL), or nftables | | MAC | AppArmor (Ubuntu/Debian) or SELinux (RHEL/AlmaLinux) | | Auditing | auditd | | Intrusion Prevention | fail2ban | | Vulnerability Scanning | Lynis, OpenSCAP |

Step-by-Step Implementation

Phase 1: SSH Hardening

#### 1.1 Disable Password Authentication

Edit `/etc/ssh/sshd_config`:

# Disable password authentication
PasswordAuthentication no
PermitEmptyPasswords no
PermitRootLogin no

# Use only protocol 2 (default in modern OpenSSH)
Protocol 2

# Limit authentication attempts
MaxAuthTries 3
MaxSessions 2

# Disable X11 forwarding
X11Forwarding no
AllowTcpForwarding no

# Set login grace time
LoginGraceTime 60

# Use key-based authentication only
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys

# Restrict allowed users
AllowUsers admin deploy

# Disable agent forwarding
AllowAgentForwarding no

Apply changes:

sudo sshd -t && sudo systemctl reload ssh
# or on RHEL-based systems:
sudo systemctl reload sshd

#### 1.2 Configure SSH Key-Based Authentication

Generate key pair on client:

ssh-keygen -t ed25519 -a 100 -f ~/.ssh/id_ed25519_server

Copy public key to server:

ssh-copy-id -i ~/.ssh/id_ed25519_server.pub admin@server-ip

Verify key-based login works, then disable password authentication.

#### 1.3 Install and Configure fail2ban

# Ubuntu/Debian
sudo apt update && sudo apt install -y fail2ban

# RHEL/AlmaLinux
sudo dnf install -y fail2ban

Create `/etc/fail2ban/jail.local`:

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600

[sshd-ddos]
enabled = true
port = ssh
filter = sshddos
logpath = /var/log/auth.log
maxretry = 5
bantime = 86400
findtime = 600
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd

Phase 2: Firewall Configuration

#### 2.1 UFW (Ubuntu/Debian)

# Reset to defaults
sudo ufw reset

# Default deny incoming, allow outgoing
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH (from specific IP ranges if possible)
sudo ufw allow from 10.0.0.0/8 to any port 22 proto tcp
# or for specific IP:
# sudo ufw allow from 203.0.113.10 to any port 22 proto tcp

# Allow HTTPS
sudo ufw allow 443/tcp

# Allow HTTP (if needed)
sudo ufw allow 80/tcp

# Enable firewall
sudo ufw enable

# Verify
sudo ufw status verbose

#### 2.2 firewalld (RHEL/AlmaLinux)

# Set default zone to drop
sudo firewall-cmd --set-default-zone=drop

# Add SSH service (restrict to specific source if possible)
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-service=http

# Reload firewall
sudo firewall-cmd --reload

# Verify
sudo firewall-cmd --list-all-zones

#### 2.3 nftables (Advanced)

Create `/etc/nftables.conf`:

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        iifname "lo" accept
        ct state established,related accept
        ct state invalid drop
        ip protocol icmp accept
        tcp dport { 22, 80, 443 } ct state new accept
        ip saddr 10.0.0.0/8 tcp dport 22 accept
    }

    chain forward {
        type filter hook forward priority 0; policy drop;
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}
sudo nft -f /etc/nftables.conf
sudo systemctl enable --now nftables

Phase 3: Kernel Hardening

#### 3.1 sysctl Configuration

Create `/etc/sysctl.d/99-security.conf`:

# IP spoofing protection
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Ignore ICMP broadcasts
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Ignore ICMP redirects
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

# Log suspicious packets
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1

# TCP SYN cookie protection
net.ipv4.tcp_syncookies = 1

# Disable IPv6 if not used
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1

# Protect kernel pointers
kernel.kptr_restrict = 2

# Restrict dmesg
kernel.dmesg_restrict = 1

# Enable ASLR
kernel.randomize_va_space = 2

# Protect against symlink attacks
fs.protected_symlinks = 1
fs.protected_hardlinks = 1

# Increase ephemeral port range
net.ipv4.ip_local_port_range = 32768 65535

Apply changes:

sudo sysctl --system

Verify key settings:

sysctl net.ipv4.conf.all.rp_filter
sysctl net.ipv4.tcp_syncookies

Phase 4: Mandatory Access Control

#### 4.1 AppArmor (Ubuntu/Debian)

AppArmor is enabled by default on Ubuntu. Verify status:

sudo aa-status
sudo apparmor_status

Enforce profiles for critical services:

# Set nginx to enforce mode
sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx

# Set all profiles to enforce
sudo aa-enforce /etc/apparmor.d/*

Create custom profile for a service:

# Generate profile from logs
sudo aa-genprof /usr/sbin/custom-service

#### 4.2 SELinux (RHEL/AlmaLinux)

Verify SELinux status:

sestatus
getenforce

Set to enforcing mode:

sudo setenforce 1

Make persistent in `/etc/selinux/config`:

SELINUX=enforcing
SELINUXTYPE=targeted

Restore default file contexts:

sudo restorecon -Rv /etc/ssh

Phase 5: Audit Logging

#### 5.1 Install and Configure auditd

# Ubuntu/Debian
sudo apt install -y auditd audispd-plugins

# RHEL/AlmaLinux
sudo dnf install -y audit

Configure `/etc/audit/rules.d/custom.rules`:

# Track all changes to /etc
-w /etc/ -p wa -k etc_changes

# Track SSH configuration
-w /etc/ssh/sshd_config -p wa -k sshd_config

# Track sudo usage
-w /var/log/sudo.log -p wa -k sudo_actions

# Track user account changes
-w /etc/passwd -p wa -k account_changes
-w /etc/group -p wa -k account_changes
-w /etc/shadow -p wa -k shadow_changes

# Track system calls
-a always,exit -F arch=b64 -S adjtimex -S settimeofday -k time_change
-a always,exit -F arch=b64 -S mount -k mounts

Load rules:

sudo augenrules --load
sudo systemctl restart auditd

Verify rules loaded:

sudo auditctl -l

View audit logs:

sudo ausearch -k etc_changes
sudo aureport --file-report

Phase 6: Automatic Security Updates

#### 6.1 Ubuntu/Debian - unattended-upgrades

sudo apt install -y unattended-upgrades apt-listchanges

Configure `/etc/apt/apt.conf.d/50unattended-upgrades`:

Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
    "${distro_id}:${distro_codename}-updates";
};

Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Mail "admin@ityantra.co.in";

Enable:

sudo dpkg-reconfigure -plow unattended-upgrades

#### 6.2 RHEL/AlmaLinux - dnf-automatic

sudo dnf install -y dnf-automatic

Edit `/etc/dnf/automatic.conf`:

[commands]
upgrade_type = security
apply_updates = yes

[emitters]
emit_via = stdio

[emitters.email]
email_from = root@ityantra.co.in
email_to = admin@ityantra.co.in
email_host = localhost
sudo systemctl enable --now dnf-automatic.timer

Verification and Expected Outputs

| Check | Method | Expected Result | |-------|--------|-----------------| | SSH password auth disabled | `ssh -o PasswordAuthentication=yes user@host` | Permission denied | | Root login disabled | `ssh root@host` | Permission denied | | Firewall active | `sudo ufw status` or `firewall-cmd --state` | Status: active/running | | Default deny | Attempt connection to blocked port | Connection refused | | SELinux/AppArmor enforcing | `getenforce` or `aa-status` | Enforcing/loaded | | Audit rules loaded | `sudo auditctl -l` | Rules listed | | Auto-updates enabled | Check service status | Service active |

Troubleshooting Scenarios

| Issue | Diagnosis | Resolution | |-------|-----------|------------| | Locked out after SSH changes | Misconfigured sshd_config | Maintain existing session, test with `sshd -t` before reload | | fail2ban not blocking | Wrong log path | Verify logpath matches distribution (`/var/log/auth.log` vs `/var/log/secure`) | | Firewall blocking legitimate traffic | Overly restrictive rules | Add specific allow rules before reload | | SELinux blocking service | SELinux in enforcing mode | Check `ausearch -m avc` and set proper file contexts | | auditd not logging | Rules not loaded | Run `augenrules --load` and restart auditd | | Package updates breaking service | Automated updates applied | Pin critical packages, test updates in staging first |

Security Best Practices

  • **Maintain an out-of-band access method** (console access, IPMI, cloud serial console) before applying SSH or firewall changes
  • **Test changes in a staging environment** before applying to production
  • **Keep a rollback plan** for every security change
  • **Use configuration management** (Ansible, Puppet, Chef) for consistent hardening across all servers
  • **Document all deviations** from the hardening standard with approved exceptions
  • **Run compliance scans monthly** using Lynis or OpenSCAP
  • **Monitor audit logs** daily and forward to SIEM

Enterprise Considerations

  • **Baseline configuration:** Maintain a hardened system image as the baseline for all deployments
  • **Configuration drift:** Use tools like Ansible or Chef to detect and remediate drift
  • **Change management:** All hardening changes must go through change advisory board (CAB)
  • **Separation of environments:** Apply stricter controls in production than development
  • **Asset inventory:** Maintain inventory of all Linux servers with OS version, patch level, and owner

Conclusion

Linux server security hardening is a foundational practice that significantly reduces the attack surface. By combining SSH hardening, firewall rules, kernel parameters, mandatory access control, audit logging, and automated updates, you create a defensible server environment. Hardening must be maintained continuously—servers drift from their secure baseline over time and require regular compliance validation.

Frequently Asked Questions

**Q: Should I change the default SSH port?** A: Changing SSH port reduces noise from automated scanners but does not provide real security. Focus on key-based authentication, fail2ban, and firewall restrictions instead.

**Q: SELinux or AppArmor?** A: Use whichever is native to your distribution. SELinux (RHEL family) offers finer-grained control but has a steeper learning curve. AppArmor (Ubuntu/Debian) is easier to configure and maintain.

**Q: How often should I run vulnerability scans?** A: Run authenticated vulnerability scans at least monthly, and after any significant change to the server configuration or installed software.

Official Technical References

  • CIS Linux Benchmark: https://www.cisecurity.org/benchmark/linux/
  • NSA Linux Hardening: https://www.nsa.gov/what-we-do/cybersecurity/defensive-cyber-operations/
  • OpenSCAP: https://www.open-scap.org/
  • Lynis: https://cisofy.com/lynis/
  • Linux Security Modules: https://kernel.org/doc/html/latest/admin-guide/LSM/index.html

Related ITYantra Tutorials

  • Enterprise Cybersecurity Fundamentals (cybersecurity)
  • SSH Security and Access Control (cybersecurity)
  • Firewall Architecture and Network Security (cybersecurity)
  • Linux Network Configuration and Troubleshooting (linux)
  • Linux Systemd Services and Boot Process (linux)
#selinux#cis-benchmark#auditd#kernel-hardening#firewall#ssh-hardening#server-hardening#linux-security