SELinux In 10 Minutes
From Wikipedia
Security-Enhanced Linux (SELinux) is a Linux kernel security module that provides a mechanism for supporting access control security policies, including mandatory access controls (MAC).
It accomplishes this by assigning labels to files/directories and processes. Only processes allowed via policy definitions can access files/directories with a given label.
SELinux comes pre-installed on RHEL-like distros (RHEL, Alma, Rocky, etc.)
Check if SELinux is Enforcing
[root@almalinux-s-2vcpu-2gb-nyc1-01 ~]# sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Memory protection checking: actual (secure)
Max kernel policy version: 33
This means SELinux will actively prevent unauthorized processes from accessing files.
To set to Permissive mode (does not persist reboot):
[root@almalinux-s-2vcpu-2gb-nyc1-01 ~]# setenforce 0
[root@almalinux-s-2vcpu-2gb-nyc1-01 ~]# getenforce
Permissive
This means SELinux will continue to log policy violations, but will not enforce them (this is useful for testing).
To set it back to Enforcing (does not persist reboot):
[root@almalinux-s-2vcpu-2gb-nyc1-01 ~]# setenforce 1
[root@almalinux-s-2vcpu-2gb-nyc1-01 ~]# getenforce
Enforcing
If you want the change to persist reboot, edit /etc/selinux/config:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - No SELinux policy is loaded.
SELINUX=enforcing
Reproducing a Permission Denial
Suppose we have apache web server running. We have created /var/www/html/index.html page. However, when we try to visit the page, we get a 403 forbidden response
[root@almalinux-s-2vcpu-2gb-nyc1-01 ~]# curl -I localhost
HTTP/1.1 403 Forbidden
Date: Sat, 12 Jul 2025 19:25:17 GMT
Server: Apache/2.4.62 (AlmaLinux)
Last-Modified: Mon, 24 Mar 2025 16:15:24 GMT
ETag: "1680-63118e9567f00"
Accept-Ranges: bytes
Content-Length: 5760
Content-Type: text/html; charset=UTF-8
Despite placing our own /var/www/html/index.html, Apache still serves the default page.
After trying several things, we suspect the issue might be SELinux blocking the page. Sure enough, if we temporarily disable SELinux (remember to re-enable it!), the page loads fine
[root@almalinux-s-2vcpu-2gb-nyc1-01 ~]# setenforce 0
[root@almalinux-s-2vcpu-2gb-nyc1-01 ~]# curl -I localhost
HTTP/1.1 200 OK
Date: Sat, 12 Jul 2025 19:28:59 GMT
Server: Apache/2.4.62 (AlmaLinux)
Last-Modified: Sat, 12 Jul 2025 19:24:45 GMT
ETag: "15-639c0611b603e"
Accept-Ranges: bytes
Content-Length: 21
Content-Type: text/html; charset=UTF-8
[root@almalinux-s-2vcpu-2gb-nyc1-01 ~]# setenforce 1
Using Audit Logs to Investigate
Let’s inspect /var/log/audit/audit.log for any policy violations. We can grep for denied to see all SELinux denials
[root@almalinux-s-2vcpu-2gb-nyc1-01 ~]# grep denied /var/log/audit/audit.log | grep httpd
type=AVC msg=audit(1752348303.196:1039): avc: denied { getattr } for pid=54333 comm="httpd" path="/var/www/html/index.html" dev="vda1" ino=25203809 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:admin_home_t:s0 tclass=file permissive=0
An easier way to view these logs is to run ausearch -ts recent -sv no which displays recent denials
[root@almalinux-s-2vcpu-2gb-nyc1-01 ~]# ausearch -ts recent -sv no
----
time->Sat Jul 12 19:32:11 2025
type=PROCTITLE msg=audit(1752348731.812:1068): proctitle=2F7573722F7362696E2F6874747064002D44464F524547524F554E44
type=SYSCALL msg=audit(1752348731.812:1068): arch=c000003e syscall=262 success=no exit=-13 a0=ffffff9c a1=7f355800e508 a2=7f355d7f97c0 a3=0 items=0 ppid=54329 pid=54332 auid=4294967295 uid=48 gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=4294967295 comm="httpd" exe="/usr/sbin/httpd" subj=system_u:system_r:httpd_t:s0 key=(null)
type=AVC msg=audit(1752348731.812:1068): avc: denied { getattr } for pid=54332 comm="httpd" path="/var/www/html/index.html" dev="vda1" ino=25203809 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:admin_home_t:s0 tclass=file permissive=0
----
time->Sat Jul 12 19:32:11 2025
type=PROCTITLE msg=audit(1752348731.812:1069): proctitle=2F7573722F7362696E2F6874747064002D44464F524547524F554E44
type=SYSCALL msg=audit(1752348731.812:1069): arch=c000003e syscall=262 success=no exit=-13 a0=ffffff9c a1=7f355800e5e8 a2=7f355d7f97c0 a3=100 items=0 ppid=54329 pid=54332 auid=4294967295 uid=48 gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=4294967295 comm="httpd" exe="/usr/sbin/httpd" subj=system_u:system_r:httpd_t:s0 key=(null)
type=AVC msg=audit(1752348731.812:1069
msg=audit(1752348731.812:1068) uniquely identifies the log entry.
Sure enough comm="httpd" was denied access to path="/var/www/html/index.html"
Interpreting Denials with audit2why
We see this denial, but why did SELinux deny access to our page?
We can use audit2why to give a human readable explanation. We will search for the entry using the unique identifier in the logs.
[root@almalinux-s-2vcpu-2gb-nyc1-01 ~]# grep "1752348731.812:1068" /var/log/audit/audit.log | audit2why
type=AVC msg=audit(1752348731.812:1068): avc: denied { getattr } for pid=54332 comm="httpd" path="/var/www/html/index.html" dev="vda1" ino=25203809 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:admin_home_t:s0 tclass=file permissive=0
Was caused by:
Missing type enforcement (TE) allow rule.
You can use audit2allow to generate a loadable module to allow this access.
This informs us that the SELinux label for /var/www/html/index.html is incorrect. Let’s look at that.
💡 You can generate a policy with
audit2allow, but for mislabeled files, it’s safer to fix the context withrestorecon.
Fixing Incorrect SELinux Labels
Every file has a context (label) that only allows certain processes to interact with them. We can view the SELinux context for the file like this
[root@almalinux-s-2vcpu-2gb-nyc1-01 ~]# ls -Z /var/www/html/index.html
unconfined_u:object_r:admin_home_t:s0 /var/www/html/index.html
Let’s compare this to what the file type should be
[root@almalinux-s-2vcpu-2gb-nyc1-01 ~]# matchpathcon /var/www/html/index.html
/var/www/html/index.html system_u:object_r:httpd_sys_content_t:s0
We can see they do not match. To change the label to what is expected, we can simply run
[root@almalinux-s-2vcpu-2gb-nyc1-01 ~]# restorecon -v /var/www/html/index.html
Relabeled /var/www/html/index.html from unconfined_u:object_r:admin_home_t:s0 to unconfined_u:object_r:httpd_sys_content_t:s0
Or, we can recursively relabel all files and subdirectories using
restorecon -Rv /var/www/html
Verifying the Fix
We can now see the page loads as expected, and httpd has access to that file
[root@almalinux-s-2vcpu-2gb-nyc1-01 ~]# curl -I localhost
HTTP/1.1 200 OK
Date: Sat, 12 Jul 2025 20:04:04 GMT
Server: Apache/2.4.62 (AlmaLinux)
Last-Modified: Sat, 12 Jul 2025 19:24:45 GMT
ETag: "15-639c0611b603e"
Accept-Ranges: bytes
Content-Length: 21
Content-Type: text/html; charset=UTF-8
Useful Commands
sestatus— Show SELinux statussetenforce— Set mode (Enforcing/Permissive)restorecon— Restore file contextsaudit2why— Explain SELinux denialsausearch— Search audit logsmatchpathcon— Show default expected context for a path