How to Configure SELinux on RHEL 7

SELinux (Security-Enhanced Linux) is a mandatory access control (MAC) system built into the Linux kernel and enabled by default on every RHEL 7 installation. Unlike traditional Unix discretionary access control (DAC), which lets file owners decide who can access their files, SELinux enforces a policy defined by the system administrator — even root cannot bypass it. When a confined process tries to access a resource it has no policy label for, SELinux denies the operation and logs the violation. This makes it one of the most powerful defenses against privilege escalation exploits: even if an attacker compromises a running service, SELinux contains the blast radius to what that service is permitted to do. This guide covers modes, file contexts, booleans, and troubleshooting so you can keep SELinux in enforcing mode rather than disabling it.

Prerequisites

  • RHEL 7 system (SELinux is enabled by default)
  • Root or sudo access
  • Basic understanding of Linux file permissions
  • The policycoreutils-python package installed (provides semanage, audit2why, audit2allow)
sudo yum install -y policycoreutils-python setroubleshoot-server setools-console

Step 1: Understanding SELinux Modes

SELinux operates in three modes:

  • Enforcing — SELinux policy is enforced. Denials are blocked and logged to /var/log/audit/audit.log. This is the correct production setting.
  • Permissive — Policy violations are logged but not blocked. Use this temporarily for troubleshooting.
  • Disabled — SELinux is completely off. Avoid this; re-enabling later requires a full filesystem relabel on the next boot.

Check the current mode at runtime:

getenforce

Get detailed status including the policy name and number of labeled objects:

sestatus

Sample output:

SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux mount point:            /sys/fs/selinux
Loaded policy name:             targeted
Current mode:                   enforcing
Mode from config file:          enforcing
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Max kernel policy version:      31

Step 2: Changing SELinux Modes at Runtime

Switch between enforcing and permissive without rebooting using setenforce. This is useful for debugging — switch to permissive, reproduce the issue, check logs, then switch back:

# Switch to permissive (0) for troubleshooting
sudo setenforce 0
getenforce
# Output: Permissive

# Switch back to enforcing (1)
sudo setenforce 1
getenforce
# Output: Enforcing

Important: setenforce changes are not persistent across reboots. To make a mode change permanent, edit the configuration file.

Step 3: Persistent Mode Configuration

The persistent SELinux mode is set in /etc/selinux/config:

sudo vi /etc/selinux/config
# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
SELINUX=enforcing

# SELINUXTYPE= can take one of these two values:
#     targeted - Targeted processes are protected,
#     minimum - Modification of targeted policy.
#     mls - Multi Level Security protection.
SELINUXTYPE=targeted

The targeted policy confines specific high-risk services (httpd, sshd, named, vsftpd, etc.) while leaving most other processes unconfined. This is the recommended setting for most RHEL 7 deployments. After changing this file, a reboot is required for the new setting to take effect.

Step 4: Understanding and Working with File Contexts

SELinux assigns a security context (label) to every file, process, port, and socket. A file context has the format user:role:type:level — for most files, the important part is the type. View contexts with the -Z flag:

# View file contexts
ls -Z /var/www/html/
# Example output:
# -rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 index.html

# View process contexts
ps -eZ | grep httpd

# View directory context
ls -dZ /etc/ssh/
# system_u:object_r:etc_t:s0 /etc/ssh/

When a file has the wrong context, access will be denied even if the standard Unix permissions allow it. This is one of the most common SELinux issues administrators encounter.

Step 5: Restoring Default File Contexts with restorecon

When you copy a file from a location with one context type to a location with another, the file retains its original context. Use restorecon to reset it to the correct context for that location based on the policy:

# Restore context for a single file
sudo restorecon -v /var/www/html/myapp.php

# Restore contexts recursively for a directory
sudo restorecon -Rv /var/www/html/

# Dry run — show what would change without making changes
sudo restorecon -Rvn /var/www/html/

The -v flag makes output verbose (shows what changed). Use this after deploying new application files to a web root or moving configuration files.

Step 6: Changing File Contexts with chcon and semanage

chcon changes a file’s context immediately, but the change is not persistent — the next restorecon or full relabel will revert it. Use semanage fcontext to make a permanent policy change:

