Securing and optimizing a RHEL 9 or RHEL 10 server

Updated November 2026

The old book "Securing and Optimizing Linux: Red Hat Edition" was written for Red Hat Linux 6 and told you to recompile half the system. Today Red Hat Enterprise Linux (RHEL) and its rebuilds ship with SELinux, a firewall, system-wide crypto policies and tuning profiles already in place. Your job is to keep them on, remove what you do not need, and set a few things deliberately. This guide does that for RHEL 9 and 10, AlmaLinux and Rocky Linux, in the order you would do it on a new server.

Pick the release

RHEL 9 came out on May 18, 2022. Full support ends May 31, 2027 and maintenance support (security fixes) May 31, 2032. RHEL 10 came out on May 20, 2025, with full support until May 31, 2030 and maintenance until May 31, 2035. Paid add-ons extend both further. These are Red Hat's published dates as of November 2026; Red Hat calls future dates approximate. For a new server, choose RHEL 10 unless an application is only certified on 9.

AlmaLinux and Rocky Linux rebuild RHEL from its sources and use the same commands. The main differences: they do not use subscription-manager, and Red Hat services such as its kernel live patches need a RHEL subscription. Check your release with cat /etc/os-release.

Things that changed in RHEL 10 and matter for hardening:

Install small

Choose the Minimal Install environment in the installer and add only what the server needs. Fewer packages mean fewer updates and fewer things that can be attacked. If you install many servers, put the choices in a Kickstart file (see the KickStart HOWTO). Consider separate file systems for /var, /var/log and /var/log/audit, so a flood of logs cannot fill the root file system, and LUKS encryption for laptops and servers outside a locked room.

After the first boot, look at what runs and what is enabled, and remove the rest:

sudo ss -tulpn
systemctl list-unit-files --state=enabled
sudo systemctl disable --now NAME.service   # a service you do not need
sudo dnf remove PACKAGE                     # and its package

Register and update

sudo subscription-manager register        # RHEL only
sudo dnf upgrade
sudo dnf updateinfo list --updates --security  # security advisories not yet installed
sudo dnf update --security                 # install only security fixes
sudo dnf needs-restarting -r               # tells you if a reboot is needed

Look at one advisory with dnf updateinfo info RHSA-2026:1234 (use a real ID from the list) and install just that one with dnf update --advisory=RHSA-2026:1234.

Automatic security updates

sudo dnf install dnf-automatic
sudoedit /etc/dnf/automatic.conf

In the [commands] section set:

upgrade_type = security
reboot = when-needed

Then enable the timer that downloads and installs:

sudo systemctl enable --now dnf-automatic-install.timer
systemctl list-timers dnf-automatic*

reboot = when-needed restarts after kernel or systemd updates, with a five-minute warning by default. If unplanned reboots are not acceptable, leave it at never and schedule a weekly maintenance reboot instead.

Kernel live patching

With a RHEL subscription, kpatch applies critical kernel fixes without a reboot. On RHEL 10 it is supported from 10.2:

sudo dnf install kpatch-dnf
sudo dnf kpatch auto        # subscribe current and future kernels
sudo kpatch list

Live patches cover a limited time per kernel. You still need regular reboots into new kernels.

SELinux: keep it enforcing

SELinux confines each service to the files, ports and actions its policy allows. A broken web application then cannot read /etc/shadow or open a shell to the attacker's server. RHEL runs it in enforcing mode by default.

getenforce          # Enforcing
sestatus            # mode, policy, and the mode set in /etc/selinux/config

When an application is blocked, fix the specific cause. Most problems are one of three kinds:

Files in an unusual place
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?"
sudo restorecon -Rv /srv/www
A service on a non-standard port
sudo semanage port -l | grep ssh_port_t
sudo semanage port -a -t ssh_port_t -p tcp 2222
A documented on/off switch (boolean)
getsebool -a | grep httpd
sudo setsebool -P httpd_can_network_connect_db on

To see what was denied and why:

sudo ausearch -m AVC,USER_AVC,SELINUX_ERR,USER_SELINUX_ERR -ts recent
sudo dnf install setroubleshoot-server
sudo sealert -l "*"

While debugging, make one domain permissive instead of the whole system: sudo semanage permissive -a httpd_t, and remove it afterwards with -d. Do not reach for audit2allow first. It writes a rule that allows whatever was denied, including things that should stay denied.

Do not disable SELinux. Turning it off (or setting the whole system permissive) removes the protection for every service at once, and Red Hat strongly discourages it. Fix the label, port or boolean, or make the one affected domain permissive for a short time while you investigate, then remove that exception.

firewalld: open only what you serve

firewalld is on by default, uses nftables underneath, and puts interfaces in the public zone. On a typical fresh install that zone allows ssh, dhcpv6-client and cockpit; check yours with --list-all.

