Linux Server Hardening: CIS Benchmark, SSH, auditd and AppArmor
Comprehensive hardening guide for Linux servers based on the CIS Benchmark Level 2.
Table of Contents (9 sections)
A freshly installed Linux server is not a secure Linux server. Default configurations are optimized for compatibility, not security: SSH root login enabled, no audit logs, no mandatory access controls, no automatic updates. This guide brings Ubuntu 22.04/24.04 LTS and RHEL 8/9 up to CIS Benchmark Level 1 - the foundation for any production server.
Basic Hardening: First Steps
System Updates and Package Management
# Ubuntu/Debian: Full update
apt update && apt upgrade -y
apt autoremove -y
# RHEL/Rocky/AlmaLinux
dnf update -y
dnf autoremove -y
# Configure automatic security updates
# Ubuntu:
apt install unattended-upgrades -y
dpkg-reconfigure --priority=low unattended-upgrades
# /etc/apt/apt.conf.d/50unattended-upgrades
# Automatic security updates only:
cat >> /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF'
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
};
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::Remove-Unused-Packages "true";
Unattended-Upgrade::Automatic-Reboot "false"; // Manual reboot after kernel updates
Unattended-Upgrade::Mail "security@firma.de";
EOF
# RHEL: dnf-automatic
dnf install dnf-automatic -y
sed -i 's/apply_updates = no/apply_updates = yes/' /etc/dnf/automatic.conf
sed -i 's/upgrade_type = default/upgrade_type = security/' /etc/dnf/automatic.conf
systemctl enable --now dnf-automatic.timer
Disable unnecessary services
# List all running services
systemctl list-units --type=service --state=active
# Typical services that are not necessary on production servers:
DISABLE_SERVICES=(
"bluetooth"
"cups" # Print service
"avahi-daemon" # mDNS/Bonjour
"rpcbind" # NFS (if not needed)
"postfix" # Mail (if no local MTA is needed)
"snapd" # Ubuntu: Snap (if not used)
)
for service in "${DISABLE_SERVICES[@]}"; do
if systemctl is-enabled "$service" 2>/dev/null | grep -q "enabled"; then
systemctl disable --now "$service"
echo "Disabled: $service"
fi
done
# Remove unnecessary packages
apt purge --auto-remove telnet ftp rsh-client \
finger talk ntpdate xinetd nis yp-tools -y 2>/dev/null || true
# Check sockets/network services
ss -tlnp # Which services are listening on which ports?
# Every service listed here must be justified!
SSH hardening
# /etc/ssh/sshd_config - secure configuration
# Create a backup
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
cat > /etc/ssh/sshd_config.d/hardening.conf << 'EOF'
# Protocol and Port
Port 22 # Recommendation: use a different port (security through obscurity, helps against script kiddies)
Protocol 2 # SSH v2 only!
# Authentication
PermitRootLogin no # Root login PROHIBITED
PasswordAuthentication no # SSH keys only! (after key deployment)
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM yes
# Access control
AllowUsers deploy monitoring # Only specific users
# OR: AllowGroups sshusers
MaxAuthTries 3 # Make brute force attacks more difficult
MaxSessions 4 # Max. concurrent sessions
LoginGraceTime 30 # 30s for login attempt
# Connection management
ClientAliveInterval 300 # 5-minute inactivity timeout
ClientAliveCountMax 2 # 2 retries → then disconnect
TCPKeepAlive no # Disable OS-level TCP keepalive
# Disable features
X11Forwarding no # X11 forwarding - unnecessary
AllowAgentForwarding no # SSH agent forwarding
AllowTcpForwarding no # TCP port forwarding (if not needed)
PermitTunnel no
GatewayPorts no
# Banner
Banner /etc/ssh/banner # Legal notice
# Cryptography (CIS Benchmark Level 2)
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group14-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
# Logging
SyslogFacility AUTH
LogLevel VERBOSE # Log fingerprint during key-based authentication
EOF
# Create banner
cat > /etc/ssh/banner << 'EOF'
*******************************************************************
* WARNING: This system is for authorized users only! *
* All activities are monitored and logged. *
* Unauthorized access will be prosecuted (§ 202a StGB) *
*******************************************************************
EOF
# Restart SSH service
sshd -t # Test configuration!
systemctl restart sshd
Kernel hardening (sysctl)
# /etc/sysctl.d/99-hardening.conf
cat > /etc/sysctl.d/99-hardening.conf << 'EOF'
# ===== Network hardening =====
# IP spoofing protection
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Reject ICMP redirects (Man-in-the-Middle prevention)
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
# Disable source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# No IP forwarding (except on routers/firewalls)
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0
# SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_syn_retries = 5
net.ipv4.tcp_synack_retries = 2
# ICMP Broadcast (Smurf Attack)
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
# Disable IPv6 (if not used)
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
# ===== Kernel Hardening =====
# ASLR (Address Space Layout Randomization) - maximum
kernel.randomize_va_space = 2
# Hide kernel pointers in /proc (make privilege escalation more difficult)
kernel.kptr_restrict = 2
# Restrict dmesg (no kernel leaks for normal users)
kernel.dmesg_restrict = 1
# Disable /proc/sysrq-trigger
kernel.sysrq = 0
# Disable core dumps (no passwords in core files)
kernel.core_pattern = |/bin/false
fs.suid_dumpable = 0
# Restrict ptrace (anti-debugging)
kernel.yama.ptrace_scope = 1
# ===== File System =====
# Hard links: only own (prevent symlink attacks)
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
# Protect FIFOs / regular files
fs.protected_fifos = 2
fs.protected_regular = 2
EOF
sysctl -p /etc/sysctl.d/99-hardening.conf
Audit logging with auditd
# Install auditd
apt install auditd audispd-plugins -y # Ubuntu
dnf install audit -y # RHEL
# /etc/audit/rules.d/hardening.rules
cat > /etc/audit/rules.d/99-hardening.rules << 'EOF'
# Audit log buffer (large enough for spikes)
-b 8192
# Deletions from the audit log should be noticeable
-e 2 # Immutable mode (rules can only be changed after a reboot)
# ===== Authentication =====
-w /etc/passwd -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d -p wa -k sudoers
# sudo usage
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid!=unset -k privileged
# SSH configuration
-w /etc/ssh/sshd_config -p wa -k sshd_config
# ===== System commands =====
# Privilege escalation
-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=unset -k sudo_usage
-a always,exit -F path=/bin/su -F perm=x -F auid>=1000 -F auid!=unset -k su_usage
# Dangerous Commands
-a always,exit -F arch=b64 -S execve -F path=/bin/bash -k shell_access
-w /usr/bin/passwd -p x -k passwd_change
-w /usr/sbin/useradd -p x -k user_management
-w /usr/sbin/userdel -p x -k user_management
-w /usr/sbin/groupadd -p x -k group_management
# ===== Network =====
-a always,exit -F arch=b64 -S socket,connect,accept4 -k network_connection
# ===== File Integrity =====
-w /etc/cron.d -p wa -k cron
-w /etc/crontab -p wa -k cron
-w /var/spool/cron -p wa -k cron
-w /etc/init.d -p wa -k init
-w /etc/systemd/system -p wa -k systemd_service
# Kernel Modules
-a always,exit -F arch=b64 -S init_module,delete_module -k kernel_modules
# Mount operations
-a always,exit -F arch=b64 -S mount,umount2 -k mount
EOF
systemctl enable --now auditd
auditctl -l # Check rules
# Log query examples:
ausearch -k sudo_usage -ts today # All sudo usage today
ausearch -k identity -ts today # Changes to /etc/passwd, etc.
aureport --summary # Summary
AppArmor (Ubuntu) / SELinux (RHEL)
# ===== AppArmor (Ubuntu/Debian) =====
# Check status
apparmor_status
# Enable AppArmor (if not active)
systemctl enable apparmor
systemctl start apparmor
# List profiles
aa-status # Enforce vs. Complain Mode
# Create a new profile (example: Python web server)
apt install apparmor-utils -y
# Complain Mode (Logging without blocking - for profile development)
aa-genprof /usr/bin/python3
# Start the application, perform all necessary actions
# Then: Refine AppArmor profiles
# Set known services to Enforce Mode
aa-enforce /etc/apparmor.d/usr.sbin.nginx
aa-enforce /etc/apparmor.d/usr.lib.snapd.snap-confine.real
# ===== SELinux (RHEL/Rocky/AlmaLinux) =====
# Check status
sestatus
# SELinux mode (in /etc/selinux/config):
# SELINUX=enforcing ← Production: always Enforcing!
# SELINUX=permissive ← For debugging only
# SELINUX=disabled ← NEVER in production!
# View AVC denials (what was blocked?)
ausearch -m AVC -ts today
audit2why -a # Why was it blocked?
# Generate new rule from AVC log (emergency workaround)
audit2allow -a -M mymodule
semodule -i mymodule.pp
# Nginx SELinux adjustments
setsebool -P httpd_can_network_connect on
setsebool -P httpd_can_connect_ldap on
semanage port -a -t http_port_t -p tcp 8080
Fail2ban - Brute Force Protection
# Installation
apt install fail2ban -y
# /etc/fail2ban/jail.local
cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600 # Blocked for 1 hour
findtime = 600 # Within 10 minutes
maxretry = 3 # 3 failed attempts → Ban
banaction = iptables-multiport
# Email upon ban
destemail = security@firma.de
action = %(action_mwl)s # Ban + Email + Whois + Log
[sshd]
enabled = true
port = 22
filter = sshd
logpath = %(sshd_log)s
maxretry = 3
bantime = 86400 # 24h for SSH
[nginx-botsearch]
enabled = true
port = http,https
filter = nginx-botsearch
logpath = /var/log/nginx/access.log
maxretry = 2
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 3
EOF
systemctl enable --now fail2ban
fail2ban-client status # Overview
fail2ban-client status sshd # SSH jail status
Lynis - Compliance Check
# Lynis: Open-Source Security Auditing Tool
# Checks system against CIS Benchmark, NIST, etc.
# Installation
apt install lynis -y
# OR latest version:
git clone https://github.com/CISOfy/lynis
cd lynis
# System Audit
lynis audit system --quick
# Output (excerpt):
# [+] System Tools
# [V] Hostname: server01
# [V] Loaded kernel modules: 42
#
# [WARNING] Found malware scanner: none
# [SUGGESTION] Consider running a malware scanner (lynis suggestion ID: MALW-3280)
#
# Hardening Index: 68 / 100
# Tests Performed: 256
# Plugins Enabled: 0
# Score Interpretation:
# 0-49: Poor - critical measures required
# 50-69: Average - significant room for improvement
# 70-84: Good - minor improvements recommended
# 85-100: Very good - CIS Level 1/2 compliant
# Detailed Report
lynis audit system --report-file /var/log/lynis-report.dat
lynis show details<test-id>
# Regular scan via Cron:
echo "0 3 * * 0 root /usr/bin/lynis audit system --cronjob | mail -s 'Lynis Report' security@firma.de" > /etc/cron.d/lynis
UFW Firewall (Ubuntu)
# UFW - Uncomplicated Firewall (simple, but sufficient for most servers)
ufw default deny incoming # Default: block all incoming traffic
ufw default allow outgoing # Default: allow all outgoing traffic
# Allow SSH (BEFORE enabling!)
ufw allow from 10.0.0.0/8 to any port 22 # Only from internal network
# OR:
ufw allow 22/tcp
# Web server
ufw allow 80/tcp
ufw allow 443/tcp
# Specific services
ufw allow from 192.168.1.0/24 to any port 5432 # PostgreSQL only internally
ufw allow from 10.0.0.5 to any port 3306 # MySQL only from specific server
# Enable UFW
ufw enable
ufw status verbose
# Enable logging
ufw logging on
# Logs in /var/log/ufw.log
Checklist: Hardening Acceptance
Basics:
□ All packages up to date (apt upgrade / dnf update)
□ Automatic security updates enabled
□ Unnecessary services disabled
□ No obsolete protocols (Telnet, FTP, rsh)
SSH:
□ Root login disabled (PermitRootLogin no)
□ Password authentication disabled (PasswordAuthentication no)
□ SSH key authentication enabled
□ Login banner configured
□ Only allowed users (AllowUsers)
Kernel:
□ ASLR enabled (randomize_va_space = 2)
□ IP spoofing protection (rp_filter = 1)
□ ICMP redirects disabled
□ No IP forwarding (if no router)
□ Kernel pointers hidden (kptr_restrict = 2)
Logging & Monitoring:
□ auditd running, rules loaded
□ Audit log for auth, sudo, file changes
□ Fail2ban configured for SSH
□ Log rotation configured
Access Control:
□ AppArmor (Enforce) / SELinux (Enforcing) active
□ sudo only for authorized users (visudo)
□ Minimal SUID/SGID files (find / -perm -4000)
□ World-writable directories checked
Lynis Score:
□ Lynis Hardening Index ≥ 75
□ Critical findings resolved
□ Report documented and filed
```</test-id> Questions about this topic?
Our experts advise you free of charge and without obligation.
About the Author
Managing Director of AWARE7 GmbH with many years of expertise in information security, penetration testing and IT risk management. Graduate of the Internet Security master's program at Westfälische Hochschule (if(is), Prof. Norbert Pohlmann). Bestselling author with Wiley-VCH and lecturer at the ASW-Akademie. His assessments of cybersecurity and digital sovereignty have appeared in Welt am Sonntag, WDR, Deutschlandfunk and Handelsblatt, among others.
10 Publikationen
- Einsatz von elektronischer Verschlüsselung - Hemmnisse für die Wirtschaft (2018)
- Kompass IT-Verschlüsselung - Orientierungshilfen für KMU (2018)
- IT Security Day 2025 - Live Hacking: KI in der Cybersicherheit (2025)
- Live Hacking - Credential Stuffing: Finanzrisiken jenseits Ransomware (2025)
- Keynote: Live Hacking Show - Ein Blick in die Welt der Cyberkriminalität (2025)
- Analyse von Angriffsflächen bei Shared-Hosting-Anbietern (2024)
- Gänsehaut garantiert: Die schaurigsten Funde aus dem Leben eines Pentesters (2022)
- IT Security Zertifizierungen - CISSP, T.I.S.P. & Co (Live-Webinar) (2023)
- Sicherheitsforum Online-Banking - Live Hacking (2021)
- Nipster im Netz und das Ende der Kreidezeit (2017)