Initial Security Configuration on CentOS/Rocky Linux

Securing your CentOS or Rocky Linux server immediately after deployment is essential to protect against unauthorized access, cyber threats, and security breaches. This comprehensive guide provides step-by-step instructions for hardening RHEL-based distributions, implementing security best practices specific to CentOS, Rocky Linux, AlmaLinux, and Red Hat Enterprise Linux systems.

Table of Contents

Prerequisites

Before beginning the security configuration process, ensure you have:

  • Fresh installation of CentOS Stream 8/9, Rocky Linux 8/9, AlmaLinux 8/9, or RHEL 8/9
  • Root access or user with sudo privileges
  • SSH access to the server
  • Server IP address or hostname
  • Basic understanding of Linux command line and RHEL-specific tools
  • Access to server console (via hosting provider panel) in case of lockout
  • At least 1GB RAM and 10GB disk space available

Understanding RHEL-Based Security

Red Hat Enterprise Linux and its derivatives (CentOS, Rocky Linux, AlmaLinux) include enterprise-grade security features that distinguish them from Debian-based distributions:

Key RHEL security features:

  • SELinux (Security-Enhanced Linux): Mandatory Access Control (MAC) system enabled by default
  • firewalld: Dynamic firewall manager with zones and rich rules
  • dnf/yum: Package manager with built-in security features and modularity
  • FIPS 140-2 compliance: Federal security standards certification
  • Audit subsystem: Comprehensive system auditing enabled by default

Security philosophy differences:

  • RHEL-based systems prioritize stability and enterprise security over bleeding-edge features
  • SELinux provides additional security layer beyond traditional DAC (Discretionary Access Control)
  • Package updates are thoroughly tested before release, reducing breaking changes
  • Extended support lifecycle (10+ years for RHEL/Rocky/Alma)

Step 1: Update System Packages

Update all system packages to patch known vulnerabilities and security issues.

# Update package repository metadata
sudo dnf check-update

# Upgrade all installed packages
sudo dnf upgrade -y

# Or use yum on older systems
sudo yum update -y

# Clean package cache
sudo dnf clean all

Why this matters: Red Hat-based distributions receive regular security updates through their update streams. The dnf upgrade command updates all packages including kernel, which may require a reboot to take effect.

Check for available security updates specifically:

# List available security updates
sudo dnf updateinfo list security

# Install only security updates
sudo dnf upgrade --security -y

# View detailed security update information
sudo dnf updateinfo info

Verify kernel version:

# Check current running kernel
uname -r

# List installed kernels
rpm -qa kernel

# View available kernel updates
dnf list kernel

If a new kernel was installed, reboot the server:

# Reboot system to load new kernel
sudo reboot

After reboot, reconnect and verify:

# Confirm new kernel is running
uname -r

# Check system uptime
uptime

Step 2: Create a Non-Root Administrative User

Running operations as root violates the principle of least privilege. Create a dedicated administrative user with sudo access.

# Create new user (replace 'adminuser' with your preferred username)
sudo useradd -m -s /bin/bash adminuser

# Set strong password for the new user
sudo passwd adminuser

Enter a strong password containing uppercase, lowercase, numbers, and special characters.

# Add user to wheel group for sudo privileges (RHEL-specific)
sudo usermod -aG wheel adminuser

# Verify user group membership
groups adminuser
id adminuser

RHEL-specific note: Unlike Debian-based systems that use the sudo group, RHEL-based distributions use the wheel group for administrative privileges. This is configured in /etc/sudoers.

Verify wheel group sudo configuration:

# Check sudoers configuration for wheel group
sudo grep -E "^%wheel" /etc/sudoers

You should see:

%wheel  ALL=(ALL)       ALL

Test sudo access:

# Switch to new user
su - adminuser

# Test sudo privileges
sudo whoami
# Should output: root

# View sudo permissions
sudo -l

Optional: Configure passwordless sudo for convenience (use cautiously):

# Edit sudoers file using visudo
sudo visudo

# Add this line for passwordless sudo (security trade-off)
adminuser ALL=(ALL) NOPASSWD: ALL

