Harden your Linux systems using CIS benchmarks with automated compliance scanning and continuous monitoring. Learn to implement security controls for filesystem permissions, user authentication, network policies, and maintain ongoing compliance with industry standards.
Prerequisites
- Root or sudo access
- Basic Linux command line knowledge
- Understanding of system administration concepts
What this solves
CIS (Center for Internet Security) benchmarks provide industry-standard security configurations for Linux systems. This tutorial implements comprehensive security hardening following CIS guidelines and sets up automated compliance scanning to detect configuration drift. You'll secure filesystem permissions, strengthen authentication controls, configure network security policies, and establish continuous monitoring for compliance violations.
Step-by-step implementation
Update system packages
Start with fully updated packages to ensure you have the latest security patches.
sudo apt update && sudo apt upgrade -y
sudo apt install -y wget curl gnupg software-properties-common
Install CIS-CAT assessment tool
Download and configure the CIS-CAT Lite assessment tool for automated benchmark scanning.
cd /opt
sudo mkdir -p cis-cat
cd cis-cat
Download CIS-CAT Lite (replace with latest version URL)
sudo wget https://learn.cisecurity.org/e/799323/CIS-CAT-Lite-v4-22-0-zip/1234567/1234567890
sudo unzip CIS-CAT-Lite-v4-22-0.zip
sudo chmod +x CIS-CAT-Lite/cis-cat-lite.sh
Configure filesystem security hardening
Implement secure mount options and filesystem permissions according to CIS benchmarks.
# Add secure mount options to existing filesystems
Example for /tmp with nodev,nosuid,noexec
tmpfs /tmp tmpfs defaults,rw,nosuid,nodev,noexec,relatime 0 0
For existing filesystems, add security options:
/dev/sda1 / ext4 defaults,nodev 0 1
/dev/sda2 /home ext4 defaults,nodev,nosuid 0 1
# Create separate partition for /var/log if not exists
sudo mkdir -p /var/log/audit
Set secure permissions on system directories
sudo chmod 700 /root
sudo chmod 750 /home
sudo chmod 755 /var/log
sudo chmod 750 /var/log/audit
Secure boot directory
sudo chmod 700 /boot
sudo chown root:root /boot
Configure user account and authentication controls
Strengthen password policies, account lockout, and user session controls.
# Password complexity requirements
minlen = 14
minclass = 4
maxrepeat = 2
maxclassrepeat = 2
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
difok = 8
reject_username
enforcetype
# Add account lockout policy (insert after auth required pam_unix.so)
auth required pam_faillock.so preauth silent audit deny=3 unlock_time=900 fail_interval=900
auth [default=die] pam_faillock.so authfail audit deny=3 unlock_time=900 fail_interval=900
auth sufficient pam_faillock.so authsucc audit deny=3 unlock_time=900 fail_interval=900
# Configure secure login defaults
PASS_MAX_DAYS 90
PASS_MIN_DAYS 1
PASS_WARN_AGE 7
LOGIN_RETRIES 3
LOGIN_TIMEOUT 60
SHA_CRYPT_MIN_ROUNDS 5000
SHA_CRYPT_MAX_ROUNDS 10000
# Remove unused user accounts and groups
sudo userdel games 2>/dev/null || true
sudo userdel news 2>/dev/null || true
sudo groupdel games 2>/dev/null || true
Lock system accounts
sudo usermod -L -s /sbin/nologin bin
sudo usermod -L -s /sbin/nologin daemon
sudo usermod -L -s /sbin/nologin adm
sudo usermod -L -s /sbin/nologin lp
Configure SSH hardening
Secure SSH access with CIS-compliant configuration settings.
# SSH hardening configuration
Protocol 2
Port 22
AddressFamily inet
ListenAddress 0.0.0.0
Authentication settings
PermitRootLogin no
MaxAuthTries 3
MaxSessions 4
MaxStartups 10:30:60
LoginGraceTime 60
Key-based authentication
PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
KerberosAuthentication no
GSSAPIAuthentication no
Security options
X11Forwarding no
PermitUserEnvironment no
Compression no
UseDNS no
PermitTunnel no
AllowTcpForwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
Restrict users and groups
AllowUsers user1 user2
AllowGroups ssh-users
Logging
SyslogFacility AUTHPRIV
LogLevel INFO
sudo systemctl restart sshd
sudo systemctl status sshd
Configure network and firewall security
Implement network hardening with secure kernel parameters and firewall rules.
# Network security hardening
IP forwarding
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0
Source routing
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0
ICMP redirects
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
Secure ICMP redirects
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
Ignore ICMP ping requests
net.ipv4.icmp_echo_ignore_all = 0
net.ipv4.icmp_echo_ignore_broadcasts = 1
TCP SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 5
IPv6 router advertisements
net.ipv6.conf.all.accept_ra = 0
net.ipv6.conf.default.accept_ra = 0
# Apply sysctl settings
sudo sysctl -p /etc/sysctl.d/99-cis-hardening.conf
Configure UFW firewall
sudo ufw --force reset
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw --force enable
Configure audit logging
Enable comprehensive audit logging for security events and compliance monitoring.
sudo apt install -y auditd audispd-plugins
# Audit daemon configuration
log_file = /var/log/audit/audit.log
log_format = RAW
log_group = adm
priority_boost = 4
flush = INCREMENTAL_ASYNC
freq = 50
num_logs = 5
disp_qos = lossy
dispatcher = /sbin/audispd
name_format = HOSTNAME
max_log_file = 50
max_log_file_action = ROTATE
space_left = 75
space_left_action = SYSLOG
verify_email = yes
admin_space_left = 50
admin_space_left_action = SUSPEND
disk_full_action = SUSPEND
disk_error_action = SUSPEND
use_libwrap = yes
tcp_listen_queue = 5
tcp_max_per_addr = 1
tcp_client_max_idle = 0
enable_krb5 = no
# CIS Audit Rules
Remove any existing rules
-D
Buffer size
-b 8192
Failure mode
-f 1
Audit the auditd configuration files
-w /etc/audit/ -p wa -k auditconfig
-w /etc/libaudit.conf -p wa -k auditconfig
Monitor for changes to user and group files
-w /etc/group -p wa -k identity
-w /etc/passwd -p wa -k identity
-w /etc/gshadow -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/security/opasswd -p wa -k identity
Monitor login and logout events
-w /var/log/lastlog -p wa -k logins
-w /var/run/faillock/ -p wa -k logins
Monitor network configuration changes
-a always,exit -F arch=b64 -S sethostname -S setdomainname -k system-locale
-a always,exit -F arch=b32 -S sethostname -S setdomainname -k system-locale
-w /etc/issue -p wa -k system-locale
-w /etc/issue.net -p wa -k system-locale
-w /etc/hosts -p wa -k system-locale
-w /etc/network/ -p wa -k system-locale
Monitor system administration events
-w /var/log/sudo.log -p wa -k actions
-w /etc/sudoers -p wa -k actions
-w /etc/sudoers.d/ -p wa -k actions
Monitor kernel modules
-w /sbin/insmod -p x -k modules
-w /sbin/rmmod -p x -k modules
-w /sbin/modprobe -p x -k modules
-a always,exit -F arch=b64 -S init_module -S delete_module -k modules
Monitor file permission changes
-a always,exit -F arch=b64 -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod
-a always,exit -F arch=b32 -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod
-a always,exit -F arch=b64 -S chown -S fchown -S fchownat -S lchown -F auid>=1000 -F auid!=4294967295 -k perm_mod
-a always,exit -F arch=b32 -S chown -S fchown -S fchownat -S lchown -F auid>=1000 -F auid!=4294967295 -k perm_mod
Make the configuration immutable
-e 2
sudo systemctl enable --now auditd
sudo augenrules --load
sudo systemctl restart auditd
Set up automated compliance monitoring
Create scripts for continuous CIS benchmark compliance checking and reporting.
#!/bin/bash
CIS Compliance Monitoring Script
LOG_DIR="/var/log/cis-compliance"
REPORT_FILE="$LOG_DIR/cis-report-$(date +%Y%m%d-%H%M%S).json"
CIS_CAT_DIR="/opt/cis-cat/CIS-CAT-Lite"
Create log directory
mkdir -p "$LOG_DIR"
Function to log messages
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_DIR/compliance.log"
}
log_message "Starting CIS compliance check"
Run CIS-CAT assessment
if [ -d "$CIS_CAT_DIR" ]; then
cd "$CIS_CAT_DIR"
./cis-cat-lite.sh -a -r "$REPORT_FILE" -rft json
log_message "CIS-CAT assessment completed: $REPORT_FILE"
else
log_message "ERROR: CIS-CAT directory not found at $CIS_CAT_DIR"
exit 1
fi
Parse results and check for failures
FAILED_CHECKS=$(jq '.TestResult[] | select(.result == "fail") | .ruleId' "$REPORT_FILE" | wc -l)
TOTAL_CHECKS=$(jq '.TestResult | length' "$REPORT_FILE")
SCORE=$(jq '.score' "$REPORT_FILE")
log_message "Compliance Score: $SCORE%"
log_message "Failed Checks: $FAILED_CHECKS/$TOTAL_CHECKS"
Send alert if compliance score is below threshold
THRESHOLD=80
if (( $(echo "$SCORE < $THRESHOLD" | bc -l) )); then
log_message "ALERT: Compliance score ($SCORE%) is below threshold ($THRESHOLD%)"
# Extract failed checks
echo "Failed CIS Controls:" >> "$LOG_DIR/failed-controls.txt"
jq -r '.TestResult[] | select(.result == "fail") | "\(.ruleId): \(.description)"' "$REPORT_FILE" >> "$LOG_DIR/failed-controls.txt"
# Optional: Send email notification (requires mail setup)
# mail -s "CIS Compliance Alert - Score: $SCORE%" admin@example.com < "$LOG_DIR/failed-controls.txt"
fi
Cleanup old reports (keep last 30 days)
find "$LOG_DIR" -name "cis-report-*.json" -mtime +30 -delete
log_message "CIS compliance check completed"
sudo mkdir -p /opt/cis-monitoring
sudo chmod +x /opt/cis-monitoring/cis-compliance-check.sh
sudo chown root:root /opt/cis-monitoring/cis-compliance-check.sh
Install bc for calculations
sudo apt install -y bc jq || sudo dnf install -y bc jq
Configure automated compliance reporting
Set up systemd timers for regular compliance checks and automated reporting.
[Unit]
Description=CIS Compliance Check
Wants=cis-compliance-check.timer
[Service]
Type=oneshot
User=root
ExecStart=/opt/cis-monitoring/cis-compliance-check.sh
StandardOutput=journal
StandardError=journal
[Unit]
Description=Run CIS compliance check daily
Requires=cis-compliance-check.service
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now cis-compliance-check.timer
sudo systemctl status cis-compliance-check.timer
Configure centralized logging integration
Forward security events and compliance reports to centralized logging system.
# CIS Security Event Logging
Forward audit logs to central server (optional)
. @@log.example.com:514
Local security log aggregation
auth,authpriv.* /var/log/auth.log
kern.* /var/log/kern.log
mail.* /var/log/mail.log
cron.* /var/log/cron.log
CIS compliance logs
:programname,isequal,"cis-compliance" /var/log/cis-compliance/compliance.log
& stop
sudo systemctl restart rsyslog
Set up log rotation for compliance logs
sudo tee /etc/logrotate.d/cis-compliance << EOF
/var/log/cis-compliance/*.log {
weekly
missingok
rotate 12
compress
delaycompress
notifempty
create 644 root root
postrotate
systemctl reload rsyslog > /dev/null 2>&1 || true
endscript
}
EOF
Verify your setup
Test the security hardening implementation and compliance monitoring system.
# Check SSH configuration
sudo sshd -t
sudo systemctl status sshd
Verify firewall status
sudo ufw status verbose || sudo firewall-cmd --list-all
Check audit daemon
sudo systemctl status auditd
sudo auditctl -s
Test audit rules
sudo ausearch -k identity -ts today
Verify sysctl security settings
sudo sysctl net.ipv4.ip_forward
sudo sysctl net.ipv4.conf.all.send_redirects
Check compliance monitoring timer
sudo systemctl status cis-compliance-check.timer
sudo systemctl list-timers cis-compliance-check.timer
Run manual compliance check
sudo /opt/cis-monitoring/cis-compliance-check.sh
Check compliance logs
sudo tail -f /var/log/cis-compliance/compliance.log
/var/log/cis-compliance/compliance.log for progress updates.Common issues
| Symptom | Cause | Fix |
|---|---|---|
| SSH login fails after hardening | Disabled password authentication without SSH keys | Add SSH public key to ~/.ssh/authorized_keys before disabling passwords |
| Applications can't write to logs | Restrictive file permissions | Create dedicated log directories with chmod 755 and correct ownership |
| CIS-CAT scan fails | Missing Java or incorrect permissions | Install OpenJDK and ensure cis-cat-lite.sh is executable |
| Audit logs filling disk | High audit rule verbosity | Configure log rotation and adjust audit rules granularity |
| Network services unreachable | Overly restrictive firewall rules | Add specific service rules before enabling firewall |
| System performance degraded | Excessive audit logging | Tune audit rules to focus on critical events only |
Next steps
- Configure centralized logging with rsyslog and logrotate for system monitoring and log management
- Implement two-factor authentication for SSH with Google Authenticator and TOTP
- Configure Linux audit system with auditd for security compliance and file monitoring
- Set up OSSEC HIDS for comprehensive intrusion detection and alerting
- Implement Linux container security with AppArmor and SELinux mandatory access controls
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Logging functions
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Cleanup on failure
cleanup() {
log_error "Installation failed. Check logs above for details."
exit 1
}
trap cleanup ERR
# Usage
usage() {
echo "Usage: $0"
echo "Implements Linux security hardening with CIS benchmarks"
echo "Must be run as root or with sudo"
exit 1
}
# Check if running as root
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root"
exit 1
fi
# Auto-detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update -y"
PKG_INSTALL="apt install -y"
PKG_UPGRADE="apt upgrade -y"
SERVICE_CMD="systemctl"
FIREWALL_CMD="ufw"
SSH_CONFIG="/etc/ssh/sshd_config"
PAM_SYSTEM_AUTH="/etc/pam.d/common-auth"
PAM_PASSWORD="/etc/pam.d/common-password"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
PKG_UPGRADE="dnf upgrade -y"
SERVICE_CMD="systemctl"
FIREWALL_CMD="firewall-cmd"
SSH_CONFIG="/etc/ssh/sshd_config"
PAM_SYSTEM_AUTH="/etc/pam.d/system-auth"
PAM_PASSWORD="/etc/pam.d/password-auth"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
PKG_UPGRADE="yum upgrade -y"
SERVICE_CMD="systemctl"
FIREWALL_CMD="firewall-cmd"
SSH_CONFIG="/etc/ssh/sshd_config"
PAM_SYSTEM_AUTH="/etc/pam.d/system-auth"
PAM_PASSWORD="/etc/pam.d/password-auth"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
else
log_error "Cannot detect distribution. /etc/os-release not found"
exit 1
fi
log_info "Detected distribution: $ID"
log_info "Starting CIS security hardening implementation..."
# Step 1: Update system packages
echo "[1/8] Updating system packages..."
$PKG_UPDATE
$PKG_UPGRADE
# Install required packages
if [[ "$ID" == "ubuntu" || "$ID" == "debian" ]]; then
$PKG_INSTALL wget curl gnupg software-properties-common libpam-pwquality auditd
else
$PKG_INSTALL wget curl gnupg2 yum-utils libpwquality audit
fi
# Step 2: Configure filesystem security hardening
echo "[2/8] Configuring filesystem security..."
# Secure directory permissions
chmod 700 /root
chmod 755 /var/log
mkdir -p /var/log/audit
chmod 750 /var/log/audit
chown root:root /var/log/audit
# Secure boot directory if it exists
if [ -d /boot ]; then
chmod 700 /boot
chown root:root /boot
fi
# Configure secure tmp mount
if ! grep -q "tmpfs /tmp" /etc/fstab; then
echo "tmpfs /tmp tmpfs defaults,rw,nosuid,nodev,noexec,relatime,size=1G 0 0" >> /etc/fstab
log_info "Added secure /tmp mount to /etc/fstab"
fi
# Step 3: Configure password policies
echo "[3/8] Configuring password policies..."
# Configure password quality
if [[ "$ID" == "ubuntu" || "$ID" == "debian" ]]; then
cat > /etc/security/pwquality.conf <<EOF
minlen = 14
minclass = 4
maxrepeat = 2
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
difok = 8
reject_username
enforce_for_root
EOF
else
cat > /etc/security/pwquality.conf <<EOF
minlen = 14
minclass = 4
maxrepeat = 2
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
difok = 8
reject_username
enforce_for_root
EOF
fi
# Configure login defaults
sed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS 90/' /etc/login.defs
sed -i 's/^PASS_MIN_DAYS.*/PASS_MIN_DAYS 1/' /etc/login.defs
sed -i 's/^PASS_WARN_AGE.*/PASS_WARN_AGE 7/' /etc/login.defs
# Step 4: Lock unused system accounts
echo "[4/8] Securing system accounts..."
SYSTEM_ACCOUNTS="bin daemon adm lp mail news uucp man proxy www-data backup list irc gnats nobody"
for account in $SYSTEM_ACCOUNTS; do
if id "$account" &>/dev/null; then
usermod -L -s /sbin/nologin "$account" 2>/dev/null || true
fi
done
# Step 5: Configure SSH hardening
echo "[5/8] Hardening SSH configuration..."
# Backup original SSH config
cp $SSH_CONFIG $SSH_CONFIG.backup
cat > $SSH_CONFIG <<EOF
Protocol 2
Port 22
AddressFamily inet
PermitRootLogin no
MaxAuthTries 3
MaxSessions 4
MaxStartups 10:30:60
LoginGraceTime 60
PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
KerberosAuthentication no
GSSAPIAuthentication no
X11Forwarding no
PermitUserEnvironment no
Compression no
UseDNS no
PermitTunnel no
AllowTcpForwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
SyslogFacility AUTHPRIV
LogLevel INFO
EOF
chmod 644 $SSH_CONFIG
chown root:root $SSH_CONFIG
# Step 6: Configure network security
echo "[6/8] Configuring network security..."
# Kernel parameters for network security
cat > /etc/sysctl.d/99-cis-hardening.conf <<EOF
# IP Forwarding
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0
# Source routing
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
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
# ICMP
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
# Source route validation
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Log suspicious packets
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
# TCP SYN cookies
net.ipv4.tcp_syncookies = 1
EOF
sysctl -p /etc/sysctl.d/99-cis-hardening.conf
# Step 7: Configure firewall
echo "[7/8] Configuring firewall..."
if [[ "$ID" == "ubuntu" || "$ID" == "debian" ]]; then
# Enable UFW
ufw --force enable
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
else
# Enable firewalld
systemctl enable firewalld
systemctl start firewalld
firewall-cmd --set-default-zone=drop
firewall-cmd --permanent --zone=drop --add-service=ssh
firewall-cmd --reload
fi
# Step 8: Enable and configure auditing
echo "[8/8] Configuring system auditing..."
# Configure auditd
if [[ "$ID" == "ubuntu" || "$ID" == "debian" ]]; then
systemctl enable auditd
else
systemctl enable auditd
fi
# Basic audit rules
cat > /etc/audit/rules.d/cis-hardening.rules <<EOF
# Delete all rules
-D
# Buffer size
-b 8192
# Failure mode
-f 1
# Audit file modifications
-w /etc/passwd -p wa -k passwd_changes
-w /etc/group -p wa -k group_changes
-w /etc/shadow -p wa -k shadow_changes
-w /etc/sudoers -p wa -k sudoers_changes
-w /var/log/faillog -p wa -k login_failures
-w /var/log/lastlog -p wa -k last_logon
# SSH configuration
-w /etc/ssh/sshd_config -p wa -k ssh_config
# Make rules immutable
-e 2
EOF
# Restart services
systemctl restart sshd
systemctl restart auditd 2>/dev/null || service auditd restart
# Final verification
echo ""
log_info "CIS security hardening completed successfully!"
log_info "Services status:"
systemctl is-active sshd && echo "SSH: Active" || echo "SSH: Inactive"
if [[ "$ID" == "ubuntu" || "$ID" == "debian" ]]; then
ufw status | grep -q "Status: active" && echo "Firewall: Active" || echo "Firewall: Inactive"
else
systemctl is-active firewalld && echo "Firewall: Active" || echo "Firewall: Inactive"
fi
log_warn "IMPORTANT: SSH password authentication has been disabled."
log_warn "Ensure SSH key authentication is configured before disconnecting."
log_warn "A reboot is recommended to apply all kernel parameter changes."
Review the script before running. Execute with: bash install.sh