Linux Server Hardening: CIS Benchmark, SSH, auditd and AppArmor
Comprehensive hardening guide for Linux servers based on the CIS Benchmark Level 2. SSH configuration, kernel parameters, auditd logging, AppArmor/SELinux, Fail2ban, automatic security updates, and compliance checks using Lynis.
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
Geschäftsführender Gesellschafter der AWARE7 GmbH mit langjähriger Expertise in Informationssicherheit, Penetrationstesting und IT-Risikomanagement. Absolvent des Masterstudiengangs Internet-Sicherheit an der Westfälischen Hochschule (if(is), Prof. Norbert Pohlmann). Bestseller-Autor im Wiley-VCH Verlag und Lehrbeauftragter der ASW-Akademie. Einschätzungen zu Cybersecurity und digitaler Souveränität erschienen u.a. in Welt am Sonntag, WDR, Deutschlandfunk und Handelsblatt.
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)