Step 3: Configure SSH Key Authentication

SSH key authentication is significantly more secure than password authentication and essential for proper server security.

Generate SSH Key Pair (on your local machine)

If you don't have an SSH key pair:

# Generate Ed25519 key (recommended for modern systems)
ssh-keygen -t ed25519 -C "[email protected]"

# Or generate RSA 4096-bit key (for maximum compatibility)
ssh-keygen -t rsa -b 4096 -C "[email protected]"

Accept the default location and set a strong passphrase for the key.

Copy Public Key to Server

# Copy public key to your new administrative user
ssh-copy-id adminuser@your_server_ip

# With custom SSH port
ssh-copy-id -p 2222 adminuser@your_server_ip

# Manual method if ssh-copy-id unavailable
cat ~/.ssh/id_ed25519.pub | ssh adminuser@your_server_ip "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

Verify Key Authentication Works

# Test connection with new user using key authentication
ssh adminuser@your_server_ip

# Should connect without entering the server password
# (only your SSH key passphrase if you set one)

Critical: Keep your current SSH session open while testing to avoid lockout.

Set Proper Permissions

Ensure correct ownership and permissions on SSH directories:

# On the server, as your administrative user
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

# Verify ownership
ls -la ~/.ssh/

# Should show:
# drwx------ (700) for .ssh directory
# -rw------- (600) for authorized_keys file

# Ensure correct ownership
sudo chown -R adminuser:adminuser ~/.ssh

Configure SELinux Context for SSH Keys

SELinux requires proper security contexts for SSH directories:

# Restore default SELinux contexts
restorecon -R -v ~/.ssh

# Verify SELinux context
ls -Z ~/.ssh/
# Should show: ssh_home_t context

Step 4: Harden SSH Configuration

Modify SSH daemon configuration to implement security best practices for RHEL-based systems.

# Backup original SSH configuration
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup.$(date +%Y%m%d)

# Edit SSH configuration
sudo vi /etc/ssh/sshd_config
# Or use nano: sudo nano /etc/ssh/sshd_config

Apply these security hardening settings:

# Disable root login via SSH
PermitRootLogin no

# Disable password authentication (only after key auth is working!)
PasswordAuthentication no

# Disable empty passwords
PermitEmptyPasswords no

# Disable challenge-response authentication
ChallengeResponseAuthentication no

# Disable keyboard-interactive authentication
KbdInteractiveAuthentication no

# Allow only specific users
AllowUsers adminuser

# Or allow only specific groups
AllowGroups wheel

# Disable X11 forwarding if not needed
X11Forwarding no

# Set maximum authentication attempts
MaxAuthTries 3

# Reduce login grace time
LoginGraceTime 30

# Maximum concurrent sessions
MaxSessions 5

# Use strong key exchange algorithms
KexAlgorithms curve25519-sha256,[email protected],diffie-hellman-group-exchange-sha256

# Use strong ciphers
Ciphers [email protected],[email protected],[email protected],aes256-ctr,aes192-ctr,aes128-ctr

# Use strong MAC algorithms
MACs [email protected],[email protected],hmac-sha2-512,hmac-sha2-256

# Disable unused authentication methods
PubkeyAuthentication yes
GSSAPIAuthentication no
HostbasedAuthentication no
IgnoreRhosts yes

# Enable strict mode
StrictModes yes

# Disable TCP forwarding if not needed (optional)
AllowTcpForwarding no

# Disable agent forwarding if not needed (optional)
AllowAgentForwarding no

# Set client alive interval to prevent timeouts
ClientAliveInterval 300
ClientAliveCountMax 2

# Log verbosity for security monitoring
LogLevel VERBOSE

# Use privilege separation
UsePrivilegeSeparation sandbox

Test SSH configuration syntax:

# Verify configuration has no syntax errors
sudo sshd -t

# If errors are found, fix them before restarting

Restart SSH service:

# Restart SSH daemon to apply changes
sudo systemctl restart sshd

# Verify SSH service is running
sudo systemctl status sshd

# Check if SSH is listening
sudo ss -tlnp | grep sshd

Test new configuration in a separate terminal before closing your current session:

