thelinuxvault guide

Essential Linux Security Hardening Scripts for Admins

Linux is renowned for its robust security, but even the most secure systems require proactive hardening to mitigate emerging threats. As a system administrator, manually applying security best practices across multiple servers is time-consuming, error-prone, and unsustainable. **Security hardening scripts** automate these critical tasks, ensuring consistency, reducing human error, and freeing up time for higher-priority work. This blog will guide you through essential Linux security hardening scripts, covering user account security, SSH configuration, firewall setup, package management, file permissions, audit logging, and malware scanning. Each script is designed to be adaptable, well-commented, and validated for common Linux distributions (Debian/Ubuntu, RHEL/CentOS). By the end, you’ll have a toolkit to strengthen your Linux infrastructure and maintain compliance with security standards like the CIS Benchmarks.

Table of Contents

  1. Prerequisites
  2. Core Hardening Scripts
  3. Testing & Validation
  4. Best Practices for Hardening Scripts
  5. References

Prerequisites

Before running these scripts:

  • Run as Root: Most hardening tasks require root privileges (sudo -i or su -).
  • Test in Staging First: Always validate scripts in a non-production environment to avoid breaking critical services.
  • Understand the Scripts: Review and modify scripts to match your infrastructure (e.g., allowed SSH ports, trusted IPs).
  • Backup Configs: Save copies of critical files (e.g., /etc/ssh/sshd_config, /etc/ufw/ufw.conf) before overwriting them.

Core Hardening Scripts

1. User Account Security Hardening

Weak user accounts are a top attack vector. This script secures user credentials, enforces password policies, and locks unused accounts.

Script: harden_user_accounts.sh

#!/bin/bash
# User Account Security Hardening Script
# Purpose: Lock unused accounts, enforce password policies, and remove empty passwords.

# --------------------------
# Step 1: Lock empty password accounts
# --------------------------
echo "Locking accounts with empty passwords..."
awk -F: '($2 == "") {print $1}' /etc/shadow | while read -r user; do
  if [ "$user" != "nobody" ]; then  # Skip system accounts like 'nobody'
    passwd -l "$user"  # Lock the account
    echo "Locked empty password account: $user"
  fi
done

# --------------------------
# Step 2: Enforce password policies (age, complexity)
# --------------------------
echo "Enforcing password policies..."
# Set max password age to 90 days (prevents permanent passwords)
chage -M 90 root  # Apply to root
for user in $(awk -F: '$3 >= 1000 {print $1}' /etc/passwd); do  # Apply to non-system users (UID ≥ 1000)
  chage -M 90 "$user"
done

# Set min password age to 7 days (prevents frequent resets)
chage -m 7 root
for user in $(awk -F: '$3 >= 1000 {print $1}' /etc/passwd); do
  chage -m 7 "$user"
done

# Set password warning period to 14 days
chage -W 14 root
for user in $(awk -F: '$3 >= 1000 {print $1}' /etc/passwd); do
  chage -W 14 "$user"
done

# --------------------------
# Step 3: Lock accounts inactive for 90+ days
# --------------------------
echo "Locking accounts inactive for 90+ days..."
for user in $(awk -F: '$3 >= 1000 {print $1}' /etc/passwd); do
  last_login=$(lastlog -u "$user" | awk 'NR==2 {print $4 $5 $6}')
  if [ "$last_login" == "**Neverloggedin**" ] || [ $(($(date +%s) - $(date -d "$last_login" +%s)))/86400 -ge 90 ]; then
    passwd -l "$user"
    echo "Locked inactive account: $user"
  fi
done

# --------------------------
# Step 4: Remove non-root accounts with UID 0 (privilege escalation risk)
# --------------------------
echo "Checking for non-root UID 0 accounts..."
non_root_uid0=$(grep -v '^root:' /etc/passwd | awk -F: '$3 == 0 {print $1}')
if [ -n "$non_root_uid0" ]; then
  echo "Warning: Non-root accounts with UID 0 found: $non_root_uid0"
  echo "Consider deleting or modifying these accounts!"
fi

echo "User account hardening complete."

Key Features:

  • Locks accounts with empty passwords (common in misconfigured systems).
  • Enforces password expiration (90 days max, 7 days min, 14-day warning).
  • Locks inactive accounts (≥90 days of inactivity).
  • Detects non-root accounts with UID 0 (a critical privilege escalation risk).

Usage:

chmod +x harden_user_accounts.sh
./harden_user_accounts.sh

2. SSH Configuration Hardening

SSH is a primary entry point for attackers. This script disables weak ciphers, enforces key-based auth, and limits access.

Script: harden_ssh.sh

#!/bin/bash
# SSH Hardening Script
# Purpose: Secure SSH daemon configuration (sshd_config).

