DevOpsil
Linux
90%
Needs Review

Rocky Linux 9 Server Hardening: Security Baseline from First Boot

Raafay AsifRaafay Asif7 min read

Rocky Linux 9 Ships Opinionated Security Defaults

Rocky Linux 9 (based on RHEL 9) has better out-of-the-box security than most distributions. SELinux is enforcing, firewalld is enabled, and password policies are set. But "better than default" isn't the same as "production-ready."

This guide takes you from the Rocky Linux 9 first boot to a hardened baseline aligned with DISA STIG and CIS Benchmark Level 1 recommendations.


1. Initial Setup: Non-Root User and SSH Keys

# Create admin user
useradd -m -G wheel adminuser
passwd adminuser

# Add SSH key for the admin user
mkdir -p /home/adminuser/.ssh
chmod 700 /home/adminuser/.ssh
echo "ssh-ed25519 AAAA... your-key" >> /home/adminuser/.ssh/authorized_keys
chmod 600 /home/adminuser/.ssh/authorized_keys
chown -R adminuser:adminuser /home/adminuser/.ssh

# Lock root login
passwd -l root

2. SSH Hardening

Edit /etc/ssh/sshd_config:

Port 2222
AddressFamily inet
ListenAddress 0.0.0.0

PermitRootLogin no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM yes

X11Forwarding no
AllowTcpForwarding no
GatewayPorts no
PermitTunnel no

MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2

Banner /etc/ssh/banner.txt

# Restrict to specific algorithms
HostKeyAlgorithms ssh-ed25519,rsa-sha2-256,rsa-sha2-512
KexAlgorithms curve25519-sha256,ecdh-sha2-nistp256
Ciphers [email protected],[email protected],aes256-ctr
MACs [email protected],[email protected]

Create the login banner:

cat > /etc/ssh/banner.txt << 'EOF'
*******************************************************************
AUTHORIZED ACCESS ONLY. All activity is monitored and logged.
Unauthorized access is prohibited and will be prosecuted.
*******************************************************************
EOF
# Validate and restart
sudo sshd -t
sudo systemctl restart sshd

3. SELinux: Keep It Enforcing

Rocky Linux 9 defaults to SELinux enforcing. Keep it there.

# Verify status
getenforce     # Should output: Enforcing
sestatus

# Never set to Disabled (requires reboot to re-enable)
# Never set to Permissive in production

# Check for SELinux denials
sudo ausearch -m AVC,USER_AVC -ts recent
sudo journalctl -t setroubleshoot --since "1 hour ago"

# If a legitimate service is being denied, create a policy module
# rather than setting permissive
sudo ausearch -c 'nginx' --raw | audit2allow -M mynginx
sudo semodule -i mynginx.pp

# Common boolean flags
getsebool -a | grep httpd
sudo setsebool -P httpd_can_network_connect on   # allow Nginx to proxy
sudo setsebool -P httpd_use_nfs on              # allow NFS-served content

# Restore correct file contexts after moving files
sudo restorecon -Rv /var/www/html/

4. firewalld Configuration

# Check status
sudo firewall-cmd --state

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

# Set default zone to drop (deny everything not explicitly allowed)
sudo firewall-cmd --set-default-zone=drop

# Allow your SSH port (do this BEFORE enabling drop zone if SSH is on non-standard port)
sudo firewall-cmd --zone=drop --permanent --add-port=2222/tcp

# Allow HTTP/HTTPS for web servers
sudo firewall-cmd --zone=drop --permanent --add-service=http
sudo firewall-cmd --zone=drop --permanent --add-service=https

# Apply changes
sudo firewall-cmd --reload

# Verify
sudo firewall-cmd --zone=drop --list-all

Rate-Limit SSH with firewalld

# Limit SSH to 3 connections per minute per IP
sudo firewall-cmd --permanent --add-rich-rule='
  rule family="ipv4"
  service name="ssh"
  limit value="3/m"
  accept'
sudo firewall-cmd --reload

5. PAM and Password Policy

# Install password quality library
sudo dnf install libpwquality -y

# Edit /etc/security/pwquality.conf
cat >> /etc/security/pwquality.conf << 'EOF'
minlen = 14
minclass = 3
maxrepeat = 3
maxsequence = 4
gecoscheck = 1
EOF

# Account lockout policy — edit /etc/security/faillock.conf
cat > /etc/security/faillock.conf << 'EOF'
deny = 5
unlock_time = 900
fail_interval = 900
EOF

# Password aging (for local accounts)
# Set in /etc/login.defs
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   14/' /etc/login.defs

6. auditd: System Call Auditing

Rocky Linux 9 ships auditd and enables it by default. Extend it with security-relevant rules:

# Check auditd status
sudo systemctl status auditd
sudo auditctl -l   # list active rules

# Add comprehensive audit rules
# File: /etc/audit/rules.d/99-hardening.rules
cat > /etc/audit/rules.d/99-hardening.rules << 'EOF'
# Immutable (must be last) — prevents rule modification at runtime
# -e 2

# Monitor authentication files
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/gshadow -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers

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

# Monitor cron
-w /etc/cron.d/ -p wa -k cron
-w /etc/crontab -p wa -k cron
-w /var/spool/cron/ -p wa -k cron

# Privileged command execution
-a always,exit -F arch=b64 -S execve -F euid=0 -k root_commands

# System calls related to time changes
-a always,exit -F arch=b64 -S adjtimex -S settimeofday -k time_change

# Network configuration changes
-a always,exit -F arch=b64 -S sethostname -S setdomainname -k system_locale
-w /etc/hosts -p wa -k hosts_file
-w /etc/sysconfig/network -p wa -k network_config

# Login/logout events
-w /var/log/lastlog -p wa -k logins
-w /var/run/faillock -p wa -k logins
EOF

# Load the rules
sudo augenrules --load
sudo systemctl restart auditd

# Search audit logs
sudo ausearch -k identity -ts today
sudo ausearch -k sudoers --since yesterday

7. AIDE: File Integrity Monitoring

AIDE (Advanced Intrusion Detection Environment) detects unauthorized changes to system files.

# Install AIDE
sudo dnf install aide -y

# Initialize the baseline database (takes a few minutes)
sudo aide --init
sudo mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

# Run a check (compare current state to baseline)
sudo aide --check

# Schedule daily checks via cron
cat > /etc/cron.daily/aide-check << 'EOF'
#!/bin/bash
/usr/sbin/aide --check 2>&1 | mail -s "AIDE report - $(hostname)" root
EOF
chmod 700 /etc/cron.daily/aide-check

# Update database after intentional changes (e.g., after package updates)
sudo aide --update
sudo mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

8. Kernel Hardening via sysctl

Create /etc/sysctl.d/99-hardening.conf:

# Disable kernel pointer exposure
kernel.kptr_restrict = 2

# Restrict dmesg to root
kernel.dmesg_restrict = 1

# Enable ASLR
kernel.randomize_va_space = 2

# Disable core dumps for setuid programs
fs.suid_dumpable = 0

# Prevent unprivileged users from reading /proc/PID/
kernel.yama.ptrace_scope = 1

# Network hardening
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.tcp_syncookies = 1

# Disable IPv6 if unused
# net.ipv6.conf.all.disable_ipv6 = 1
sudo sysctl --system

9. CIS Benchmark Check with OpenSCAP

Rocky Linux 9 ships with OpenSCAP for automated compliance checking:

# Install OpenSCAP and SCAP Security Guide
sudo dnf install openscap-scanner scap-security-guide -y

# List available profiles
oscap info /usr/share/xml/scap/ssg/content/ssg-rl9-ds.xml | grep Profile

# Run CIS Level 1 assessment
sudo oscap xccdf eval \
  --profile xccdf_org.ssgproject.content_profile_cis \
  --report /tmp/cis-report.html \
  /usr/share/xml/scap/ssg/content/ssg-rl9-ds.xml

# View the report
# Open /tmp/cis-report.html in a browser
# It shows pass/fail for each CIS control with remediation guidance

# Generate remediation script
sudo oscap xccdf generate fix \
  --profile xccdf_org.ssgproject.content_profile_cis \
  --fix-type bash \
  /usr/share/xml/scap/ssg/content/ssg-rl9-ds.xml > /tmp/cis-remediation.sh

Hardening Checklist

ControlCommand to Verify
SELinux enforcinggetenforce
Root login disabledgrep PermitRootLogin /etc/ssh/sshd_config
SSH password auth offgrep PasswordAuthentication /etc/ssh/sshd_config
firewalld activefirewall-cmd --state
auditd runningsystemctl is-active auditd
AIDE initializedls /var/lib/aide/aide.db.gz
No unneeded servicessystemctl list-units --type=service --state=running
Password policy setchage -l root
Kernel hardeningsysctl kernel.randomize_va_space

This baseline covers the most impactful controls. Run the OpenSCAP assessment after applying these to identify remaining gaps — the report includes remediation scripts for each failed check.

Share:

Was this article helpful?

Raafay Asif
Raafay Asif

Linux Systems Engineer

Everything runs on Linux — I make sure it runs well. From kernel tuning to systemd debugging, I live in the terminal. If your server is misbehaving, I've probably seen that exact dmesg output before.

Related Articles

More in Linux

View all →

Discussion