# Open new terminal and test connection
ssh adminuser@your_server_ip

# Verify connection works with key authentication
# Attempt root login (should be denied)
ssh root@your_server_ip
# Should fail with "Permission denied"

Step 5: Configure Firewalld

Firewalld is the default dynamic firewall solution for RHEL-based distributions, providing zone-based network management.

# Install firewalld (usually pre-installed)
sudo dnf install firewalld -y

# Start and enable firewalld
sudo systemctl start firewalld
sudo systemctl enable firewalld

# Check firewalld status
sudo systemctl status firewalld
sudo firewall-cmd --state

Configure firewall rules:

# View default zone
sudo firewall-cmd --get-default-zone

# List all zones
sudo firewall-cmd --get-zones

# View active zones
sudo firewall-cmd --get-active-zones

# Set default zone to public (recommended)
sudo firewall-cmd --set-default-zone=public

Add essential services:

# Add SSH service (critical to avoid lockout!)
sudo firewall-cmd --permanent --add-service=ssh

# Add HTTP and HTTPS for web servers
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https

# Or use port numbers directly
sudo firewall-cmd --permanent --add-port=22/tcp
sudo firewall-cmd --permanent --add-port=80/tcp
sudo firewall-cmd --permanent --add-port=443/tcp

# Reload firewall to apply changes
sudo firewall-cmd --reload

# Verify active rules
sudo firewall-cmd --list-all

Expected output:

public (active)
  target: default
  icmp-block-inversion: no
  interfaces: eth0
  sources:
  services: cockpit dhcpv6-client http https ssh
  ports:
  protocols:
  forward: no
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

Advanced firewalld configuration:

# Allow specific IP to all ports
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.50" accept'

# Allow specific IP to specific port
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.50" port port="22" protocol="tcp" accept'

# Block specific IP
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.100" reject'

# Allow port range
sudo firewall-cmd --permanent --add-port=8000-8100/tcp

# Enable masquerading (NAT)
sudo firewall-cmd --permanent --add-masquerade

# Forward port
sudo firewall-cmd --permanent --add-forward-port=port=8080:proto=tcp:toport=80

# Reload after changes
sudo firewall-cmd --reload

# List all rules
sudo firewall-cmd --list-all-zones

Firewalld zone concepts:

  • drop: Incoming packets dropped without reply (most restrictive)
  • block: Incoming rejected with icmp-host-prohibited message
  • public: For untrusted public networks (default for servers)
  • external: For external networks with masquerading
  • dmz: For DMZ systems with limited access
  • work: For work networks with more trust
  • home: For home networks with greater trust
  • internal: For internal networks
  • trusted: All connections accepted (least restrictive)

Step 6: Install and Configure Fail2Ban

Fail2Ban protects against brute-force attacks by monitoring logs and banning malicious IP addresses.

# Enable EPEL repository (Extra Packages for Enterprise Linux)
sudo dnf install epel-release -y

# Install Fail2Ban
sudo dnf install fail2ban fail2ban-firewalld -y

# Create local configuration file
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

# Edit local configuration
sudo vi /etc/fail2ban/jail.local

Configure Fail2Ban for RHEL-based systems:

[DEFAULT]
# Ban hosts for 1 hour
bantime = 3600

# Monitor for failed attempts over 10 minutes
findtime = 600

# Ban after 5 failed attempts
maxretry = 5

# Backend (systemd for RHEL 8+)
backend = systemd

# Email notifications (optional)
destemail = [email protected]
sendername = Fail2Ban
action = %(action_mwl)s

# Use firewalld for ban actions
banaction = firewallcmd-ipset
banaction_allports = firewallcmd-ipset