# Backup original sshd_config
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak-"$(date +%F)"

# --------------------------
# Step 1: Basic SSH hardening
# --------------------------
echo "Configuring /etc/ssh/sshd_config..."
sed -i.bak \
  -e 's/^#PermitRootLogin.*/PermitRootLogin no/' \  # Disable root SSH login
  -e 's/^PermitEmptyPasswords.*/PermitEmptyPasswords no/' \  # No empty passwords
  -e 's/^#PasswordAuthentication.*/PasswordAuthentication no/' \  # Disable password auth (key-based only)
  -e 's/^#PubkeyAuthentication.*/PubkeyAuthentication yes/' \  # Enable key-based auth
  -e 's/^#AuthorizedKeysFile.*/AuthorizedKeysFile .ssh\/authorized_keys/' \  # Define key file path
  -e 's/^#MaxAuthTries.*/MaxAuthTries 3/' \  # Limit failed login attempts (3 tries)
  -e 's/^#ClientAliveInterval.*/ClientAliveInterval 300/' \  # Idle timeout (5 mins)
  -e 's/^#ClientAliveCountMax.*/ClientAliveCountMax 0/' \  # Disconnect after 5 mins idle
  /etc/ssh/sshd_config

# --------------------------
# Step 2: Restrict SSH ciphers, MACs, and KexAlgorithms
# --------------------------
echo "Hardening SSH ciphers and algorithms..."
cat << EOF >> /etc/ssh/sshd_config

# Hardened security settings
Ciphers aes256-ctr,aes192-ctr,aes128-ctr  # Only use AES-CTR (no CBC)
MACs hmac-sha2-512,hmac-sha2-256  # SHA-2 only (no SHA-1)
KexAlgorithms [email protected],diffie-hellman-group-exchange-sha256  # Strong key exchange
EOF

# --------------------------
# Step 3: Restrict SSH access (optional: limit by IP/user)
# Uncomment and modify the lines below to allow only specific IPs/users.
# --------------------------
# echo "AllowUsers [email protected]/24" >> /etc/ssh/sshd_config  # Allow only 'admin' from 192.168.1.0/24
# echo "AllowGroups ssh-users" >> /etc/ssh/sshd_config  # Allow only members of 'ssh-users' group

# --------------------------
# Validate and restart SSH
# --------------------------
echo "Validating SSH configuration..."
if sshd -t; then
  echo "SSH configuration is valid. Restarting sshd..."
  systemctl restart sshd  # Use 'service ssh restart' on older systems
  echo "SSH hardening complete."
else
  echo "Error: Invalid SSH configuration! Restoring backup..."
  cp /etc/ssh/sshd_config.bak-"$(date +%F)" /etc/ssh/sshd_config
fi

Key Features:

  • Disables root SSH login and password authentication (key-based only).
  • Limits failed login attempts (3 tries) and enforces idle timeout (5 mins).
  • Uses strong ciphers (AES-CTR), MACs (SHA-2), and key exchange algorithms.
  • Includes optional rules to restrict access by user/IP (e.g., AllowUsers).

Usage:

chmod +x harden_ssh.sh
./harden_ssh.sh

Note: After running, test SSH access from a secondary terminal to avoid locking yourself out!

3. Firewall Setup with UFW/iptables

A firewall blocks unauthorized network traffic. This script uses UFW (Uncomplicated Firewall) for simplicity (replace with iptables for advanced setups).

Script: harden_firewall.sh

#!/bin/bash
# Firewall Hardening Script (UFW)
# Purpose: Block all inbound traffic except essential services (SSH, HTTP/HTTPS).

# --------------------------
# Step 1: Install UFW (if missing)
# --------------------------
if ! command -v ufw &> /dev/null; then
  echo "Installing UFW..."
  if [ -f /etc/debian_version ]; then
    apt-get update && apt-get install -y ufw  # Debian/Ubuntu
  elif [ -f /etc/redhat-release ]; then
    yum install -y ufw  # RHEL/CentOS (may require EPEL repo)
  fi
fi

# --------------------------
# Step 2: Set default policies (deny inbound, allow outbound)
# --------------------------
echo "Setting default firewall policies..."
ufw default deny incoming
ufw default allow outgoing

# --------------------------
# Step 3: Allow essential inbound services
# Modify these rules to match your needs (e.g., add 443 for HTTPS).
# --------------------------
echo "Allowing critical inbound ports..."
ufw allow 22/tcp  # SSH (replace with your SSH port if non-default)
# ufw allow 80/tcp  # HTTP (uncomment if needed)
# ufw allow 443/tcp  # HTTPS (uncomment if needed)
# ufw allow 5901/tcp  # VNC (only if absolutely necessary!)