sudo firewall-cmd --get-active-zones
sudo firewall-cmd --list-all
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --remove-service=cockpit
sudo firewall-cmd --reload

To allow SSH only from your admin network, replace the open service with a rich rule:

sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" service name="ssh" accept'
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --reload

Do this from a console or from inside that network, and check you can still log in before closing your session. The Firewall HOWTO has more on zones and logging.

SSH

In RHEL 10, PermitRootLogin defaults to prohibit-password: root may log in with a key but not a password. Go further. Set up key login for your own account first (ssh-keygen -t ed25519, then ssh-copy-id; in FIPS mode use -t ecdsa, because Ed25519 is not FIPS-approved). Then add a drop-in file. sshd uses the first value it reads, and RHEL's own settings are in 50-redhat.conf, so pick a lower number:

# /etc/ssh/sshd_config.d/10-local.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
AllowGroups wheel

Keep your current session open. Before you reload or restart sshd, check that your own account is in wheel (id) because of AllowGroups wheel, leave the session you are working in open (and ideally a second one), and have console access ready. Existing sessions survive a reload; log in from a new terminal to test, and close the old session only when that works.

sudo sshd -t && sudo systemctl reload sshd
sudo sshd -T | grep -Ei 'permitrootlogin|passwordauthentication'

If you move SSH to another port, label the port for SELinux (above) and open it in firewalld, or sshd will fail to start or be unreachable.

Crypto policies

One setting controls which protocols and algorithms OpenSSL, GnuTLS, NSS, OpenSSH, Libreswan and other libraries accept:

update-crypto-policies --show       # DEFAULT
sudo update-crypto-policies --set FUTURE    # stricter; then reboot

DEFAULT is a sound choice for most servers. FUTURE rejects more older algorithms and may break connections to old clients. Avoid LEGACY except as a short-term fix for one old system, and prefer a narrow custom sub-policy in /etc/crypto-policies/policies/modules/ over weakening the whole machine. Setting the FIPS policy alone does not put the system in FIPS mode.

Auditing with auditd

The audit daemon records security events (logins, sudo use, changes to watched files) in /var/log/audit/audit.log. Red Hat ships ready-made rule sets in /usr/share/audit/sample-rules/. A common starting point:

cd /usr/share/audit/sample-rules
sudo cp 10-base-config.rules 30-stig.rules 31-privileged.rules 99-finalize.rules /etc/audit/rules.d/
sudo augenrules --load
sudo auditctl -l | head

Read each file before you copy it; the sample rules are pieces of a policy, not meant to be used all at once. Use service auditd restart rather than systemctl for auditd: Red Hat documents service as the correct way, with systemctl only for enable and status. Search and report:

sudo ausearch -m USER_LOGIN -ts recent -sv no     # failed logins
sudo aureport --login -i

File integrity with AIDE

sudo dnf install aide
sudo aide --init
sudo mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
sudo aide --check

Red Hat recommends checking at least weekly and ideally daily, for example with this line in /etc/crontab: 05 4 * * * root /usr/sbin/aide --check. After planned updates, run sudo aide --update, review the report, and rename the new database the same way. Keep a copy of the database on another system.

For a formal baseline (CIS, DISA STIG, PCI DSS), RHEL's OpenSCAP tools and the scap-security-guide content can scan the system and generate fixes; see the Security hardening guide below.

Optimizing: TuneD first

The old advice was to rebuild packages with special compiler flags. Do not. Red Hat's packages are already built well, and rebuilt ones lose support and security updates. Tune the running system instead, and measure before and after.

TuneD applies a profile of kernel, disk, CPU and network settings. The installer picks one automatically (for example virtual-guest for a VM):

tuned-adm active
tuned-adm recommend
sudo tuned-adm profile throughput-performance
tuned-adm verify
Main TuneD profiles
ProfileUse it for
balancedgeneral use; trades some speed for lower power
throughput-performancephysical servers: file, web, build hosts
latency-performance / network-latencylow-latency services; disables power saving
network-throughputbulk network transfers; bigger network buffers
virtual-guest / virtual-hostVMs and KVM hosts
powersavemaximum power saving

To change one setting, make a child profile instead of editing system files. On RHEL 10 it goes in /etc/tuned/profiles/myserver/tuned.conf (on RHEL 9, /etc/tuned/myserver/tuned.conf):

[main]
include=throughput-performance

[sysctl]
vm.swappiness=5

Activate it with sudo tuned-adm profile myserver. Other quick wins: give databases enough RAM rather than more swap, put write-heavy data on SSDs, and look at top, vmstat 5 and iostat -x 5 (package sysstat) to find the real bottleneck before changing anything.

Checklist

The original 2001 book (version 1.3) is kept by the LDP at tldp.org. It describes Red Hat Linux 6.x and is of historical interest only.

Sources