[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 3
bantime = 7200

Create SELinux policy for Fail2Ban (if needed):

# Check for SELinux denials
sudo ausearch -c 'fail2ban-server' --raw | audit2allow -M fail2ban
sudo semodule -i fail2ban.pp

Start and enable Fail2Ban:

# Start Fail2Ban service
sudo systemctl start fail2ban

# Enable Fail2Ban to start on boot
sudo systemctl enable fail2ban

# Check Fail2Ban status
sudo systemctl status fail2ban

# View enabled jails
sudo fail2ban-client status

# View SSH jail status and banned IPs
sudo fail2ban-client status sshd

Fail2Ban management commands:

# Unban an IP address
sudo fail2ban-client set sshd unbanip 203.0.113.50

# Ban an IP address manually
sudo fail2ban-client set sshd banip 203.0.113.100

# View banned IPs in firewalld
sudo firewall-cmd --direct --get-all-rules

# Reload Fail2Ban
sudo fail2ban-client reload

# View Fail2Ban logs
sudo tail -f /var/log/fail2ban.log

Configure additional jails for common services:

# Edit jail.local to add more protections
sudo vi /etc/fail2ban/jail.local

# Add these sections for additional services:
[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 3

[postfix]
enabled = true
port = smtp,465,submission
logpath = /var/log/maillog
maxretry = 5

[dovecot]
enabled = true
port = pop3,pop3s,imap,imaps,submission,465,sieve
logpath = /var/log/dovecot.log
maxretry = 3

Step 7: Configure SELinux

SELinux (Security-Enhanced Linux) is a mandatory access control system that provides an additional security layer beyond traditional permissions.

Check SELinux status:

# View SELinux status
sudo sestatus

# Check current mode
getenforce

SELinux modes:

  • Enforcing: SELinux actively enforces security policy (recommended)
  • Permissive: SELinux logs violations but doesn't enforce (for troubleshooting)
  • Disabled: SELinux is completely disabled (not recommended)

Keep SELinux in enforcing mode (default on RHEL-based systems):

# Ensure SELinux is enforcing
sudo setenforce 1

# Make it persistent
sudo vi /etc/selinux/config

# Set:
SELINUX=enforcing
SELINUXTYPE=targeted

Common SELinux management tasks:

# Install SELinux management tools
sudo dnf install policycoreutils-python-utils setroubleshoot-server -y

# View SELinux denials
sudo ausearch -m AVC,USER_AVC -ts recent

# Generate allow rules from denials
sudo ausearch -m AVC,USER_AVC -ts recent | audit2allow -M custom_policy
sudo semodule -i custom_policy.pp

# View loaded SELinux modules
sudo semodule -l

# Check file context
ls -Z /path/to/file

# Restore default file contexts
sudo restorecon -Rv /path/to/directory

# Change file context
sudo semanage fcontext -a -t httpd_sys_content_t "/web(/.*)?"
sudo restorecon -Rv /web

# View SELinux boolean options
sudo getsebool -a

# Enable SELinux boolean
sudo setsebool -P httpd_can_network_connect on

# View SELinux port contexts
sudo semanage port -l

# Add port to SELinux context
sudo semanage port -a -t http_port_t -p tcp 8080

Troubleshooting SELinux issues:

# View SELinux alerts
sudo sealert -a /var/log/audit/audit.log

# Generate human-readable report
sudo sealert -a /var/log/audit/audit.log > /tmp/selinux-report.txt

# Temporarily set to permissive for testing
sudo setenforce 0

# Return to enforcing after fixing issues
sudo setenforce 1

Important: Never disable SELinux on production servers. If you encounter issues, use permissive mode temporarily to identify problems, then create appropriate policies.

Step 8: Set Up Automatic Security Updates

Configure DNF Automatic to install security updates automatically.

# Install dnf-automatic
sudo dnf install dnf-automatic -y

# Edit configuration
sudo vi /etc/dnf/automatic.conf

Configure automatic updates:

[commands]
# What kind of upgrade to perform:
# default = all available updates
# security = only security updates
upgrade_type = security

# Whether to download updates
download_updates = yes

# Whether to apply updates
apply_updates = yes

[emitters]
# Emit via email
emit_via = email
email_from = root@localhost
email_to = [email protected]

# Emit via systemd
system_name = your-server-hostname

[email]
email_host = localhost

Enable automatic updates:

# Enable and start dnf-automatic timer
sudo systemctl enable --now dnf-automatic.timer

# Check timer status
sudo systemctl status dnf-automatic.timer

# List all timers
sudo systemctl list-timers

# View dnf-automatic logs
sudo journalctl -u dnf-automatic.timer

Alternative: Only download updates automatically:

# Edit configuration to only download
sudo vi /etc/dnf/automatic.conf

# Set:
apply_updates = no

# Use download timer instead
sudo systemctl enable --now dnf-automatic-download.timer

Manual security update check:

# Check for security updates
sudo dnf updateinfo list security

# Apply security updates
sudo dnf upgrade --security -y

# View update history
sudo dnf history

Step 9: Configure Timezone and NTP

Correct time configuration is essential for logging, authentication, and scheduled tasks.

# Check current timezone and time status
timedatectl

# List available timezones
timedatectl list-timezones

# Set timezone (example: UTC)
sudo timedatectl set-timezone UTC

# Or set to specific location
sudo timedatectl set-timezone America/New_York

Configure NTP time synchronization:

# Install chrony (default on RHEL 8+)
sudo dnf install chrony -y

# Enable and start chronyd
sudo systemctl enable chronyd
sudo systemctl start chronyd

# Check chrony status
sudo systemctl status chronyd

# View time sources
chronyc sources -v

# Check synchronization status
chronyc tracking

Configure chrony:

# Edit chrony configuration
sudo vi /etc/chrony.conf

# Add or modify NTP servers:
server 0.pool.ntp.org iburst
server 1.pool.ntp.org iburst
server 2.pool.ntp.org iburst
server 3.pool.ntp.org iburst

# Allow NTP client access from local network (optional)
allow 192.168.1.0/24

# Restart chrony to apply changes
sudo systemctl restart chronyd

Verify time synchronization:

# Check system time
date

# Verify NTP synchronization
timedatectl status

# View chrony statistics
chronyc activity

# Force synchronization
sudo chronyc makestep

Step 10: Disable Unnecessary Services

Reduce attack surface by disabling services not required for your server's function.

# List all running services
sudo systemctl list-units --type=service --state=running

# List all enabled services
sudo systemctl list-unit-files --type=service --state=enabled

Common services to consider disabling on servers:

# Disable Bluetooth
sudo systemctl disable bluetooth.service
sudo systemctl stop bluetooth.service

# Disable CUPS printing
sudo systemctl disable cups.service
sudo systemctl stop cups.service

# Disable Avahi (unless using mDNS/Zeroconf)
sudo systemctl disable avahi-daemon.service
sudo systemctl stop avahi-daemon.service

# Disable ModemManager
sudo systemctl disable ModemManager.service
sudo systemctl stop ModemManager.service

# Disable postfix if not using mail
sudo systemctl disable postfix.service
sudo systemctl stop postfix.service

# Mask services to prevent them from starting
sudo systemctl mask bluetooth.service

Remove unnecessary packages:

# List installed packages
rpm -qa

# Remove specific package
sudo dnf remove package_name -y

# Remove unused dependencies
sudo dnf autoremove -y

# Clean package cache
sudo dnf clean all

Disable unnecessary network protocols:

# Disable IPv6 if not used
sudo vi /etc/sysctl.conf

# Add these lines:
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1

# Apply changes
sudo sysctl -p

# Verify IPv6 is disabled
ip a | grep inet6

Review and disable kernel modules:

# List loaded kernel modules
lsmod

# Disable specific module
echo "blacklist modulename" | sudo tee /etc/modprobe.d/blacklist-modulename.conf

# Common modules to consider disabling:
# USB storage (if not needed)
echo "install usb-storage /bin/true" | sudo tee /etc/modprobe.d/disable-usb-storage.conf

# Bluetooth
echo "install bluetooth /bin/true" | sudo tee /etc/modprobe.d/disable-bluetooth.conf

Step 11: Implement Additional Security Measures

Configure System Limits

# Edit system limits
sudo vi /etc/security/limits.conf

# Add resource limits:
* soft nofile 65536
* hard nofile 65536
* soft nproc 32768
* hard nproc 32768
root soft nofile 65536
root hard nofile 65536

Harden Kernel Parameters

# Edit sysctl configuration
sudo vi /etc/sysctl.d/99-security.conf

# Add security-related kernel parameters:
# IP Forwarding (disable unless routing)
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0

# Disable source packet routing
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# Disable ICMP redirect acceptance
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

# Disable secure ICMP redirect acceptance
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0

# Enable source address verification
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Log martian 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

# Ignore bogus ICMP error responses
net.ipv4.icmp_ignore_bogus_error_responses = 1

# Enable TCP SYN cookies (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

# Disable IPv6 router advertisements
net.ipv6.conf.all.accept_ra = 0
net.ipv6.conf.default.accept_ra = 0

# Increase system file descriptor limits
fs.file-max = 2097152

# Disable core dumps
fs.suid_dumpable = 0
kernel.core_uses_pid = 1

# Enable ExecShield (buffer overflow protection)
kernel.exec-shield = 1
kernel.randomize_va_space = 2

Apply kernel parameters:

# Load new sysctl settings
sudo sysctl -p /etc/sysctl.d/99-security.conf

# Verify settings
sudo sysctl -a | grep net.ipv4.tcp_syncookies

Configure Audit System

# Auditd is installed by default on RHEL-based systems
# Verify auditd is running
sudo systemctl status auditd

# Enable auditd
sudo systemctl enable auditd

# View audit configuration
sudo vi /etc/audit/auditd.conf

# Add audit rules
sudo vi /etc/audit/rules.d/custom.rules

Add these audit rules:

# Monitor authentication attempts
-w /var/log/lastlog -p wa -k logins
-w /var/run/faillock/ -p wa -k logins

# Monitor network environment changes
-a always,exit -F arch=b64 -S sethostname -S setdomainname -k network_modifications
-w /etc/hosts -p wa -k network_modifications
-w /etc/sysconfig/network -p wa -k network_modifications

# Monitor changes to security files
-w /etc/selinux/ -p wa -k selinux_changes
-w /etc/sudoers -p wa -k sudoers_changes
-w /etc/sudoers.d/ -p wa -k sudoers_changes

# Monitor SSH configuration
-w /etc/ssh/sshd_config -p wa -k sshd_config_changes

# Monitor firewall configuration
-w /etc/firewalld/ -p wa -k firewall_changes

Load audit rules:

# Load custom audit rules
sudo augenrules --load

# View loaded audit rules
sudo auditctl -l

# Search audit logs
sudo ausearch -k logins
sudo ausearch -k network_modifications

Configure Password Policies

# Install password quality library
sudo dnf install libpwquality -y

# Configure password requirements
sudo vi /etc/security/pwquality.conf

Set strong password requirements:

# Minimum password length
minlen = 14

# Require at least one digit
dcredit = -1

# Require at least one uppercase character
ucredit = -1

# Require at least one lowercase character
lcredit = -1

# Require at least one special character
ocredit = -1

# Maximum number of consecutive characters
maxrepeat = 3

# Maximum number of same character class
maxclassrepeat = 4

# Minimum number of character classes
minclass = 4

Configure password aging:

# Edit login.defs
sudo vi /etc/login.defs

# Set password aging:
PASS_MAX_DAYS 90
PASS_MIN_DAYS 7
PASS_WARN_AGE 14
PASS_MIN_LEN 14

Verification

After completing all security configurations, verify everything works correctly.

Comprehensive Security Check

# Create security verification script
cat << 'EOF' | sudo tee /usr/local/bin/security-check.sh
#!/bin/bash
echo "=== System Security Status ==="
echo ""
echo "--- Package Updates ---"
dnf check-update | head -10
echo ""
echo "--- SSH Configuration ---"
sshd -t && echo "SSH config: OK" || echo "SSH config: ERROR"
echo ""
echo "--- Firewall Status ---"
firewall-cmd --state
firewall-cmd --list-all
echo ""
echo "--- Fail2Ban Status ---"
systemctl is-active fail2ban
fail2ban-client status
echo ""
echo "--- SELinux Status ---"
sestatus
echo ""
echo "--- Open Ports ---"
ss -tulnp
echo ""
echo "--- Recent Login Attempts ---"
grep "Failed password" /var/log/secure | tail -10
echo ""
echo "--- Audit Rules ---"
auditctl -l | wc -l
echo ""
EOF

# Make script executable
sudo chmod +x /usr/local/bin/security-check.sh

# Run security check
sudo /usr/local/bin/security-check.sh

Verify Individual Components

# Verify SSH security
sudo sshd -t
sudo systemctl status sshd

# Verify firewall
sudo firewall-cmd --state
sudo firewall-cmd --list-all

# Verify Fail2Ban
sudo systemctl status fail2ban
sudo fail2ban-client status sshd

# Verify SELinux
sudo sestatus
sudo ausearch -m AVC -ts recent

# Verify automatic updates
sudo systemctl status dnf-automatic.timer

# Verify time synchronization
chronyc tracking

# Verify audit system
sudo systemctl status auditd
sudo auditctl -l

Troubleshooting

SSH Connection Issues After Hardening

Problem: Cannot connect via SSH after configuration changes.

Solution:

# Access via console and check SSH status
sudo systemctl status sshd

# Check for configuration errors
sudo sshd -t

# View SSH logs
sudo journalctl -u sshd -n 50

# Check SELinux denials
sudo ausearch -m AVC -c sshd

# Temporarily allow all SSH
sudo semanage permissive -a sshd_t

# Fix and return to enforcing
sudo semanage permissive -d sshd_t

Firewalld Blocking Services

Problem: Cannot access services after enabling firewalld.

Solution:

# Check firewall rules
sudo firewall-cmd --list-all

# Add missing service
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload

# Check if port is open
sudo ss -tlnp | grep :80

# View firewall logs
sudo journalctl -u firewalld -n 50

# Temporarily disable firewall for testing
sudo systemctl stop firewalld

# Re-enable after fixing
sudo systemctl start firewalld

SELinux Blocking Operations

Problem: Service fails due to SELinux policy violations.

Solution:

# Check SELinux denials
sudo ausearch -m AVC -ts today

# Generate allow rules
sudo ausearch -m AVC -ts today | audit2allow -M mypolicy
sudo semodule -i mypolicy.pp

# Check file contexts
ls -Z /path/to/file

# Restore contexts
sudo restorecon -Rv /path/to/directory

# View detailed alerts
sudo sealert -a /var/log/audit/audit.log

# Temporarily set permissive for specific domain
sudo semanage permissive -a httpd_t

Fail2Ban Not Banning

Problem: Fail2Ban not blocking attackers.

Solution:

# Check Fail2Ban status
sudo systemctl status fail2ban

# View Fail2Ban logs
sudo tail -f /var/log/fail2ban.log

# Test regex patterns
sudo fail2ban-regex /var/log/secure /etc/fail2ban/filter.d/sshd.conf

# Check SELinux denials for Fail2Ban
sudo ausearch -c fail2ban

# Manually ban IP for testing
sudo fail2ban-client set sshd banip 1.2.3.4

# Check firewalld for banned IPs
sudo firewall-cmd --direct --get-all-rules

Time Synchronization Issues

Problem: Server time is incorrect.

Solution:

# Check chronyd status
sudo systemctl status chronyd

# View time sources
chronyc sources -v

# Force time sync
sudo chronyc makestep

# Check for network issues
ping -c 4 0.pool.ntp.org

# Verify firewall allows NTP (UDP 123)
sudo firewall-cmd --permanent --add-service=ntp
sudo firewall-cmd --reload

# Restart chronyd
sudo systemctl restart chronyd

Best Practices

Security Maintenance Schedule

Daily:

  • Monitor authentication logs: sudo journalctl -u sshd -S today
  • Review Fail2Ban activity: sudo fail2ban-client status
  • Check resource usage: top, htop

Weekly:

  • Review security updates: sudo dnf updateinfo list security
  • Audit firewall rules: sudo firewall-cmd --list-all-zones
  • Check SELinux denials: sudo ausearch -m AVC -ts week
  • Review audit logs: sudo ausearch -ts week

Monthly:

  • Apply security updates: sudo dnf upgrade --security
  • Review user accounts and permissions
  • Audit SSH authorized_keys files
  • Check for unnecessary packages
  • Review and rotate logs
  • Test backup restoration

Quarterly:

  • Full security audit with Lynis or OpenSCAP
  • Review and update security policies
  • Rotate passwords and SSH keys
  • Test disaster recovery procedures
  • Update documentation

RHEL-Specific Security Tools

# Install OpenSCAP security scanner
sudo dnf install openscap-scanner scap-security-guide -y

# Run security compliance scan
sudo oscap xccdf eval --profile xccdf_org.ssgproject.content_profile_pci-dss \
  --results-arf /tmp/scan-results.xml \
  --report /tmp/scan-report.html \
  /usr/share/xml/scap/ssg/content/ssg-rl9-ds.xml

# View report in browser
firefox /tmp/scan-report.html

Configuration Backup

# Create backup script
cat << 'EOF' | sudo tee /usr/local/bin/backup-configs.sh
#!/bin/bash
BACKUP_DIR="/root/config-backups"
DATE=$(date +%Y%m%d-%H%M%S)
mkdir -p $BACKUP_DIR

tar -czf $BACKUP_DIR/configs-$DATE.tar.gz \
  /etc/ssh/sshd_config \
  /etc/firewalld/ \
  /etc/fail2ban/ \
  /etc/selinux/config \
  /etc/sysctl.d/ \
  /etc/security/ \
  /etc/audit/ \
  /etc/chrony.conf \
  /etc/dnf/automatic.conf

echo "Backup created: $BACKUP_DIR/configs-$DATE.tar.gz"

# Keep only last 10 backups
ls -t $BACKUP_DIR/configs-*.tar.gz | tail -n +11 | xargs rm -f
EOF

# Make executable
sudo chmod +x /usr/local/bin/backup-configs.sh

# Run backup
sudo /usr/local/bin/backup-configs.sh

Monitoring Commands Aliases

# Add useful aliases to .bashrc
cat << 'EOF' >> ~/.bashrc

# Security monitoring aliases
alias auth-log='sudo journalctl -u sshd -n 100'
alias fail-log='sudo tail -f /var/log/fail2ban.log'
alias fw-status='sudo firewall-cmd --list-all'
alias se-status='sudo sestatus'
alias ports='sudo ss -tulnp'
alias sec-check='sudo /usr/local/bin/security-check.sh'

EOF

source ~/.bashrc

Conclusion

You've successfully implemented comprehensive security hardening for your RHEL-based server. These configurations establish a robust security foundation that leverages the enterprise-grade security features of CentOS, Rocky Linux, AlmaLinux, or Red Hat Enterprise Linux.

Key achievements:

  • System packages updated with latest security patches
  • Non-root administrative user with sudo access configured
  • SSH hardened with key authentication and secure settings
  • Firewalld configured for network-level protection
  • Fail2Ban actively protecting against brute-force attacks
  • SELinux enforcing mandatory access controls
  • Automatic security updates enabled
  • Unnecessary services disabled
  • Kernel security parameters hardened
  • Audit logging configured for accountability

RHEL-specific advantages:

  • SELinux provides additional mandatory access control layer
  • Firewalld offers dynamic firewall management with zones
  • Extended support lifecycle ensures long-term security
  • Enterprise-grade security certifications (FIPS, Common Criteria)
  • Robust audit subsystem for compliance requirements

Next steps:

  • Configure application-specific security measures
  • Implement monitoring and alerting systems
  • Set up backup and disaster recovery procedures
  • Regular security audits and compliance scans
  • Stay informed about RHEL security advisories

Remember that security is an ongoing process. Stay current with security updates, monitor logs regularly, and adapt configurations as new threats emerge.

Additional Resources

Related Guides

  • How to Connect to Your Server via SSH
  • Firewall Configuration with firewalld (CentOS/Rocky)
  • SELinux: Basic Configuration and Modes
  • How to Configure Users and Permissions on Linux
  • How to Change the Default SSH Port
  • Fail2Ban Configuration for Brute Force Protection
  • Linux Server Hardening: Complete Guide