# --------------------------
# Step 4: Enable UFW and verify status
# --------------------------
echo "Enabling UFW..."
ufw --force enable  # --force bypasses confirmation prompt

echo "Firewall status:"
ufw status verbose

Key Features:

  • Blocks all inbound traffic by default (deny incoming, allow outgoing).
  • Allows only critical ports (SSH, optionally HTTP/HTTPS).
  • Installs UFW automatically (Debian/Ubuntu/RHEL/CentOS).

Usage:

chmod +x harden_firewall.sh
./harden_firewall.sh

For iptables Users: Replace UFW with iptables rules (e.g., iptables -A INPUT -p tcp --dport 22 -j ACCEPT).

4. Automated Package Updates & Vulnerability Patching

Outdated packages are a leading cause of breaches. This script automates updates and removes unneeded software.

Script: harden_packages.sh

#!/bin/bash
# Package Hardening Script
# Purpose: Update packages, remove unneeded software, and enable auto-updates.

# --------------------------
# Step 1: Update package lists and upgrade
# --------------------------
echo "Updating packages..."
if [ -f /etc/debian_version ]; then
  apt-get update && apt-get upgrade -y  # Debian/Ubuntu
elif [ -f /etc/redhat-release ]; then
  yum update -y  # RHEL/CentOS (use 'dnf' for newer versions)
fi

# --------------------------
# Step 2: Remove unneeded packages (reduce attack surface)
# --------------------------
echo "Removing unneeded packages..."
if [ -f /etc/debian_version ]; then
  apt-get autoremove -y  # Remove unused dependencies
  apt-get purge -y telnet rsh ftp  # Remove insecure tools
elif [ -f /etc/redhat-release ]; then
  yum autoremove -y
  yum remove -y telnet rsh ftp
fi

# --------------------------
# Step 3: Enable automatic updates
# --------------------------
echo "Enabling automatic updates..."
if [ -f /etc/debian_version ]; then
  apt-get install -y unattended-upgrades  # Debian/Ubuntu
  dpkg-reconfigure -plow unattended-upgrades  # Auto-accept prompts
elif [ -f /etc/redhat-release ]; then
  yum install -y dnf-automatic  # RHEL/CentOS 8+
  systemctl enable --now dnf-automatic.timer
fi

echo "Package hardening complete."

Key Features:

  • Updates all packages to the latest security patches.
  • Removes insecure tools (telnet, rsh, ftp) and unused dependencies.
  • Enables automatic updates (unattended-upgrades for Debian, dnf-automatic for RHEL).

Usage:

chmod +x harden_packages.sh
./harden_packages.sh

5. File & Directory Permission Hardening

Misconfigured file permissions allow unauthorized access to sensitive data. This script secures critical system files and directories.

Script: harden_permissions.sh

#!/bin/bash
# File Permission Hardening Script
# Purpose: Secure critical system files and directories.

# --------------------------
# Step 1: Secure /etc/passwd, /etc/shadow, and /etc/sudoers
# --------------------------
echo "Securing user/group files..."
chmod 644 /etc/passwd  # Readable by all, writable only by root
chmod 000 /etc/shadow  # No access except root (shadow-utils manages permissions)
chmod 440 /etc/sudoers  # Readable by root and sudo group, no write for others

# --------------------------
# Step 2: Set sticky bit on /tmp (prevent unauthorized file deletion)
# --------------------------
echo "Securing /tmp..."
chmod 1777 /tmp  # Sticky bit: users can only delete their own files

# --------------------------
# Step 3: Find and fix world-writable files (excluding /tmp)
# --------------------------
echo "Checking for world-writable files (excluding /tmp)..."
world_writable=$(find / -path /tmp -prune -o -perm -0002 -type f -ls 2>/dev/null)
if [ -n "$world_writable" ]; then
  echo "Warning: World-writable files found:"
  echo "$world_writable"
  echo "Consider fixing with: chmod o-w <file>"
fi

# --------------------------
# Step 4: Find SUID/SGID binaries (potential privilege escalation)
# --------------------------
echo "Checking for SUID/SGID binaries..."
suid_sgid=$(find / -perm -4000 -o -perm -2000 -type f -ls 2>/dev/null)
if [ -n "$suid_sgid" ]; then
  echo "SUID/SGID binaries found (review for necessity):"
  echo "$suid_sgid"
fi

echo "File permission hardening complete."

Key Features:

  • Secures /etc/passwd (user info), /etc/shadow (password hashes), and /etc/sudoers (sudo privileges).
  • Sets the sticky bit on /tmp (prevents users from deleting others’ files).
  • Detects world-writable files (a common vulnerability) and SUID/SGID binaries (potential escalation vectors).

Usage:

chmod +x harden_permissions.sh
./harden_permissions.sh

