SELinux (Security-Enhanced Linux) adds a mandatory access control (MAC) layer on top of standard Linux permissions. Even if an attacker compromises a service running as root, SELinux can prevent them from accessing files or network resources outside the service policy.
SELinux vs Standard Permissions
# Standard (DAC): If nginx runs as root, it can read ANY file
# SELinux (MAC): Even as root, nginx can only access files labeled for nginx
# SELinux adds labels (contexts) to every file, process, and port
# Example: ls -Z /var/www/html/
# unconfined_u:object_r:httpd_sys_content_t:s0 index.html
# ^^^^^^^^^^^^^^^^^
# SELinux type label
SELinux Modes
# Check current mode
getenforce
# Enforcing — Policies are enforced (production mode)
# Permissive — Policies logged but not enforced (testing mode)
# Disabled — SELinux is off
# Temporarily change mode
sudo setenforce 0 # Permissive (for testing)
sudo setenforce 1 # Enforcing
# Permanently set mode in /etc/selinux/config
# SELINUX=enforcing
Common SELinux Commands
# Check SELinux context of files
ls -Z /var/www/html/
# Check SELinux context of processes
ps auxZ | grep nginx
# Check SELinux context of ports
semanage port -l | grep http
# View SELinux booleans (on/off switches for common policies)
getsebool -a | grep httpd
Fixing Common SELinux Issues
# Problem: Nginx cannot serve files from a custom directory
# Solution: Set the correct SELinux context
sudo semanage fcontext -a -t httpd_sys_content_t "/custom/web(/.*)?"
sudo restorecon -Rv /custom/web
# Problem: Nginx cannot connect to a backend on a non-standard port
# Solution: Allow httpd to connect to the network
sudo setsebool -P httpd_can_network_connect 1
# Problem: Nginx cannot bind to a non-standard port
# Solution: Add the port to the http_port_t type
sudo semanage port -a -t http_port_t -p tcp 8080
# Problem: Application cannot write to a directory
sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/app/storage(/.*)?"
sudo restorecon -Rv /var/www/app/storage
Troubleshooting with audit2why
# When SELinux blocks something, it logs to /var/log/audit/audit.log
# Use audit2why to understand why:
sudo ausearch -m AVC -ts recent | audit2why
# Generate a policy module to allow the blocked action
sudo ausearch -m AVC -ts recent | audit2allow -M myapp
sudo semodule -i myapp.pp
# WARNING: Only do this if you understand what is being allowed
SELinux on Ubuntu (AppArmor Alternative)
# Ubuntu uses AppArmor by default instead of SELinux
# AppArmor provides similar MAC but with path-based rules
# Check AppArmor status
sudo aa-status
# View a profile
cat /etc/apparmor.d/usr.sbin.nginx
# Put a profile in complain mode (like SELinux permissive)
sudo aa-complain /usr/sbin/nginx
# Enforce a profile
sudo aa-enforce /usr/sbin/nginx
Best Practices
- Never disable SELinux in production — use permissive mode for troubleshooting
- Use audit2why before creating custom policies
- Use booleans before writing custom modules
- Test in permissive mode, then switch to enforcing
- Keep SELinux policies updated with your OS