# Temporary change with chcon (not persistent)
sudo chcon -t httpd_sys_content_t /srv/mywebsite/index.html

# Permanent: add a policy rule and then apply it
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/mywebsite(/.*)?"
sudo restorecon -Rv /srv/mywebsite/

View all custom fcontext rules you have added:

sudo semanage fcontext -l -C

Common context types you will encounter on RHEL 7:

  • httpd_sys_content_t — web content readable by Apache
  • httpd_sys_rw_content_t — web content writable by Apache
  • ssh_home_t — SSH key files in user home directories
  • var_log_t — log files in /var/log
  • etc_t — configuration files in /etc

Step 7: Working with SELinux Booleans

SELinux booleans are toggles that enable or disable specific policy behaviors without writing custom policy modules. They allow administrators to adjust the policy for common use cases without deep policy knowledge. List all booleans and their current state:

getsebool -a
getsebool -a | grep httpd

Check a specific boolean:

getsebool httpd_can_network_connect
# httpd_can_network_connect --> off

Set a boolean for the current session only (resets on reboot):

sudo setsebool httpd_can_network_connect on

Set a boolean persistently with the -P flag (writes to policy on disk):

sudo setsebool -P httpd_can_network_connect on
sudo setsebool -P httpd_can_network_connect_db on

Common httpd booleans on RHEL 7 and what they enable:

  • httpd_can_network_connect — allows Apache to make outbound TCP connections (needed for reverse proxy)
  • httpd_can_network_connect_db — allows Apache to connect to remote databases
  • httpd_can_sendmail — allows Apache to send email via sendmail/postfix
  • httpd_use_nfs — allows Apache to serve content from NFS mounts
  • httpd_execmem — allows Apache to execute memory (needed by some PHP extensions)
  • httpd_read_user_content — allows Apache to read files from user home directories

Step 8: Troubleshooting SELinux Denials with audit2why

When SELinux blocks something, the denial is logged to /var/log/audit/audit.log. The audit2why tool translates raw AVC (Access Vector Cache) denial messages into human-readable explanations:

# Show recent AVC denials
sudo ausearch -m avc -ts recent

# Pipe through audit2why for explanations
sudo ausearch -m avc -ts recent | audit2why

Sample output from audit2why:

type=AVC msg=audit(1716000000.123:456): avc:  denied  { name_connect } for  pid=12345 ...
        Was caused by:
                One of the following booleans was set incorrectly.
                Description:
                Allow httpd to can network connect

                Allow access by executing:
                # setsebool -P httpd_can_network_connect 1

The setroubleshoot-server package provides sealert, which gives even more detailed analysis:

sudo sealert -a /var/log/audit/audit.log | head -100

Step 9: Generating Custom Policy Modules with audit2allow

When no boolean covers your use case, audit2allow generates a custom policy module from denial messages. Use this as a last resort — always prefer booleans and correct file contexts first, as poorly written custom modules can create security holes:

# Generate a policy module from recent denials
sudo ausearch -m avc -ts recent | audit2allow -M myapp_custom

# Review the generated .te (type enforcement) file before loading
cat myapp_custom.te

# Load the policy module
sudo semodule -i myapp_custom.pp

# Verify it is loaded
sudo semodule -l | grep myapp_custom

Remove a custom module when no longer needed:

sudo semodule -r myapp_custom

Step 10: Labeling Ports with semanage

Services listening on non-standard ports require an SELinux port label. For example, if Apache listens on port 8080:

# Check what port types already exist
sudo semanage port -l | grep http

# Add a new port label
sudo semanage port -a -t http_port_t -p tcp 8080

# Verify
sudo semanage port -l | grep 8080

# Modify an existing label
sudo semanage port -m -t http_port_t -p tcp 8080

# Remove a label
sudo semanage port -d -t http_port_t -p tcp 8080

The key to living productively with SELinux on RHEL 7 is to treat it as a debugging problem rather than an obstacle. When something breaks, the answer is almost always one of three things: a wrong file context (fix with semanage fcontext + restorecon), a disabled boolean (fix with setsebool -P), or a non-standard port label (fix with semanage port). Disabling SELinux to make something work is the wrong answer — it removes a critical layer of defense that protects against real-world exploits. Invest the time to understand the denial messages and apply the correct fix; your system will be significantly more secure for it.