6. Auditd Configuration for Security Monitoring

auditd logs system events (e.g., file modifications, privilege escalation) for post-incident analysis. This script sets up basic audit rules.

Script: harden_auditd.sh

#!/bin/bash
# Auditd Hardening Script
# Purpose: Configure auditd to log critical security events.

# --------------------------
# Step 1: Install auditd (if missing)
# --------------------------
if ! command -v auditd &> /dev/null; then
  echo "Installing auditd..."
  if [ -f /etc/debian_version ]; then
    apt-get install -y auditd audispd-plugins
  elif [ -f /etc/redhat-release ]; then
    yum install -y audit
  fi
  systemctl enable --now auditd
fi

# --------------------------
# Step 2: Add critical audit rules
# --------------------------
AUDIT_RULES="/etc/audit/rules.d/sec_hardening.rules"

echo "Writing audit rules to $AUDIT_RULES..."
cat > "$AUDIT_RULES" << EOF
# Log modifications to user/group files
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity

# Log sudo usage
-w /usr/bin/sudo -p x -k sudo

# Log SSH access
-w /var/log/auth.log -p wa -k sshd  # Debian/Ubuntu
-w /var/log/secure -p wa -k sshd     # RHEL/CentOS

# Log firewall changes
-w /etc/ufw/ -p wa -k firewall
-w /etc/iptables/ -p wa -k firewall

# Log kernel module loading/unloading
-w /sbin/insmod -p x -k modules
-w /sbin/rmmod -p x -k modules
-w /sbin/modprobe -p x -k modules
EOF

# --------------------------
# Reload audit rules
# --------------------------
echo "Reloading audit rules..."
augenrules --load  # Load new rules
systemctl restart auditd

echo "Auditd hardening complete. Logs are in /var/log/audit/audit.log."

Key Features:

  • Logs modifications to user/group files (/etc/passwd, /etc/shadow).
  • Tracks sudo usage, SSH access, firewall changes, and kernel module modifications.
  • Stores logs in /var/log/audit/audit.log (use ausearch -k identity to query).

Usage:

chmod +x harden_auditd.sh
./harden_auditd.sh

Bonus: Malware Scanning with ClamAV

ClamAV is an open-source antivirus engine. This script installs ClamAV and runs a weekly scan.

Script: harden_malware_scan.sh

#!/bin/bash
# Malware Scanning Script (ClamAV)
# Purpose: Install ClamAV and schedule weekly scans.

# --------------------------
# Step 1: Install ClamAV
# --------------------------
echo "Installing ClamAV..."
if [ -f /etc/debian_version ]; then
  apt-get install -y clamav clamav-daemon
elif [ -f /etc/redhat-release ]; then
  yum install -y clamav clamav-update epel-release
fi

# Update virus definitions
echo "Updating virus definitions..."
freshclam  # May take a few minutes

# --------------------------
# Step 2: Schedule weekly scans with cron
# --------------------------
echo "Scheduling weekly scan (Sunday 2 AM)..."
(crontab -l 2>/dev/null; echo "0 2 * * 0 clamscan -r / --exclude-dir=/sys --exclude-dir=/proc --log=/var/log/clamav/weekly_scan.log") | crontab -

echo "Malware scanning setup complete. Weekly scans log to /var/log/clamav/weekly_scan.log."

Usage:

chmod +x harden_malware_scan.sh
./harden_malware_scan.sh

Testing & Validation

After running the scripts, validate their effectiveness with these checks:

Hardening AreaValidation Command
User Accountsawk -F: '($2 == "") {print $1}' /etc/shadow (no output = no empty passwords)
SSHsshd -t (validates config); grep PermitRootLogin /etc/ssh/sshd_config (should be no)
Firewallufw status (verify allowed ports); netstat -tulpn (check for unexpected listeners)
Packagesapt list --upgradable (no output = fully updated); dpkg -l telnet (should be “not installed”)
File Permissionsls -l /etc/shadow (permissions: -rw-------); find /tmp -perm -0002 -type f (no world-writable files)
Auditdausearch -k identity (logs user file modifications)

Best Practices for Hardening Scripts

  • Version Control: Store scripts in Git (e.g., GitHub, GitLab) to track changes and roll back if needed.
  • Logging: Add logging to scripts (e.g., >> /var/log/security_hardening.log 2>&1) for auditing.
  • Regular Execution: Run scripts via cron (e.g., monthly) to catch new vulnerabilities.
  • Least Privilege: Run scripts with minimal permissions (e.g., use sudo for specific commands instead of full root).

References

By integrating these scripts into your workflow, you’ll significantly reduce your Linux infrastructure’s attack surface. Remember: security is a journey, not a destination—regularly update scripts and stay informed about new threats!