Fedora-Server-Hardening-script.sh
#!/usr/bin/env bash
# Fedora 43 System Hardening Script
# Run as root: sudo bash fedora43_hardening.sh
set -euo pipefail
IFS=$'\n\t'
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
section() { echo -e "\n${CYAN}========== $* ==========${NC}"; }
[[ $EUID -ne 0 ]] && { echo -e "${RED}Run as root: sudo bash $0${NC}"; exit 1; }
LOG="/var/log/fedora_hardening_$(date +%Y%m%d_%H%M%S).log"
exec > >(tee -a "$LOG") 2>&1
info "Logging to $LOG"
section "System Update"
dnf upgrade -y --refresh
ok "System updated"
section "Installing Security Tools"
dnf install -y \
aide \
audit \
auditd \
fail2ban \
firewalld \
libpwquality \
policycoreutils-python-utils \
rsyslog \
selinux-policy-targeted \
setroubleshoot-server \
usbguard \
rkhunter \
chrony
ok "Security tools installed"
section "SELinux"
if sestatus | grep -q "disabled"; then
sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
warn "SELinux was disabled — set to enforcing. Reboot required."
elif sestatus | grep -q "permissive"; then
setenforce 1
sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
ok "SELinux set to enforcing"
else
ok "SELinux already enforcing"
fi
section "Firewall"
systemctl enable --now firewalld
firewall-cmd --set-default-zone=public --permanent
firewall-cmd --zone=public --set-target=DROP --permanent
firewall-cmd --zone=public --add-service=ssh --permanent
firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" log prefix="FIREWALL-DROP " level="warning" limit value="5/m" drop' --permanent
firewall-cmd --reload
ok "Firewall configured"
section "SSH Hardening"
SSHD=/etc/ssh/sshd_config
cp "$SSHD" "${SSHD}.bak.$(date +%Y%m%d)"
set_ssh() {
local key=$1 val=$2
if grep -qE "^#?${key}" "$SSHD"; then
sed -i "s/^#\?${key}.*/${key} ${val}/" "$SSHD"
else
echo "${key} ${val}" >> "$SSHD"
fi
}
set_ssh PermitRootLogin no
set_ssh PasswordAuthentication no
set_ssh PermitEmptyPasswords no
set_ssh PubkeyAuthentication yes
set_ssh AuthorizedKeysFile ".ssh/authorized_keys"
set_ssh X11Forwarding no
set_ssh AllowTcpForwarding no
set_ssh UsePAM yes
set_ssh MaxAuthTries 3
set_ssh MaxSessions 5
set_ssh LoginGraceTime 30
set_ssh ClientAliveInterval 300
set_ssh ClientAliveCountMax 2
set_ssh IgnoreRhosts yes
set_ssh HostbasedAuthentication no
set_ssh Banner /etc/issue.net
set_ssh LogLevel VERBOSE
cat > /etc/issue.net << 'EOF'
***************************************************************************
AUTHORIZED ACCESS ONLY
Unauthorized access to this system is prohibited and will be prosecuted.
***************************************************************************
EOF
sshd -t && systemctl restart sshd
ok "SSH hardened"
section "Password Policy"
cat > /etc/security/pwquality.conf << 'EOF'
minlen = 14
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
minclass = 4
maxrepeat = 3
maxsequence = 3
gecoscheck = 1
dictcheck = 1
EOF
cat > /etc/security/faillock.conf << 'EOF'
deny = 5
unlock_time = 900
fail_interval = 900
silent
audit
EOF
sed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS 90/' /etc/login.defs
sed -i 's/^PASS_MIN_DAYS.*/PASS_MIN_DAYS 7/' /etc/login.defs
sed -i 's/^PASS_WARN_AGE.*/PASS_WARN_AGE 14/' /etc/login.defs
ok "Password policy configured"
section "Kernel Hardening"
cat > /etc/sysctl.d/99-hardening.conf << 'EOF'
net.ipv4.ip_forward = 0
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.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_rfc1337 = 1
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_ra = 0
net.ipv6.conf.default.accept_ra = 0
kernel.randomize_va_space = 2
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
kernel.perf_event_paranoid = 3
kernel.yama.ptrace_scope = 1
kernel.sysrq = 0
kernel.unprivileged_bpf_disabled = 1
kernel.core_uses_pid = 1
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
fs.protected_fifos = 2
fs.protected_regular = 2
fs.suid_dumpable = 0
EOF
sysctl --system
ok "Kernel parameters applied"
section "Kernel Module Blacklist"
cat > /etc/modprobe.d/hardening-blacklist.conf << 'EOF'
install dccp /bin/false
install sctp /bin/false
install rds /bin/false
install tipc /bin/false
install n-hdlc /bin/false
install ax25 /bin/false
install netrom /bin/false
install x25 /bin/false
install rose /bin/false
install decnet /bin/false
install econet /bin/false
install af_802154 /bin/false
install ipx /bin/false
install appletalk /bin/false
install psnap /bin/false
install p8022 /bin/false
install p8023 /bin/false
install cramfs /bin/false
install freevxfs /bin/false
install jffs2 /bin/false
install hfs /bin/false
install hfsplus /bin/false
install udf /bin/false
EOF
ok "Kernel module blacklist written"
section "Audit Rules"
cat > /etc/audit/rules.d/99-hardening.rules << 'EOF'
-D
-b 8192
-f 1
-a always,exit -F arch=b64 -S adjtimex -S settimeofday -k time-change
-a always,exit -F arch=b32 -S adjtimex -S settimeofday -S stime -k time-change
-w /etc/localtime -p wa -k time-change
-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
-a always,exit -F arch=b64 -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/sysconfig/network -p wa -k system-locale
-w /var/log/lastlog -p wa -k logins
-w /var/run/faillock -p wa -k logins
-w /var/run/utmp -p wa -k session
-w /var/log/wtmp -p wa -k session
-w /var/log/btmp -p wa -k session
-w /etc/sudoers -p wa -k scope
-w /etc/sudoers.d/ -p wa -k scope
-a always,exit -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/sbin/useradd -F perm=x -F auid>=1000 -k privileged
-a always,exit -F path=/usr/sbin/userdel -F perm=x -F auid>=1000 -k privileged
-a always,exit -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -k privileged
-a always,exit -F arch=b64 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -k access
-a always,exit -F arch=b64 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -k access
-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
-e 2
EOF
systemctl restart auditd
ok "Auditd configured"
section "Fail2ban"
cat > /etc/fail2ban/jail.d/hardening.conf << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
backend = systemd
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
maxretry = 3
bantime = 86400
EOF
systemctl enable --now fail2ban
ok "Fail2ban enabled"
section "USBGuard"
if ! systemctl is-active --quiet usbguard; then
usbguard generate-policy > /etc/usbguard/rules.conf 2>/dev/null || true
systemctl enable --now usbguard
ok "USBGuard enabled"
else
ok "USBGuard already running"
fi
section "Time Sync"
systemctl enable --now chronyd
ok "chrony enabled"
section "AIDE"
if [[ ! -f /var/lib/aide/aide.db.gz ]]; then
info "Building AIDE database..."
aide --init && mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
ok "AIDE database created"
else
ok "AIDE database already exists"
fi
cat > /etc/cron.weekly/aide-check << 'EOF'
#!/bin/bash
/usr/sbin/aide --check 2>&1 | mail -s "AIDE Report $(hostname)" root
EOF
chmod +x /etc/cron.weekly/aide-check
ok "AIDE weekly cron installed"
section "File Permissions"
chmod 600 /etc/shadow
chmod 600 /etc/gshadow
chmod 644 /etc/passwd
chmod 644 /etc/group
chmod 700 /root
chmod 600 /boot/grub2/grub.cfg 2>/dev/null || true
ok "File permissions set"
section "Disable Unnecessary Services"
SERVICES=(avahi-daemon bluetooth cups rpcbind nfs-server vsftpd telnet rsh ypbind)
for svc in "${SERVICES[@]}"; do
systemctl disable --now "$svc" 2>/dev/null && info "Disabled: $svc" || true
done
ok "Unnecessary services disabled"
section "umask"
for f in /etc/profile /etc/bashrc; do
grep -q "umask 027" "$f" || echo "umask 027" >> "$f"
done
ok "umask 027 set"
section "Core Dumps"
grep -q "* hard core 0" /etc/security/limits.conf || {
echo "* hard core 0" >> /etc/security/limits.conf
echo "* soft core 0" >> /etc/security/limits.conf
}
echo "fs.suid_dumpable = 0" > /etc/sysctl.d/99-nodumps.conf
sysctl -p /etc/sysctl.d/99-nodumps.conf
ok "Core dumps disabled"
section "rkhunter"
rkhunter --update --nocolors 2>/dev/null || true
rkhunter --propupd --nocolors 2>/dev/null || true
cat > /etc/cron.weekly/rkhunter << 'EOF'
#!/bin/bash
/usr/bin/rkhunter --check --nocolors --skip-keypress 2>&1 | mail -s "rkhunter Report $(hostname)" root
EOF
chmod +x /etc/cron.weekly/rkhunter
ok "rkhunter configured"
section "Hardening Complete"
echo -e "${GREEN}"
cat << 'EOF'
✔ System updated
✔ Security tools installed
✔ SELinux enforcing
✔ Firewall configured
✔ SSH hardened
✔ Password / lockout policy applied
✔ Kernel sysctl hardened
✔ Kernel modules blacklisted
✔ Auditd rules applied
✔ Fail2ban enabled
✔ USBGuard enabled
✔ Time sync enabled
✔ AIDE initialised
✔ File permissions set
✔ Unnecessary services disabled
✔ umask 027 applied
✔ Core dumps disabled
✔ rkhunter configured
!! Reboot to activate all kernel/SELinux changes !!
EOF
echo -e "${NC}"
info "Full log: $LOG"