Red Hat Enterprise Linux System Hardening Guide
Introduction
Hardening a Red Hat Enterprise Linux (RHEL) system means setting it up to be as secure as possible by minimizing its vulnerabilities and sticking to good security practices. Often, when you first install an operating system, it comes with basic settings or software that aren’t the best for security. By using a solid hardening process, admins can fix common security weaknesses and meet guidelines like CIS or DISA STIG. This guide walks you through hardening RHEL , including Red Hat's advice and tips from the community. We’ll cover essential topics like minimal installations, managing services, keeping the system updated, setting up cryptographic policies (including FIPS mode), using SELinux, disk encryption, configuring the firewall, auditing, SSH hardening, and managing user privileges. We’ll also look at possible trade-offs, like how security could affect performance or compatibility, and how to check your compliance with OpenSCAP security profiles.
Note: This guide lays out some solid steps for hardening your system based on common best practices. But keep in mind that every setup is unique, so you might need to tweak things a bit to make sure everything keeps working smoothly. It’s a good idea to test out changes in a safe environment first, and take a logical approach to make sure your security measures don't get in the way of how your system runs.
1. Installation and Initial Configuration
Good security starts when you first set things up. If you keep your installation simple and organized, and make sure to apply any updates right away, you're setting yourself up for a more secure system.
1.1 Disk Partitioning and Mount Options
Separate Critical Mounts: For bare-metal installations, plan a partition scheme that isolates certain directories into separate partitions. Red Hat recommends using separate partitions for , , , and on physical machines. This provides both security and resilience benefits:
: Contains the bootloader and kernel. It should not be encrypted, because the system must read it at boot before decryption is available. Keep it separate so that if the OS root filesystem is encrypted or unavailable, the boot files remain accessible for startup.
: Isolating user data in prevents users from filling up the root filesystem and making the OS unstable. It also makes upgrades easier (user data can remain intact when reinstalling or upgrading the OS) and offers a measure of protection if the root () partition is corrupted. Frequent backups of are advised.
and : These hold temporary files. If they reside on the root partition and an application floods them with data, it could consume all space and crash the system. Placing them on separate partitions (with their own size limits) containing the damage to the overall system. In virtual or cloud environments where disk sizes can be adjusted on the fly, separate partitions are less critical, but you should still monitor usage and enforce quotas as needed.
Mount Options: For each of these partitions, use restrictive mount options to harden them: , , and wherever applicable. These options respectively prevent device files, set-user-ID executables, and direct execution of binaries on those mounts. For example:
Mounting with will “deny binary execution from , disable any binary from gaining elevated privileges via SUID, and prevent creation of device files in ”. This thwarts many common exploits that rely on writing malicious executables or device nodes to world-writable directories.
Similarly, and the shared memory filesystem should be mounted with . For instance, add lines to like:
Mount as read-only () after system updates (except when installing new kernels) to protect its contents. For example, in :
Consider hardening: Mounting the filesystem with the option will hide process details from users who do not own those processes. This means users can only see their own processes, not those of other users, which prevents information leakage via . To do this, add to :
Encrypt Partitions: During installation, RHEL offers an option to encrypt partitions. Enabling this provides confidentiality for data at rest: you supply a passphrase that decrypts an encryption key used to secure the partition’s contents. It is strongly recommended to encrypt sensitive data partitions (and swap space) on laptops or any system where an attacker might get physical access to the drives. We cover Linux Unified Key Setup (LUKS) disk encryption in detail in a later section, but it’s easiest to implement at install time for the root filesystem or any planned data volumes.
1.2 Network Configuration During Install
If possible, perform OS installation in a controlled network environment or offline. The RHEL installer media may not have the latest security patches (since it’s a snapshot from the release date), so connecting it directly to the Internet during install could expose the system to vulnerabilities that have since been discovered. The safest choice is to use the “No network” zone or otherwise keep the machine offline until you can complete installation and then immediately apply updates. If you must attach to a network for package retrieval, prefer a trusted local network or repository mirror over a direct Internet connection.
Additionally, consider enabling UEFI Secure Boot (if supported and not already on) and setting a BIOS/firmware password. Secure Boot ensures the system only runs bootloaders and kernels signed by trusted keys, mitigating some boot-level malware. A firmware password prevents unauthorized changes to boot order or booting from removable media.
1.3 Minimal Package Installation
It is best practice to install only the software packages needed for the system’s purpose – each additional program can introduce potential vulnerabilities. During a custom DVD or Kickstart installation, select the minimal environment or explicitly deselect unneeded package groups. On RHEL 8, the “Minimal Install” option provides a very stripped-down system. You can always add packages later if required, but it’s much harder to securely remove or disable every component of an unnecessary package that was installed by default. In the words of the Practical Guide, “do not install unnecessary or unstable software” – this reduces the attack surface significantly.
If you have a base install that included more than needed, identify and remove packages that are not required. For example, on a server, you likely don’t need desktop environments, office suites, or development tools. Use commands like or to review extraneous software. Removing compilers or interpreters on production servers (or restricting their use) can also reduce risk; an attacker who gains access cannot easily build or run new code if tools like or are absent or locked down. If you don’t need compilers, don’t install them – they could be leveraged by an attacker, and they also represent additional code that might have its own vulnerabilities. (One method is to uninstall them or change their permissions to only be usable by an admin group.)
1.4 Post-Installation Updates and Patches
Update Immediately: After the OS is installed, your first step should be to update all packages to pick up the latest security fixes. New RHEL installs might be several minor versions behind. Update the system by running:
(or the equivalent , as both are supported on RHEL 8). This ensures you’re not running software with known vulnerabilities that have been patched in newer releases. Red Hat’s guide explicitly lists “Update your system” as the top post-installation step. If the system has Internet access, make sure it’s registered to Red Hat Subscription Manager or has access to a local Satellite/Repository so that updates can be obtained. For critical systems, you might also consider enabling automatic updates (with the package) or at least automated notifications of available patches.
Reboot after a kernel update or any update that recommends a restart. Keeping the system fully patched is an ongoing process (see Section 9 on maintenance and auditing), but getting to a fully patched state before hardening further is important.
1.5 Enable the Firewall Service
RHEL 8 comes with the firewalld service, which by default is enabled and starts on boot in most installation scenarios. However, if a custom Kickstart or admin action disabled it during install, be sure to turn it back on now. The RHEL security guide notes that even though firewalld is enabled by default, it might have been explicitly disabled in some cases. Use the following commands to ensure the firewall is active and persistent:
These commands start the firewall now and enable it across reboots. We will configure firewall rules in detail in Section 6, but at minimum, having it running in its default (mostly restrictive) state is preferable to no firewall at all. By default, firewalld’s “public” zone blocks incoming connections except a few basic services (like DHCPv6 client). You can verify the firewall status with:
If firewalld is not desired, ensure that an alternative firewall (like a static or configuration) is in place. Generally, using firewalld on RHEL 8 is recommended for its simplicity and integration.
1.6 Disable Unneeded Services
Reducing the number of running services minimizes potential entry points for attackers and saves system resources. After installation and updates, review which services are enabled (set to start on boot) and which are currently running. You can list active services with:
or to see all services enabled to start at boot:
For a quick check of active system services, Red Hat suggests using .
For each service, ask if it’s needed for the system’s role. For example, on a server without a printer, the cups service (printing system) is unnecessary and should be disabled. To disable a service and prevent it from starting at boot:
For instance, will turn off the printing service. Similarly, consider disabling or masking:
Bluetooth (bluetooth.service) on a server.
Avahi (avahi-daemon.service) if multicast DNS is not needed.
RPC portmapper (rpcbind) if NFS or related services are not in use.
DNS server (named) if this system isn’t a DNS server.
DHCP server (dhcpd) if not serving DHCP.
Any database, web server, or mail server software that got installed but isn’t required.
It’s also advisable to remove any leftover initialization scripts or compatibility services you don’t need. For example, RHEL 8 uses , so classical init scripts are not present by default; but if you installed something that uses an older service script, ensure it’s not enabled via or similar. The command can show legacy services, but on RHEL 8 this is mostly for compatibility with older sysv init scripts.
Check Listening Ports: As a complement to reviewing services, check open network ports. Use to list processes listening on TCP/UDP ports. Every open port is an avenue into the system; shut down or firewall any that shouldn’t be available. For example, if you see a service listening on port 10000 and you don’t recognize it, investigate and likely disable it. Tools like nmap (scanning from outside) or netstat (similar output to ) can also be used.
Special Case – Unconfined Services: On RHEL, if SELinux is enabled (which it is by default; see Section 3), most system services run in confined domains. If you find a process running in the SELinux domain, it means it’s running unconfined by SELinux policy (often because it’s not a standard service). The Practical Guide recommends ensuring “no daemons are unconfined by SELinux”. You can check for such cases with:
This lists processes with SELinux context . If any correspond to a service you need, consider writing or installing an SELinux policy module for it so it can run in a confined domain, or find a more appropriate mechanism to run it. Ideally, all long-running services should be properly confined under SELinux for defense in depth.
1.7 BIOS and Bootloader Security
BIOS/UEFI Password: Set a firmware password to prevent unauthorized users from altering the boot device order or entering firmware setup to disable security features. This helps ensure an attacker can’t boot the machine from external media to bypass the OS security.
GRUB2 Bootloader Hardening: Protect the GRUB bootloader with a password to prevent malicious users with console access from editing boot parameters (which could be used to disable SELinux or boot into single-user mode without a password). In RHEL 8, you can set a GRUB2 password using the utility, which prompts for a password and then configures . For example:
This will create an encrypted password entry in . After setting it, GRUB will ask for this username/password if someone attempts to edit boot entries at startup. Ensure the generated GRUB config is owned by root and not world-readable:
This locks down the bootloader config files’ permissions.
Kernel Options: Even with GRUB locked, it’s worth reviewing kernel boot options in . Make sure no insecure options are present. For instance, ensure there is no or on the kernel command line (these would disable SELinux). The Practical Guide explicitly warns to remove from if present. After any change, rebuild the GRUB config (). Also consider adding if you are certain your environment does not use IPv6 – this would disable the IPv6 stack from boot time, eliminating any IPv6-related attack surface. (Be cautious: disabling IPv6 can affect services that expect a listening socket on ::1 or any IPv6 features.)
Secure Boot: If your system supports UEFI Secure Boot and it’s enabled, the bootloader and kernel must be signed by a trusted certificate (such as Red Hat’s keys). Using Secure Boot can prevent unauthorized boot code, but if you are running custom kernel builds or certain third-party drivers, ensure they are signed or Secure Boot is appropriately configured.
In summary, at the completion of initial install and configuration, you should have: a minimal RHEL system, fully updated, with only necessary services running, appropriate partitions with secure mount options, and baseline protections on boot and network. The next sections will build on this foundation to configure specific security controls in more detail.
2. System Services Hardening and Process Security
Beyond just turning off unnecessary services, it’s important to harden the configuration of the services that you do need. This section covers strategies to secure system services and the OS processes.
2.1 Managing System Services with Systemctl
RHEL 8 uses systemd as its init system. Become familiar with to manage services. Key commands include:
– shows a service’s status and recent logs.
– starts a service and enables it at boot (replace with separate and commands if desired).
– stops a service and prevents it at boot.
– completely prevents a service from being started (even manually or if a dependency tries to start it). Use this for services you are sure you never want to run. Masking sets the service’s unit file to point to .
Ensure that critical services like firewalld, auditd, etc., are enabled (they usually are by default on RHEL, but double-check). Conversely, services that pose security risks if misconfigured (e.g., FTP servers, telnet, rsh, NFS servers, etc.) should be off unless explicitly required. If they are needed, consider alternatives or ensure they are properly locked down (for instance, prefer SFTP over FTP, or SSH over telnet/rsh).
2.2 Systemd Service Security Features
Systemd offers a number of options to sandbox services. For built-in services, many of these options are already employed by Red Hat. It’s useful to know them if you run custom services or want to further tighten things:
PrivateTmp: This option gives a service its own isolated directory (under ) so that it cannot see or interfere with other processes’ temp files. Many daemon unit files in RHEL have by default (e.g., for Apache httpd).
ProtectSystem and ProtectHome: mounts system directories like and as read-only for the service, and hides , , and from the service (unless needed). This can prevent a compromised service from tampering with system files outside its scope.
CapabilityBoundingSet and NoNewPrivileges: These directives can drop Linux capabilities from the service process, ensuring it cannot perform actions beyond its intended scope (even as root). For example, a web server likely doesn’t need the capability to reboot the system or load kernel modules. RHEL unit files often already drop some capabilities. prevents the service or its children from gaining any new privileges via (even if an executable has SUID/SGID).
User and Group: Running services as non-root where possible. Many services are configured to run as a less-privileged user (like or ). If you create your own systemd service for a custom application, include a line like (and optionally ) to drop privileges. If the service doesn’t absolutely need root, don’t run it as root.
ProtectKernelTunables, ProtectKernelModules, ProtectControlGroups: These newer settings (if available in RHEL 8’s systemd version) restrict the service’s ability to alter kernel settings, load modules, or manipulate cgroups. These should be enabled (set to ) for most services that don’t need such access.
As an example, if you had a custom service , you could create a drop-in file (e.g., ) with:
This would isolate its , make root filesystem read-only, disallow gaining privileges, and remove the ability to reboot or load modules (two capabilities the service likely doesn’t require). Keep in mind that misconfiguring these can break a service – always test after adding restrictions. System logs () will show if the service fails due to denied permissions.
2.3 Resource Limits and DoS Protection
Set resource limits for processes to prevent abuse or accidents that consume excessive resources (which could be a vector for denial-of-service). This can be done via ulimit/PAM limits or systemd cgroups:
PAM limits.conf: For user-run processes, configure or files in . For example, to limit core dump size and number of processes:
Systemd cgroups: Every service unit is a cgroup, and you can assign CPU, memory, and I/O limits. For instance, in a service unit limits it to 1GB RAM, preventing a runaway service from consuming all memory. could limit it to half a CPU in heavy load scenarios. Use these if you have multi-tenant environments or critical services that must not starve others.
Avoiding core dumps: Unless you are actively debugging, disable core dumps system-wide (as above with ). Core dumps of sensitive processes could capture passwords or keys in memory at crash time, which is a security concern. In RHEL, you can also set in to avoid storing core dumps via systemd-coredump.
SymLink and FIFO restrictions: A common hardening is to enable kernel protections against malicious symlinks in world-writable directories. On modern kernels, sysctls like and are enabled by default (they mitigate TOCTOU attacks via ). It’s worth verifying these are set:
2.4 Disable SMT (Hyper-Threading) if Not Needed
On Intel CPUs with Hyper-Threading (Simultaneous Multi Threading - SMT), there have been vulnerabilities (L1TF, MDS, etc.) where one thread could potentially snoop data from another running on the same core. RHEL’s security guidelines mention disabling SMT to mitigate such CPU vulnerabilities. If your workload and performance profile permit, you might consider disabling SMT for security. This can be done in BIOS (preferred, truly turns off the second thread per core) or at the OS level. RHEL 8’s Web Console (Cockpit) provides a way to disable SMT under Hardware->System information (it toggles the kernel parameter). You can also add to the kernel command line via and rebuild grub.cfg, or echo to (but doing it at boot is better).
Trade-off: Disabling SMT will “lower the system performance” in threaded workloads – roughly a 20-30% performance hit for CPU-bound tasks that benefited from hyper-threading. However, it may be worth it in high-security environments or multi-tenant systems where you don’t fully trust code running on one thread vs. another. Evaluate based on your threat model: for a single-tenant dedicated server, leaving SMT on might be fine; for a shared compute environment, consider disabling it to close those side channels.
2.5 Time Synchronization Security
Use NTP or chrony (the default in RHEL 8) to keep system time accurate, which is crucial for logs, authentication (think Kerberos or 2FA timing), certificate validation, etc. In terms of hardening:
Ensure chronyd is running (enabled by default). Configure it to use trusted NTP servers. If the server is in a DMZ, use internal NTP sources to avoid exposing UDP port 123 unnecessarily.
You might consider enabling NTP authentication (so the time updates are signed by a trusted key), to prevent any tampering with time via man-in-the-middle. This is rarely done in practice unless in a closed network with control over NTP infra.
If this system itself should not be an NTP server, confirm chrony is not configured to allow others to sync from it (the default in RHEL allows only localhost control).
In summary, for services and processes: keep only what you need, run them with least privilege, use systemd’s security features and kernel sysctls to contain them, and be mindful of how resources and CPU features are used for possible attack vectors.
3. System-Wide Cryptographic Policies and FIPS Mode
Cryptographic policy is a mechanism in RHEL 8 that allows administrators to easily set the allowed cryptographic algorithms and protocols for the system in a centralized way. This affects libraries and applications that honor the system policy (OpenSSL, GnuTLS, NSS, Kerberos, OpenSSH, etc.), ensuring a consistent security level.
3.1 Using System-Wide Crypto Policies
RHEL 8 introduced system-wide cryptographic policies to simplify configuring crypto across various services. Instead of manually editing config files for each application (SSL/TLS libraries, etc.), you can apply a policy profile. The default profiles available are: DEFAULT, LEGACY, FUTURE, FIPS, and (for RHEL 9 onwards) STRICT. On RHEL 8, the main ones are:
DEFAULT: A balanced, modern policy that meets general security standards. For example, DEFAULT in RHEL 8 disallows SSLv2/3, TLS1.0, and weak ciphers like DES, while allowing TLS1.2 and strong ciphers. It is the out-of-the-box setting.
LEGACY: Allows older, less secure protocols and algorithms (for compatibility with legacy systems). For instance, LEGACY might enable TLS1.0 and certain older hashes or shorter RSA keys that DEFAULT forbids. Use this only if you have no choice – it weakens security (the name itself implies it’s for backward compatibility).
FUTURE: A stricter policy that anticipates future security requirements – it disables some algorithms that are currently considered secure but might become weak in the near future. For example, FUTURE might require TLS1.3 (and TLS1.2), drop some less preferred curves or ciphers, and increase minimum RSA/DH key sizes. This is useful if you want to harden crypto beyond the default, “future-proofing against emerging threats”. Be aware that some clients/servers might not yet support these stricter settings.
FIPS: Aligns with FIPS 140-2 requirements. This policy allows only FIPS-approved ciphers and protocols. For example, MD5 and SHA-1 (except in HMAC) are disallowed, as are protocols below TLS1.2. FIPS mode also requires the system’s cryptographic modules to run in FIPS-approved mode (including a specific kernel flag and dracut modules). We’ll discuss enabling full FIPS mode below.
To view the current policy, run:
By default, this will output on a standard RHEL 8 system. To change the policy, use:
e.g., to switch to the FUTURE policy. The tool will adjust system configurations accordingly. Note that if you have any services currently running, they may need a restart to fully pick up the new policy. The system will remind you to reboot or restart services to apply changes to running processes (crypto libraries typically read policy at initialization).
Custom Policies: If needed, you can create custom crypto policies by defining a policy file and using . For example, you could start with DEFAULT and exclude certain algorithms by creating a policy string like (this is a hypothetical syntax to remove SHA-1). The RHEL documentation provides a way to “adjust the set of enabled cryptographic algorithms” via subpolicies. In practice, you might not need custom policies unless you have a very specific requirement. Common tweaks, like disallowing SHA-1, can be done as:
Which would modify the DEFAULT set by excluding the SHA-1 hash algorithm.
Application Exceptions: In rare cases, you have an application that cannot work with the system policy (for example, an old Java app that insists on a now-disabled cipher). You can opt specific applications out of the system policy and manage their crypto settings independently. Typically this is done by editing that application’s config (like Java’s file or OpenSSL config) to not use the system policy. RHEL’s guide has a section on “excluding an application from following system-wide policy”. The better approach, if possible, is to update or fix the application so it can comply with modern policies rather than weakening the whole system or granting exceptions.
3.2 Enabling FIPS Mode
FIPS 140-2 is a U.S. government standard for cryptographic modules. RHEL 8 is designed such that it can operate in a FIPS-compliant mode. Enabling FIPS mode will restrict the cryptographic algorithms to those approved by FIPS 140-2 and trigger self-tests at startup of crypto modules.
To enable FIPS mode on RHEL 8:
Install FIPS modules: Ensure the dracut-fips package is installed. In RHEL 8, the script will handle this.
Run as root: This command performs several actions automatically: it installs the necessary dracut modules for FIPS and regenerates the initramfs, and it adds the kernel boot parameter to the GRUB configuration. It may also set up a flag file indicating FIPS is enabled.
Reboot the system: The changes take effect on next boot (the kernel will see and the crypto modules will run in FIPS mode). After reboot, you can verify FIPS mode by checking:
What does FIPS mode do? Aside from enforcing the crypto policy “FIPS”, it causes kernel and user-space crypto libraries to operate with FIPS-approved algorithms only. For example, OpenSSH in FIPS mode will not permit the use of the RSA SHA-1 signature algorithm for host or user authentication since SHA-1 is not FIPS-approved for digital signatures. It will also disable non-approved cipher suites (like any using RC4, MD5, etc.). Protocols like SSLv3/TLS1.0 are not allowed. The RHEL docs note that “communication protocols supporting cryptographic agility do not announce ciphers that the system policy disallows” when in FIPS mode – meaning a server will not even offer them.
Warnings / Compatibility: Enabling FIPS mode can break compatibility with older clients or servers. For instance, if you need to connect to a device that only supports SSH with an old cipher or TLS 1.0, a FIPS-enabled RHEL will refuse. Also, any custom or third-party application that calls into crypto libraries might encounter errors if it requests a now-disabled algorithm. Test your important apps after enabling FIPS. If certain things fail, you might need to adjust (e.g., update the other side, or if not possible, consider not enabling full FIPS mode but instead just the strict FUTURE policy). Red Hat also emphasizes that the system’s crypto libraries must themselves be FIPS certified – which they are in RHEL’s case, provided the system uses Red Hat’s builds. If you use a non-vendor cryptographic module, it might not be FIPS certified (which could be a compliance issue in some environments).
If you later need to disable FIPS mode, you can run and reboot.
3.3 OpenSSH and Crypto Policy
OpenSSH on RHEL 8 will follow the system crypto policy for its ciphers, MACs, and key exchange algorithms. This means if you set the system policy to FUTURE or FIPS, SSH will automatically avoid disallowed algorithms (for example, in FIPS mode, SSH won’t use the default curve25519 key exchange because that algorithm isn’t FIPS-approved – it will stick to diffie-hellman-group14-sha256 or similar). This integration is very useful – it means by setting the system policy once, you’ve effectively hardened SSH, TLS libraries, Kerberos, etc., in one stroke.
One thing to note: if you want to see what ciphers/MACs SSH is using under the hood with the current policy, you might run or check . Red Hat’s implementation hooks into the crypto policy by generating an OpenSSH config snippet that lists allowed ciphers/MACs. If you need something non-standard for SSH only, you could override it in , but that should rarely be necessary if policies are set appropriately.
3.4 TLS/SSL Settings for Apache, NGNX, etc.
When using web servers or other TLS services on RHEL 8, if they use OpenSSL or NSS, they typically also follow the system crypto policy. For example, Apache httpd mod_ssl uses OpenSSL – it will automatically disable protocols/ciphers per the policy. That said, it’s always good to double-check the application’s config after changing policies. For instance, ensure your Apache isn’t manually enabling something weak (the default is often which is okay). If system policy is FIPS, Apache will only allow TLS1.2 anyway.
Similarly, tools like (which use libcurl/OpenSSL) and libraries like GnuTLS will all inherit the system settings.
3.5 Verification and Testing Crypto
After setting a policy or enabling FIPS, test your services:
Use OpenSSL to attempt connections with various protocols. e.g., to see if TLS1.0 is indeed rejected.
For SSH, try using a disallowed algorithm (like if you put SSH in FIPS mode which disallows SHA-1 RSA, you should see it refuse).
Check logs (for example, or application logs) for any errors loading ciphers or keys.
In logs, if an application tries to use a disallowed cipher, you often see an error like “Algorithm X is not available” or “failed to initialize cipher suite Y”. That’s a clue that either the policy blocked it or something is misconfigured.
3.6 Summary of Crypto Hardening
By using system-wide cryptographic policies, you gain a centralized and consistent way to enforce strong encryption. Stick with at least the DEFAULT policy on all systems; consider FUTURE for internal systems where you control all clients/peers and want extra security. Use FIPS mode if you are required to by compliance (government systems, etc.) or if you want the assurance of validated crypto modules and algorithms only. The trade-off with stricter policies is always compatibility – so weigh the need. RHEL’s default settings are chosen to be secure yet broadly compatible; deviating from them should be done with understanding of the implications.
In the next section, we will delve into SELinux – another critical component of RHEL’s security posture that works alongside these crypto policies to protect the system.
4. Mandatory Access Control with SELinux
Security-Enhanced Linux (SELinux) is a cornerstone of RHEL security. It implements Mandatory Access Control (MAC), confining processes and users to only the resources (files, sockets, etc.) they are permitted to access according to a security policy. Unlike traditional discretionary access controls (file permissions owned by users), SELinux can enforce system-wide policies regardless of file ownership.
By default, SELinux is enabled and in enforcing mode on RHEL 8. The policy used is the targeted policy, which mainly confines network-facing services (daemons) and treats normal user processes as unconfined (they run in a context that SELinux does not restrict beyond standard Linux permissions).
4.1 Ensuring SELinux is Enabled and Enforcing
You should verify SELinux is not inadvertently turned off. Check:
This should return “Enforcing”. If it says “Permissive” or “Disabled”, SELinux is not providing protection. Also inspect – it should have:
If SELINUX is set to permissive or disabled, change it to enforcing (permissive means SELinux is logging policy violations but not enforcing them; disabled means SELinux is completely off, which is not recommended). You will need to reboot if it was disabled to properly enable it.
Ensure also that the kernel command line does not have which would override this and completely disable SELinux at boot. (As mentioned earlier, remove any such flag from GRUB config.)
Rationale: “Disabling a major host protection feature, such as SELinux, at boot time prevents it from confining system services... and increases the chances it will remain off during system operation.” SELinux provides an additional layer of security that can mitigate the impact of a 0-day exploit or misconfiguration by confining what a compromised service can do. It should always be enabled on production systems unless you have a very specific reason and equivalent controls in place.
4.2 SELinux Policy Basics
The SELinux policy is a large set of rules describing how subjects (processes, users) and objects (files, ports, etc.) can interact. Every process and object in the system has an SELinux context (a label) composed of user, role, type, level. The most important field for targeted policy is the type (often called the domain for processes). For example, the Apache web server runs as type , BIND DNS runs as , etc. Files have types like for web content, for logs, etc. The policy says processes can read files of type , but cannot, say, read type (even if file permissions would allow it).
For the average admin, the main tasks are:
Keep SELinux enabled (Enforcing).
Fix any SELinux denials properly rather than turning it off.
4.3 Handling SELinux Denials (Audit Logs)
When SELinux blocks an action, it logs an “AVC denial” to (and sometimes to ). These logs will show the source context (process) and target, and a reason code. For example:
This indicates the Apache process () tried to write a file labeled (which might be a user’s home directory file). By policy, isn’t allowed to write files in home directories, so it was denied.
To address SELinux denials:
Determine if the access should be allowed. In the above example, maybe your web server should be able to write that file. If so, the file is mislabeled – the solution would be to give it a type httpd can write to (e.g., , and better, use to make it persistent and ). You choose an appropriate type that httpd_t is allowed to write (SELinux documentation or suggestions can help).
If the access should not be allowed truly, then the denial is SELinux doing its job – no action needed except possibly informing whoever tried that action that it’s not permitted.
Never remove or loosen protections unless necessary. A common mistake is to see an SELinux denial and quickly add a rule to allow it without understanding it. This could undermine security.
Use the audit2why and audit2allow tools (from package) to analyze denials:
will read recent AVCs and explain common solutions.
suggests a policy module if you want to allow all those logged denials. Instead of immediately applying it, inspect it to ensure you’re not over-granting permissions.
Often there are SELinux Booleans that tune policy for common use cases. For example, if Apache needs to send emails, instead of creating a custom rule, you can set (with ). Booleans allow toggling certain access without writing new policy. List booleans with or use . Documentation (e.g., ) lists booleans relevant to each service. Use booleans rather than custom rules whenever possible, as they are part of the supported policy and receive testing by Red Hat. Common ones: , , , etc. If a web server needs database access, is the right way (rather than turning off SELinux or writing a broad allow rule).
If you have a custom application that SELinux is blocking, the ideal approach is to write a custom policy module for it. This can be done by generating a policy template via audit2allow or from scratch. Red Hat’s documentation has sections on “Writing a custom policy for a custom application”. It involves using the package, writing a .te (policy source) and .fc (file contexts) file, then compiling with /. While this is beyond our scope to fully detail here, be aware that this is the method to cleanly integrate an in-house app into SELinux’s protection.
4.4 SELinux Context Management
Ensure that key directories and files have correct SELinux context (labels). If you create new directories or move things around, use to relabel according to policy defaults. For example, if you add a new virtual host directory for Apache under , you’d want to label it so Apache can read it. You could do:
This adds a persistent file context pattern and applies it.
System contexts: Some special files should always retain their context, like (shadow_t), SSH keys ( for host keys). The default file contexts provided by the policy cover standard locations. Running or will force a full relabel if things get messy. That is a heavy-handed fix, though, usually not needed unless many labels were wrong.
No Unconfined Daemons: As mentioned, processes running in context are a sign something is not properly confined. The CIS benchmark recommends ensuring no processes run in except some trivial ones. After boot and normal operation, run:
This filters out a few that might appear (like the greps themselves or transitional processes). If anything shows up, investigate. Perhaps you have a third-party service that wasn’t shipped with a policy. Consider confining it.
Uninstall mcstrans (optional): The service is for translating SELinux contexts with MLS/MCS levels into human-readable form. It’s not commonly needed unless using MLS (multi-level security). Some hardening guides (like CIS) suggest removing it to reduce code running. Red Hat’s note is that this is not a security necessity but more of a cleanup. You can remove it with:
This will not disable SELinux – it only affects how context fields might be displayed (now you’d see raw sensitivity labels like instead of numbers). It’s low-risk to remove if not needed, but also low-impact if left installed but not running.
4.5 SELinux and Containers/VMs
If running containers (e.g., Podman, Docker) on RHEL, be mindful of SELinux options like to ensure containers are confined (containers run in SELinux type by default). Don’t turn off SELinux for containers ( on host) – instead, use the or mount options to label volumes for container use (if you need to share host directories). Container runtimes in RHEL are SELinux-aware and allow a strong boundary between containers and host when properly labeled.
If this RHEL is a VM host or guest, note that SELinux in the host can confine the QEMU/KVM process (type ). This is usually automatic via libvirt – each VM’s resources get a unique label to isolate them. Just ensure not to disable those by fiddling with virt_use_* booleans without knowing the effect.
4.6 Summary
Keep SELinux Enforcing, understand denials as they occur, and address them by fixing labels or toggling booleans rather than turning off SELinux. This system service runs in the background and might be “silent” when all is well, but it is actively protecting your system. It can limit what an attacker who compromises one service can do – for instance, if Apache is compromised, SELinux can prevent it from reading SSH keys or modifying user home directories, significantly limiting damage. SELinux is a powerful safeguard in RHEL; embracing it rather than viewing it as a nuisance will pay off in security resilience.
5. Storage and Disk Encryption
Encrypting sensitive data at rest ensures that if drives or backups fall into unauthorized hands, the information remains protected. RHEL supports full-disk encryption via LUKS (Linux Unified Key Setup) and provides options for both local passphrase-based encryption and network-bound encryption for automated unlocking. In this section, we cover setting up LUKS, using TPM or network (Tang) for unlocking, and other storage hardening tips.
5.1 LUKS Disk Encryption Overview
LUKS is the standard encryption layer on Linux for block devices (disks, partitions). RHEL 8 uses LUKS2 by default (which has some enhancements over LUKS1, like metadata redundancy and support for encryption algorithm agility). When you encrypt a disk with LUKS, it turns the block device into a locked container that can only be accessed by providing a valid passphrase/key, which then decrypts a master key that actually encrypts the data. Once unlocked and mounted, usage is transparent to applications.
Benefits of disk encryption:
If a disk or laptop is lost or stolen, the data is inaccessible without the key.
If someone tries to mount the drive on another machine, they cannot read it.
Even within the running system, encryption can enforce an extra credential requirement for mounting certain data.
In RHEL’s security guide’s words: “By using disk encryption, you can protect the data on a block device by encrypting it. To access the device’s decrypted contents, enter a passphrase or key as authentication.”. This emphasizes that encryption ties data access to possession of a secret (passphrase or key file).
What to encrypt: For servers, focus on encrypting partitions with sensitive data (customer data, credentials, databases). On laptops or developer workstations, full disk encryption (except /boot) is common. Even on servers, some compliance standards require encrypting certain filesystems (for example, PCI-DSS might require crypto material to be on encrypted storage).
Remember, as noted earlier, cannot be encrypted (the system must read kernel and initramfs at boot). But you can encrypt , , , etc. The installer supports this easily (LUKS passphrase entered at boot). Post-installation, you can still set up encryption for additional disks or even encrypt an existing partition (though that requires backup and reformat typically).
5.2 Setting Up LUKS Encryption
During Installation: If you planned ahead, you might have encrypted partitions when installing RHEL (Anaconda installer has a checkbox for encryption and prompts for a passphrase). In that case, you’re done – the system will prompt at boot for the passphrase (unless you configured auto-unlock via TPM or Tang, which is advanced and usually post-install configuration).
Post-Installation Encryption: To encrypt a new disk or partition on an existing system, the general steps are:
Choose the device (e.g., ) and ensure it’s empty or you have moved any data off (encrypting will overwrite it).
Initialize LUKS on the partition:
Open the LUKS container:
Create a filesystem on the mapped device:
Mount the filesystem:
Persist the setup: Add an entry in so that the system knows about this encrypted volume for future boots. For example, add:
Encryption Algorithm: By default, LUKS uses AES with XTS mode (and some hash for key derivation). That is secure. You can explicitly specify algorithms if needed (e.g., ). But the defaults in RHEL 8’s cryptsetup are generally FIPS-compliant if FIPS mode is on, and strong.
LUKS Key Management: You can have multiple passphrases or keys (up to 8 key slots). Use to add an additional passphrase (for example, have one key for admin1, another for admin2 or a backup). Also periodically review keys and remove if needed (). This flexibility allows, say, setting a random high-entropy key for normal operations (stored in a secure vault or USB key) and a human-memorable passphrase for emergency access.
5.3 Automating Unlocking: TPM2 and Tang (Network-Bound Disk Encryption)
Typing a passphrase at boot is fine for single servers or laptops, but in a data center with many servers it’s not scalable. RHEL provides Policy-Based Decryption (PBD) techniques to automate LUKS unlock while still keeping keys secure:
TPM 2.0 Unlock (Clevis TPM2 pin): If the system has a Trusted Platform Module (TPM 2.0 chip), you can seal the LUKS passphrase in the TPM such that only that machine’s TPM can retrieve it (and only if certain system states are true, depending on PCR configuration). Clevis (a tool for automated encryption unlocking) has a tpm2 pin that integrates with LUKS. Essentially, you bind a LUKS slot to the TPM. On boot, Clevis retrieves the key from TPM and unlocks the volume without user intervention. This is secure as long as the platform integrity is maintained (TPM will not release secrets if the boot chain is altered, if configured correctly). A downside is that if the machine’s motherboard/TPM dies, you need recovery keys (so always keep a escrowed passphrase not in TPM).
Network-Bound Disk Encryption (NBDE) with Tang server: Tang is a network service that provides cryptographic binding. The client (Clevis tang pin) can obtain a decryption key from a Tang server if it’s on a certain network. The key is not directly transmitted; Tang uses a challenge-response (with ephemeral keys) so that it never sees your actual LUKS key, but will only provide the decryption material to clients that can reach it. The idea: if your server is in your data center network, at boot it contacts the Tang server and unlocks; if someone steals the disk and boots it elsewhere, it cannot contact the Tang server, thus cannot unlock. Tang is stateless and doesn’t require client identity – security is purely by network presence and possession of the bound data. You can use multiple Tang servers and Shamir’s Secret Sharing (Clevis sss pin) to require, say, any 2 out of 3 servers to respond for unlock (for high availability and some trust distribution). The RHEL docs provide an NBDE architecture where clevis+Tang allows automated unlocking of LUKS volumes in a scalable way.
Setting up NBDE:
Deploy a Tang server: Typically on a secure network segment. It generates its own key pair on install.
Bind a LUKS volume to Tang: On the client (after you’ve setup LUKS and added at least one passphrase), you’d do something like:
Test unlock: On next reboot, if the network is reachable, the clevis unlock client (dracut module) will contact Tang and unlock the volume. If the network is unavailable (or you boot offline), it will timeout and you’d have to enter the fallback passphrase manually.
Red Hat’s guide has diagrams for NBDE; essentially the Tang server holds part of the decryption key and the client holds another part, and only by connecting them does the full key assemble (Tang doesn’t learn the full key by design, and doesn’t store client info). This is a powerful method to manage large scale disk encryption without manual intervention each boot.
PBD and Clevis are beyond simple hardening, but good to mention as part of an advanced hardening scenario, since you can have encrypted servers that auto-unlock when in the right environment, combining physical security (machine in data center) with encryption.
5.4 File System Permissions and Integrity
Encryption doesn’t replace good filesystem permissions and other hardening:
Set appropriate ownership and mode on sensitive files (e.g., your application secrets file should be owned by root and mode 600 or 640 to a specific group).
Avoid using broad permissions like 777 or world-readable on any sensitive directories.
Use chattr +i (immutable flag) on files that rarely change but are critical (e.g., key configuration files) to prevent even root (or accidents) from modifying them without first removing the flag. For example, could be set immutable after hardening, so an attacker who gains root can’t silently alter the SSH config to weaken it (they’d have to notice and first, which might alert an admin).
Implement file integrity monitoring using AIDE (discussed in Section 7) to detect any unauthorized changes to important files/directories.
5.5 Swap and Temp Encryption
If your system uses swap space, consider encrypting swap as well. Unencrypted swap can be a leak for sensitive info (passwords, encryption keys) that get paged out of RAM. The RHEL installer, if you choose disk encryption, usually encrypts swap by default (either by putting it in LUKS or by using an ephemeral encryption with a random key each boot). If not, you can encrypt swap with a random key on boot (so no passphrase needed but contents are protected). This can be done by a crypttab entry like:
This instructs the system to generate a random key for the swap device each boot (so you don’t hibernate to disk in that scenario since you can’t recover the swap content across reboot). A simpler method on modern systems: systemd’s can handle this if configured.
Temporary directories (, ) – if not on separate partitions – could be encrypted in scenarios where the storage medium might be removed. On servers, it’s usually not necessary to encrypt /tmp as it’s cleared on reboot and mostly contains non-sensitive data, but consider your threat model.
5.6 Backups and Off-site Data
Any hardening of disks should extend to backups: if you dump data to tapes or off-site storage, encrypt your backups or use encrypted backup tools. Otherwise, an attacker could target those rather than your live system. Tools like or can encrypt backup files, or use solutions that integrate encryption (e.g., backup software that supports encrypted archives).
In summary, disk encryption adds strong protection for data at rest. RHEL offers robust LUKS encryption and advanced unlock methods. The primary trade-off is the need to manage keys (don’t lose them!) and a minor performance overhead (modern CPUs with AES-NI make encryption overhead negligible for most workloads – a few percent at most). Given the high stakes of data breaches, the slight performance cost is usually worth paying. Always store encryption passphrases/keys securely (in an enterprise, consider a secrets manager or an HSM). Also, test your disaster recovery: can you unlock the data if the system cannot (e.g., Tang server down)? Have break-glass procedures (like knowing the fallback passphrase). With these considerations, disk encryption becomes a seamless part of your security posture.
6. Network Security and Firewalls
Securing network communications involves configuring host-level firewalls, kernel network protections, and safe service configurations. RHEL 8 provides a powerful firewall (firewalld with an nftables backend) and many kernel tunables to guard against network-based attacks. This section covers firewall configuration, TCP/IP stack hardening, and some service-specific network settings.
6.1 Firewalld and nftables
Firewalld is the default front-end for managing firewall rules on RHEL. It abstracts rule sets into “zones” such as public, internal, dmz, etc., each with its own set of allowed services or ports. By default, all interfaces are in the public zone (unless changed), which is fairly restrictive (it allows SSH and a few ICMP types by default, and blocks most other incoming traffic).
Basic usage:
To see the default zone and active rules:
To allow a service or port, use either the service name (services are predefined in firewalld, see ) or specify port/protocol. For example, to allow HTTPS:
To remove/block something:
Change default zone: If your system’s interface is in “public” but you want to define a different set, you could move it to another zone or modify public. E.g., to assign interface ens3 to zone “internal” (which maybe is more open if this host is inside a secure LAN):
Advanced firewalld:
Use rich rules for more granular control (allow or block specific source addresses, logging, rate limiting). For example:
Masquerading/NAT: If this box is a router, you can enable masquerading on a zone to NAT outbound.
Forward ports or port-range: Many possibilities via or rich rules.
Under the hood, firewalld uses nftables (since RHEL 8) to implement these rules. If you prefer not to use firewalld, you can directly write nftables rules (for experts) or use the legacy interface (not recommended to mix with firewalld). Firewalld is dynamic and can adjust rules without flushing everything, which is nice for production changes.
Lockdown mode: Firewalld has a lockdown feature to prevent even root processes from altering firewall rules (except via firewalld D-Bus with proper credentials). It’s usually disabled by default, but for high security, you could enable in . Then define which apps (via polkit) can make changes. This is an advanced hardening that prevents compromised root from simply stopping the firewall – though a truly compromised root could also just poke holes by creating raw sockets. It’s one layer to consider.
Firewall default policy: The default for incoming is deny (except allowed services), which is good. Outgoing traffic is typically allowed by default (firewalld default policy is not to restrict outgoing). For a highly secure environment, you might also restrict outgoing connections (to prevent a compromised service from calling out). This can be done by adding rich rules to reject or allow only certain outbound traffic. However, this adds complexity (you need to know all legitimate outbound needs, like DNS, updates, etc., and permit them).
ICMP: Firewalld by default allows some ICMP like echo-request (ping). You can adjust which ICMP types are allowed (in the zone settings or with ). Blocking ping is a debated topic – security by obscurity vs. legitimate network debugging. Usually allowing ping is fine and helps with diagnostics; it doesn’t expose much. But some prefer to block it to reduce visibility of the host.
Testing firewall: Use from another host to port-scan and ensure only intended ports are open. Also test that services are indeed unreachable from disallowed sources, etc.
6.2 Kernel Network Tunables (sysctl)
The Linux kernel’s networking stack has many parameters that should be adjusted for security. Typically these are set via or better in separate files under . Here are important ones (many of these are recommended by CIS benchmarks):
Disable IP Forwarding: Unless this system is meant to route traffic (act as a router), turn off IP forwarding.
Send Redirects (all and default): If not a router, you shouldn’t send ICMP redirects.
Accept Redirects: You also usually want to ignore ICMP redirects that other devices send, to avoid malicious redirection.
Accept Secure Redirects: There’s a notion of “secure” redirects – from the default gateway only. Best to disable those too unless needed:
Reverse Path Filtering (rp_filter): Enable strict mode reverse path filtering to counter IP spoofing.
Log Martians: Enable logging of packets with impossible addresses (martians).
TCP SYN Cookies: Ensure SYN flood protection is enabled.
IPv6 Privacy: If using IPv6, you might want and to use temporary random addresses for outgoing connections (to avoid tracking via the stable IPv6 address which often includes the MAC). However, that’s more of a client privacy thing.
Source Routing: Disable acceptance of source-routed packets (should be default off).
ARP Restrictions: You might consider and to help prevent ARP spoofing issues if the server has multiple IPs on one interface. This makes the server only respond to ARP for addresses actually configured on the receiving interface and prefer using an address on the network when making ARP requests – reduces chance of answering ARP on the wrong NIC.
Add all the chosen settings to a file like and then run to apply immediately (or individually via ).
6.3 Anti-DDoS and Rate Limiting
Network DoS Mitigations:
The kernel has some tunables for TCP which help in heavy load. For example, increasing backlog queues (, ) can help sustain more connections, and enabling SYN cookies we already did.
There’s also to mitigate TCP TIME-WAIT assassination hazards (minor hardening).
On the firewall side, you can add rate limiting rules with rich rules or direct nftables. For instance, limit new SSH connections to X per minute to thwart brute force:
Prevent malicious packets: Some iptables modules (nftables equivalents) can drop malformed packets (e.g., using the equivalent in nft). Firewalld by default has a rule to drop INVALID state packets. This prevents weird packets from going further.
6.4 SSH Daemon Hardening (Network Entry Point)
SSH is often the primary remote access method, so it deserves special attention (it’s network-facing and a common target). While we cover SSH in its own section (Section 8), in network context:
Ensure SSH is running on a secure configuration (no weak ciphers or MACs, etc. – which is largely handled by crypto policy as discussed, plus explicit config).
Possibly change SSH to listen on a non-standard port as a minor obfuscation (though still use firewall to restrict). If you do, update firewalld to allow that port and remove default ssh service on 22.
Use the firewall to restrict SSH: if you only connect from certain IPs or ranges, enforce that (e.g., allow ssh service only from your admin subnet via rich rule as shown above).
Consider port knocking or single-packet auth schemes if needing an extra layer (outside scope of RHEL default, but there are knockd or SPA implementations).
6.5 TCP Wrappers / Host-Based Access Control
Some services (like SSH, vsftpd, etc.) still can use TCP Wrappers via and . In modern RHEL, xinetd or tcp_wrappers library might not be installed by default (and many services have dropped direct support). It's generally better to rely on firewalld for access control. However, if you do have tcp_wrappers support, you can define rules like in hosts.allow, but note that OpenSSH in RHEL8 is compiled without tcp_wrappers support by default (since it's deprecated). So focus on firewall for host-level allow/deny.
6.6 DNS Security
If the system relies on DNS, ensure points to trusted DNS servers (to avoid poisoning or spoofed responses). If using unbound or a local DNS cache, consider enabling DNSSEC validation. Not directly “hardening the host” but rather ensuring it’s getting valid network info.
Also, if the host runs a DNS server (named), secure it: run it in a chroot by default (the named service on RHEL can run in a chroot jail), enable DNSSEC, limit recursion to internal clients if it’s an authoritative server, etc. That’s more service-specific.
6.7 Services Network Configuration
For network-facing applications:
Ensure they are binding only to necessary interfaces. For example, if a service should only be accessed via an internal network, have it bind to that IP instead of 0.0.0.0. Many server apps let you configure a “listen address”.
Use TLS encryption for any service that supports it (HTTPS for web, secure variants for others). Use strong certificates (if internal CA, ensure it’s not using weak signatures).
Turn off any legacy protocol compatibility. E.g., if running Apache httpd, disable TLS1.0/1.1 in SSL config (on RHEL8, crypto policy might handle it, but double-check).
For web servers, consider headers that increase security (HSTS, X-Frame-Options, etc.) – though that’s more application-level security.
6.8 Monitoring and Logging Network Activity
Configure system logging (rsyslog or journald) to record important network events. For instance:
The kernel will log martians (with our sysctl), check for those.
The firewall can be configured to log dropped packets (but be careful – DoS attacks could spam your logs. If needed, limit rate of logging).
Use tools like auditd to track usage of networking commands by users (we’ll discuss auditd soon). For instance, an audit rule to watch or might catch if someone reconfigures networking.
Test your network hardening: After applying firewall rules and sysctls, perform scans and attempt bad behavior. E.g., run to send spoofed packets and see if rp_filter drops them (you should see no response). Try an SYN flood (in a safe test environment) to see if syncookies protect (the system should still accept some connections). Try connecting to closed ports and see that they are filtered (no response) vs. closed (ICMP unreachable) – filtered is better as it reveals less info.
6.9 Example: OpenSCAP Network Settings Check
Compliance tools like OpenSCAP (with a STIG or CIS profile) will flag many of these settings if not configured. For instance, DISA STIG for RHEL8 requires the above sysctl settings. If you run an OpenSCAP scan after doing this, you should see checks like “Ensure IP forwarding is disabled” and “Ensure source-routed packets are not accepted” as pass, whereas they would fail if defaults weren’t overridden. This is a good way to validate.
At this stage, we have a system that is fairly locked down on the network side: only required ports open, kernel tuned to reject spoofing and redirects, and processes confined to their expected network behavior.
7. Logging and Auditing
Robust logging and auditing are essential for both detecting intrusions and for after-the-fact investigations. RHEL provides multiple logging facilities: systemd-journald, rsyslog, and the auditd subsystem. It’s important to configure and use all of them appropriately.
7.1 System Logging (rsyslog & journald)
By default, RHEL 8 uses to write logs to text files in (e.g., , , etc.), while captures logs in memory and on disk (in the journal). Typically, journald forwards entries to rsyslog which then writes to files.
Ensure logs are persistent: The journal by default stores logs in memory (/run/log/journal) unless you create directory, in which case it stores persistent logs there. RHEL might have done this by default. Check if shows older boot logs (if yes, persistent). If not, you can do:
This makes journald persistent. But since rsyslog is writing to /var/log anyway, you at least have text logs.
Secure log files: Log files in should be readable only by root (or specific groups for some). Ensure permissions are correct (they usually are by default). Example: is 600 (root only) because it contains auth info. might be 640 (root:adm). The group members can read general logs.
Log rotation: RHEL uses to rotate logs (see ). Ensure logrotate is functioning (should be via cron or systemd timer). This prevents logs from filling disk and also archives old logs. The default config usually compresses and keeps logs for a period. Adjust retention as needed for your policies (e.g., keep 1 year of logs if disk permits).
Remote logging: Consider sending critical logs to a remote log server (via forwarding or a service like Graylog/ELK). This is important because if the system is compromised, local logs can be erased. Offloading logs in real-time or frequently to a secure remote store means an attacker can’t cover their tracks easily. Rsyslog is capable of forwarding via RELP or plain TCP/UDP syslog. At minimum, forwarding authentication logs, security events, and audit logs to a SIEM system can significantly improve detection and forensics.
Kernel logging: The output (kernel ring buffer) is also stored in on boot and kernel messages go to or . Ensure those are present.
Time synchronization: Make sure system clock is correct (via chrony as mentioned). Logs with incorrect timestamps are problematic for correlation and legal evidence. Consider using with time-change rules (it has by default) to alert if time is changed.
7.2 Auditd Configuration
The Linux Audit daemon (auditd) provides a way to log security-relevant events from the kernel (syscalls, SELinux denials, process creations, etc.). While system logs cover applications and some system messages, auditd is used for finer-grained auditing – such as tracking file accesses, command executions, and configuration changes. RHEL’s security guide notes that Audit doesn’t directly add security but helps discover policy violations so you can address them.
Ensuring auditd is active: On RHEL 8, auditd should be installed and running by default (enabled at boot). Check with . It’s important that auditd starts early (it typically does, even before regular init, via kernel parameters and initrd). By default, if auditd cannot start, the system might be configured to panic (this is “audit=1” mode for high security, but not default in RHEL out of the box). For most, audit failure just means no auditing (but you can tighten it by adding on kernel command line to make the kernel panic if audit cannot log – useful in highly secure setups where losing audit logs is unacceptable).
Audit rules: The rules are defined in files under or via the older . RHEL supplies a default set of rules in for baseline coverage. There are often commented examples or STIG/CIS provided rules in documentation. You can also load rules on the fly with (for testing) and then make permanent.
Some key things to audit (and often mandated by CIS/STIG profiles):
Audit login events: (Default) Every successful or failed login, as well as session open/close, is audited. These go to with event types USER_LOGIN, USER_ACCT, etc. The PAM system triggers those.
Audit use of privileged commands: Typically, any execution of a program that could alter system security. For example, use audit rules to watch usage of (to change passwords), , , (inserting kernel modules), etc. The STIG has a list of binaries in to audit. Example rule:
Audit file access for critical files: For example, watch , , , , etc., for any changes. This can be done with audit watches:
Audit network configuration changes: e.g., changes to firewall (calls to , or execution of , etc.), and loading of kernel modules. Many default rules cover , syscalls, etc.
Audit privilege escalation: The audit system automatically logs things like use of (because it’s a PAM event and because of exec rules if configured) and calls. For instance, if a process calls the syscall to change its UID, that can be logged. Ensure there’s an rule to catch any process trying to change its user/group IDs.
Audit data export (optional): If concerned about data exfiltration, audit usage of (especially to external drives), or creation of files in tmp that are world-readable, or use of networking tools (like netcat). However, these can be noisy and require tuning.
Red Hat provides pre-configured audit rules files for compliance with standards – for example, there might be ready STIG profile rules in or via the SCAP Security Guide package. You can adopt those or use to compile multiple rule files in .
Audit log management:
By default, audit logs go to . Check plugins too; sometimes they forward to syslog or elsewhere.
The can grow quickly. The file is rotated via settings. There you can set (size in MB) and (like rotate or keep_logs). You might choose for high-security (never delete old ones, just keep adding .old files) if you have space, or set up a cron to archive them offline.
Consider setting and in . For example, if disk space for audit logs is below a threshold, auditd can email an admin or even halt logging/new sessions. STIG requires that if audit logs can’t be written, the system should alert or halt depending on criticality.
As a tip, the audit logs are verbose. Use and to query them. For instance: will find events with that key. shows failed logins. gives a summary of authentication events.
Regularly review audit logs for anomalies: multiple failed login attempts (indicative of password brute force), editing of files that shouldn’t change often, usage of compilers or unexpected programs by users, etc. AIDE (discussed next) and auditd together give both change detection and activity logging.
7.3 File Integrity Monitoring with AIDE
The Advanced Intrusion Detection Environment (AIDE) is a file integrity checker which takes “snapshots” (cryptographic hashes) of specified files and later checks if they have changed. It’s not enabled by default, but is a recommended tool for hardening (CIS, etc., suggest installing and running it). Red Hat’s guide explicitly suggests running AIDE regularly to detect changes.
Setup AIDE:
This initializes the database (usually at ). Then move that to the official db location:
Now you have a baseline. The config controls which paths are monitored and what attributes. By default, it covers a broad set of system directories (/, /bin, /sbin, /etc, /usr, /var, etc) with appropriate ignore patterns (like dynamic files in /var run or /proc are excluded). You may want to tweak it, e.g., to include or application directories if appropriate.
Run AIDE check:
This scans and compares to the DB, reporting any differences. On a stable system, initially, you might see some differences if things changed since init (like log files updated, etc.). After baseline, you expect no changes except when authorized (like after system updates, or log rotation).
It’s good practice to run AIDE daily or weekly via cron or systemd timer. For instance, create to run and email the result to admins. The RHEL guide suggests at minimum weekly, optimally daily. If daily at say 4:05 AM: Add in root’s crontab or a cron file:
(This requires mailx and an MTA configured to send mail, or use an setting in auditd for serious events.)
Update AIDE DB: When you deliberately change files (system updates, config changes), you should update the AIDE database so it doesn’t keep reporting those as anomalies. That means re-running and replacing the DB (or if you prefer). Always keep a secure copy of the AIDE database off the machine if possible (store on a read-only media or separate host) – because if an attacker compromises your system, they could also alter the AIDE database to match their changes, defeating the purpose. A strategy is to keep the database read-only on the system (perhaps on a mounted CD or a USB that’s plugged in only for the check) or to run AIDE from a remote trusted host via SSH.
EVM/IMA: Red Hat has advanced features like IMA (Integrity Measurement Architecture) and EVM in the kernel that can enforce file integrity at run-time (signing files and verifying before access). Those are powerful but complex to set up, often beyond typical hardening scope. AIDE is a simpler, offline check approach.
7.4 Centralized Audit (Optional)
If you have many Linux systems, consider using a system like Auditd aggregation or feeding logs to a SIEM. The audit daemon can multicast events or be read by audisp-remote. The SCAP guide or OpenSCAP content might require an audit aggregation server (for high level compliance, e.g., common event format).
7.5 Summary of Logging & Auditing
Goals: capture all important events, store logs securely, monitor changes. Logging and auditing doesn’t prevent incidents by itself (except where you set it to abort actions), but it is critical for detection and recovery. Ensure at minimum:
All authentication attempts are logged (success and failure).
All use of admin privileges is logged (e.g., sudo commands appear in /var/log/secure; you might also configure sudoers with to record commands, though that’s heavy).
Key security files and commands are audited.
Logs are retained and protected from tampering (remote copy or proper perms).
Regular integrity scans are conducted.
With these in place, you significantly increase the chances of catching unauthorized activity. For example, if an attacker manages to edit to add a backdoor user, you’d have an audit record of the file change (auditd watch) and next AIDE run would flag that content changed. You would also hopefully see how they got in, from logs.
In high security contexts, logs might need to be reviewed daily. In enterprise, using a SIEM that collects and highlights anomalies (e.g., Splunk, ELK, or cloud equivalents) can offload the manual review burden.
8. SSH and Remote Access Hardening
Secure Shell (SSH) is the primary method for remote administration on Linux. Hardening SSH is critical because it’s often the first service exposed on a server. A compromise of SSH can immediately lead to root access if not properly restricted. Below are best practices for SSH (some of which we’ve touched on earlier but we’ll consolidate here):
8.1 Protocol and Algorithms
Protocol Version: Ensure SSH Protocol 2 is in use (Protocol 1 is obsolete/insecure). RHEL 8’s OpenSSH no longer even supports v1 by default, so this is usually fine. In , ensure there’s no “Protocol 1” line (should be protocol 2 only).
Ciphers/MACs/KEX: Leverage the system crypto policy – by default, RHEL8’s crypto policy will remove weak ciphers from SSH. For instance, on a RHEL8 host will not list things like 3DES or RC4 (they’re disabled by default). It will primarily list AES variants and CHACHA20. The DEFAULT crypto policy also likely removes the older hmac-md5, etc., leaving SHA2-based MACs. If you want to be explicit or you have an older OpenSSH that doesn’t integrate with crypto policies, explicitly set in sshd_config:
This is an example set that is strong as of now. However, with RHEL8’s defaults, you might not need to list them – just ensure none of the explicitly weak ones are enabled. Additionally, if in FIPS mode, OpenSSH will drop chacha20 and some curve25519 (because not FIPS certified), using only FIPS allowed (likely AES and SHA, and diffie-hellman).
Host Keys: Use strong host keys. RHEL8 by default generates RSA 2048-bit, and possibly an ECDSA and Ed25519. For new systems, Ed25519 keys are recommended (very strong and small). It’s fine to have RSA as well for compatibility with older clients. You could consider creating a 4096-bit RSA key to be extra safe if you expect RSA to be used. In sshd_config, you can specify:
and maybe disable DSA keys if any (they shouldn’t be generated by default nowadays).
8.2 Authentication Methods
Disable root login: Do not allow direct root SSH login with password. Ideally, disable root SSH login entirely:
in sshd_config. This forces admins to login as a normal user and then escalate with sudo – providing accountability (which user accessed, vs all coming as root). If you absolutely need root login, restrict it to key authentication only ( means “only allow root via keys, not password”). But it’s better to forbid it completely; the Practical Guide notes “Direct root logins should be allowed only for emergency use” for accountability reasons.
Also ensure no other account has UID 0 (sometimes people made user with uid 0 – that defeats the purpose). The guide provides a one-liner to lock any such accounts. Check for duplicate UID 0. The only UID 0 user should be root.
Use Public Key Authentication: Set up SSH key-based auth for users. This means (to disable passwords over SSH) and ensure . With keys, brute-forcing passwords is not possible, and you can (with a passphrase on the key) protect against key theft. Most compliance standards strongly prefer key authentication over passwords for remote access. If you cannot disable passwords entirely (perhaps some users still need them), at least enforce strong password policy (and consider 2FA).
Enable Two-factor Authentication (optional): For extremely important systems, consider integrating SSH with MFA. This can be done via PAM (for example, using Google Authenticator PAM module or SSH’s directive to require “publickey & keyboard-interactive” which can tie into a OTP prompt). RHEL8 can support this if configured, but it’s a bit beyond default. For example, you might use which means either two pubkeys or pubkey + PAM (where PAM could be an OTP). Alternatively, require users to use an SSH certificate plus an OTP.
Limit Users/Groups: Use or in sshd_config to restrict which accounts can SSH in. For instance, if you only have a couple of admin accounts that should login, explicitly allow them:
This way if someone somehow created another user or some system accounts exist, they cannot be used to SSH in. Or use and put all SSH-permitted users in that group, then:
All other accounts (even if they have valid creds) will be denied at connection.
Idle Timeout: Set an idle timeout so that idle sessions close, reducing risk of hijacking an unattended session. For SSH, that is:
This means after 300 seconds (5 minutes) of no activity, send an alive message, if no response twice (so ~10 min), disconnect. Essentially a 10-minute idle logout. Adjust to your comfort (the STIG often says 10 or 15 minutes). This is particularly important for shared environments or if people walk away from their terminal without locking.
Banner and Legal Warning: Configure a banner to display an unauthorized access warning before login (to deter and for legal coverage). In :
Then put a message in like “WARNING: This system is for authorized use only. All activity may be monitored and reported.” (Typically what your organization’s legal counsel suggests). This is often required by policy. The Practical Guide notes use of login banners with DoD text as an example. Ensure it’s short and to the point; longer text might actually scroll off.
X11 and Agent Forwarding: Unless needed, disable X11 forwarding:
This prevents X11 apps from being forwarded which is a minor risk (someone could attempt to open a GUI and capture the display, etc.). If you don't use it, off is safer. Also, be cautious with SSH agent forwarding ( in client). On server side, there's not much to disable, but be aware if you into machines, that machine could use your agent to authenticate to others – so only use agent forwarding to hosts you fully trust.
Port: Running SSH on a non-standard port (e.g., 2222) can reduce the noise from random scanners and bot attacks on port 22. However, a determined attacker will scan all ports anyway. If you do change it, update in config and firewall accordingly. It's security by obscurity, but it can drop the number of log entries for failed logins significantly.
Logging: SSH already logs to /var/log/secure (or journal). Ensure it’s working. It logs successful and failed logins, key fingerprint used, etc. This pairs with audit logs.
SFTP Jail: If you provide SFTP to users, consider using the internal SFTP in and possibly chroot them ( and a Match group for sftp.
8.3 SFTP Jail (Chroot)
If you provide SFTP access to users, ensure they are confined to their home directories (jail) to prevent them from accessing other areas of the filesystem. Configure this by adding the following to :
Then, ensure user directories have correct permissions:
This setup locks SFTP users into their home directories and only allows uploads in specified subdirectories.
Conclusion and Final Recommendations
Keeping your system secure is an ongoing task that needs constant checking and updating. Following the hardening standards in this guide can really help protect against attacks and unauthorized access. Make it a habit to regularly check your system settings, keep your software updated, and review your logs. Staying on top of these things puts your Red Hat Enterprise Linux system in a good place for security, reliability, and meeting requirements.
References and Further Reading
Red Hat Enterprise Linux 8 Security Hardening Guide (official PDF documentation)
The Practical Linux Hardening Guide by trimstray (GitHub Repository)
CIS Red Hat Enterprise Linux Benchmark (CIS Benchmarks)
DISA Security Technical Implementation Guide (STIG) for RHEL 8 (DISA STIG)
OpenSCAP Security Guide (OpenSCAP Project)
Red Hat Documentation Portal (Official Red Hat Documentation)