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
sudoaccess - Basic understanding of Linux file permissions
- The
policycoreutils-pythonpackage installed (providessemanage,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 Apachehttpd_sys_rw_content_t— web content writable by Apachessh_home_t— SSH key files in user home directoriesvar_log_t— log files in/var/logetc_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 databaseshttpd_can_sendmail— allows Apache to send email via sendmail/postfixhttpd_use_nfs— allows Apache to serve content from NFS mountshttpd_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.