Tested on: AlmaLinux 9.4 (kernel 5.14.x). The same steps apply to Rocky Linux 9 and Red Hat Enterprise Linux 9.x — all three share the same package set, service names, and SELinux profile.
Why this matters
A fresh RHEL-family cloud image differs from Ubuntu’s defaults in two important ways:
- SELinux is in enforcing mode by default — which is good, until an
operator hits a denied syscall, panics, and runs
setenforce 0. The baseline below assumes you’ll keep SELinux on and learn to tune it. - The default firewall is
firewalld, notufw— same idea, different vocabulary. Internal-API hosts often ship withfirewalldenabled but permissive; never trust the defaults without inspecting.
Otherwise the threat model and the order of operations match the Ubuntu baseline: create a non-root user, harden SSH, enforce a host firewall, enable automatic security updates, turn on auditing, fix time. This guide is the RHEL-family-specific translation.
Order matters
Same warning as the Ubuntu baseline: do not enable the firewall before you have confirmed SSH access for your new non-root user.
1. First boot: create your operator account
Most RHEL-family cloud images ship with a cloud-user, ec2-user, or
almalinux account. Treat that account as a bootstrap, not as a permanent
operator identity.
1sudo adduser alice
2sudo usermod -aG wheel alice # wheel = sudo equivalent on RHEL family
3sudo mkdir -p /home/alice/.ssh
4sudo chmod 700 /home/alice/.ssh
5sudo tee /home/alice/.ssh/authorized_keys <<'EOF'
6ssh-ed25519 AAAA... alice@workstation 2026-05-16
7EOF
8sudo chmod 600 /home/alice/.ssh/authorized_keys
9sudo chown -R alice:alice /home/alice/.ssh
10
11# Verify wheel grants sudo:
12grep -E '^\s*%wheel' /etc/sudoers # should be present and uncommented
13
14# Test in a second terminal:
15# ssh alice@host 'sudo -n true && echo ok'
2. Apply the SSH hardening baseline
The full guide is at /guides/ssh-hardening/;
the drop-in file is identical on Ubuntu and RHEL. The only RHEL-specific
detail: AllowGroups wheel instead of AllowGroups sudo.
1sudo tee /etc/ssh/sshd_config.d/00-hardened.conf >/dev/null <<'EOF'
2PasswordAuthentication no
3PermitRootLogin no
4PubkeyAuthentication yes
5AllowGroups wheel
6MaxAuthTries 3
7ClientAliveInterval 300
8ClientAliveCountMax 2
9EOF
10sudo sshd -t && sudo systemctl reload sshd
3. Enable the host firewall — firewalld
firewalld is zone-based; the public zone is the default on cloud
images. For a typical web host:
1sudo systemctl enable --now firewalld
2
3# Open only what this server needs (services are pre-defined):
4sudo firewall-cmd --zone=public --permanent --add-service=ssh
5sudo firewall-cmd --zone=public --permanent --add-service=http
6sudo firewall-cmd --zone=public --permanent --add-service=https
7
8# Apply.
9sudo firewall-cmd --reload
10sudo firewall-cmd --list-all --zone=public
For internal-API hosts, drop the default public zone services and use
a custom zone bound to the private interface — see the firewalld zones
guide for that pattern.
4. Automatic security updates — dnf-automatic
1sudo dnf install -y dnf-automatic
Configure /etc/dnf/automatic.conf:
1[commands]
2upgrade_type = security
3random_sleep = 0
4download_updates = yes
5apply_updates = yes
6
7[emitters]
8emit_via = stdio,motd
9
10[email]
11email_from = root@$HOSTNAME
12email_to = [email protected]
Enable the timer:
1sudo systemctl enable --now dnf-automatic.timer
2systemctl list-timers dnf-automatic.timer
Reboot policy on RHEL is a deliberate choice — dnf-automatic itself
does not reboot. Either install dnf-utils and schedule
needs-restarting -r checks, or use the needrestart package
(EPEL) for advisories. For single-instance workloads, a scheduled
weekly reboot via cron is the simplest approach.
5. Time sync — chrony
chrony is the RHEL family default. Verify it’s running:
1sudo systemctl enable --now chronyd
2chronyc tracking
3chronyc sources
Cloud images typically point at the provider’s NTP servers; that’s fine. For multi-cloud or on-prem fleets, point at a public stratum-2 pool plus internal NTP servers if you have them.
6. SELinux — verify enforcing, learn to tune
1sestatus
2# Expected output:
3# SELinux status: enabled
4# Current mode: enforcing
5# Policy MLS status: enabled
6# Policy deny_unknown status: allowed
If a service is being denied, the workflow is:
1# 1. Look at the AVC denial:
2sudo ausearch -m AVC -ts recent | tail
3
4# 2. Get a human-readable explanation:
5sudo ausearch -m AVC -ts recent | audit2why
6
7# 3. ONLY if the requested access is legitimate, generate a custom policy:
8sudo ausearch -m AVC -ts recent | audit2allow -M myapp-fix
9sudo semodule -i myapp-fix.pp
The temptation to set SELinux to permissive will arise. Don’t. SELinux is one of the few defences that has prevented real container-escape and file-disclosure vulnerabilities from being exploitable. Tune the policy; do not disable the enforcement.
7. Audit logging — auditd
auditd is installed by default on RHEL-family systems. The ruleset
matches the Ubuntu baseline:
1sudo tee /etc/audit/rules.d/baseline.rules <<'EOF'
2# Identity / authentication
3-w /etc/passwd -p wa -k identity
4-w /etc/shadow -p wa -k identity
5-w /etc/group -p wa -k identity
6-w /etc/sudoers -p wa -k identity
7-w /etc/sudoers.d/ -p wa -k identity
8
9# SSH config
10-w /etc/ssh/sshd_config -p wa -k sshd
11-w /etc/ssh/sshd_config.d/ -p wa -k sshd
12
13# SELinux config — useful for change detection
14-w /etc/selinux/config -p wa -k selinux
15
16# Privileged commands
17-a always,exit -F arch=b64 -S execve -F euid=0 -F auid>=1000 -F auid!=4294967295 -k privileged
18
19-e 2
20EOF
21
22sudo augenrules --load
23sudo systemctl restart auditd
24sudo auditctl -l
Forward auditd events off-host. RHEL has first-class support for
audisp-remote which sends events to a central audit collector over TLS.
8. Remove or disable unused services
1# Listening sockets:
2sudo ss -ltnp
3
4# Enabled services:
5systemctl list-unit-files --state=enabled --type=service
Common candidates on a minimal install: cups-browsed, bluetooth,
avahi-daemon. Do not remove firewalld, chronyd, auditd, sshd,
or polkit.
9. Optional: brute-force log filtering
EPEL provides fail2ban; RHEL/AlmaLinux also bundles sshguard directly
in the base repositories. Either is acceptable — the security boundary is
key-only authentication, and these tools exist to keep your logs readable.
1sudo dnf install -y epel-release
2sudo dnf install -y fail2ban
3sudo systemctl enable --now fail2ban
10. Run a baseline scan
Two tools are worth running on a RHEL-family host:
1# CIS-style hardening guidance:
2sudo dnf install -y openscap-scanner scap-security-guide
3sudo oscap xccdf eval --profile xccdf_org.ssgproject.content_profile_cis \
4 /usr/share/xml/scap/ssg/content/ssg-almalinux9-ds.xml | less
5
6# Lynis (also available via EPEL):
7sudo dnf install -y lynis
8sudo lynis audit system --quick
Investigate every FAIL from oscap and every WARNING from lynis.
Document accepted deviations.
Gotchas
firewall-cmd without --permanent
firewall-cmd --add-service=... without --permanent makes a runtime
change that disappears on the next reload or reboot. Always pair the
change with a --reload, or use --permanent + --reload explicitly.
setenforce 0 is not a fix
Putting SELinux into permissive mode to “see if it works” is fine for a short diagnostic. Leaving it that way is a documented finding in any serious audit and undoes one of the strongest controls on the host.
dnf-automatic and kernel updates
Kernel updates apply, but the new kernel only runs after a reboot.
Until then, your uname -r reports the old version while dnf shows
the new package installed — confusing during compliance scans. Pair
dnf-automatic with a documented reboot policy.
EPEL is a third-party repository
Tools mentioned above (fail2ban, lynis) come from EPEL. EPEL is
maintained by Fedora contributors but is not Red Hat. Document this as a
processor / supply-chain dependency where compliance demands a full SBOM.
Cloud-init replacing your changes on reboot
Some cloud images run cloud-init on every boot and can re-apply original
network or user configuration. Check /etc/cloud/cloud.cfg and disable
the modules whose output you do not want re-applied (commonly
users-groups, set-passwords, ssh).
What this guide deliberately does not cover
- Full CIS Benchmark profile compliance — the oscap step above is a start; full coverage is a separate guide.
- Subscription Manager / Red Hat Insights — only relevant on upstream RHEL; AlmaLinux / Rocky operators can ignore.
- podman / container host hardening — out of scope for this site.