chmod 755, chown to the right user, and yet Permission denied persists for a service like nginx or Apache trying to access a file that’s otherwise perfectly configured according to classic Unix permissions. On a RHEL, CentOS, Fedora, or Rocky Linux distribution with SELinux active, Unix permissions are only one of two access-control layers that must be satisfied, never the only one.
What SELinux adds on top of Unix permissions
SELinux enforces mandatory access control (MAC) on top of the discretionary access control (DAC) of classic Unix permissions: even with perfectly correct rwx rights, a process whose SELinux security context doesn’t match what’s expected for that resource gets denied access, a denial that stacks on top of the Unix check rather than replacing it.
# -Z reveals the SELinux context, invisible
# in a plain ls -l limited to Unix permissions
ls -lZ /var/www/html/index.html
# -rw-r--r--. user user unconfined_u:object_r:user_home_t:s0 index.html
The classic trap: a file moved to the wrong place
A file copied or moved into /var/www/html/ from a home directory (/home/user/, for instance) often keeps the SELinux context of its original location (user_home_t), rather than automatically inheriting the context expected at its new location (httpd_sys_content_t). nginx or Apache, whose process context is only allowed to read httpd_sys_content_t, then gets denied access despite otherwise flawless Unix permissions.
restorecon: restoring the expected context, not inventing one
restorecon fixes a file’s context by resetting it to the default value expected for its current location, according to SELinux policy rules already defined for that path, rather than assigning an arbitrary context.
# Resets the expected SELinux context for this path,
# per the rules already defined for /var/www/html
restorecon -v /var/www/html/index.html
audit2allow: understanding a denial before writing a rule
An SELinux denial gets systematically logged, making it possible to precisely diagnose which rule is missing rather than disabling SELinux entirely out of frustration, a practice that removes the protection without understanding its cause.
# Generates a suggested SELinux policy rule,
# based on denials actually logged
sudo ausearch -m avc -ts recent | audit2allow -M mypolicy
Why disabling SELinux is never the right answer
Disabling SELinux (setenforce 0, or worse, at the bootloader level) removes a defense-in-depth layer specifically designed to limit the damage if a compromised service (an application vulnerability in nginx, for instance) tries to reach resources outside its legitimate perimeter: the same least-privilege principle already applied to Kubernetes RBAC, but at the operating system level rather than the orchestrator level.
Takeaway
A Permission denied on a RHEL/CentOS/Fedora system with SELinux active never gets resolved just by checking chmod/chown: SELinux enforces a second control layer, the security context, visible via ls -lZ and invisible in a plain ls -l. A moved file often keeps the context of its original location rather than inheriting the one expected at its destination, restorecon fixing that context according to the policy already defined. Disabling SELinux when faced with a frustrating denial removes a system-level least-privilege protection rather than understanding it, audit2allow offering a path to precisely diagnose and allow what actually needs allowing.