SELinux Explained (and How to Live With It)
Learn SELinux modes, file contexts, booleans, and how to fix denials with restorecon, setsebool, and audit2allow — without ever disabling it.
Before you start
- ▸Root or sudo access on a system with SELinux installed (Fedora, RHEL, Rocky, or CentOS Stream recommended)
- ▸policycoreutils and policycoreutils-python-utils (or equivalent) installed
- ▸Basic familiarity with systemd services and reading log files
- ▸SELinux not set to disabled in /etc/selinux/config (permissive is fine to start)
SELinux — Security-Enhanced Linux — is the mandatory access control (MAC) system built into the Linux kernel. It confines every process to exactly what it needs, enforcing policy on top of traditional Unix permissions. Most people's first instinct is to turn it off. That instinct is wrong. A single setenforce 0 silently removes a substantial layer of protection from production systems. This guide teaches you to read what SELinux is actually telling you and fix it properly.
How SELinux Works
Traditional Linux uses discretionary access control (DAC): the file owner decides who can read or write it. SELinux adds mandatory access control on top: the kernel enforces a policy that even root cannot override. Every process runs in a domain, every file carries a context, and the policy defines which domains can access which contexts.
A context looks like this:
ls -Z /var/www/html/index.html
Output (will vary):
unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/index.html
The four colon-separated fields are user, role, type, and level. In practical type enforcement, the type field is what controls access. The Apache process runs in the httpd_t domain; policy allows httpd_t to read httpd_sys_content_t files. If a file has the wrong type, Apache gets denied — and the audit log tells you exactly why.
The Three Modes
- Enforcing — Policy is active. Violations are blocked and logged.
- Permissive — Policy is not enforced, but violations are still logged. Useful for diagnosis.
- Disabled — SELinux is completely off. Files created while disabled have no context, which causes headaches when you re-enable it.
Check the current mode:
getenforce
sestatus
Switch to permissive at runtime (survives until reboot, no filesystem relabel required):
sudo setenforce 0 # permissive
sudo setenforce 1 # enforcing
The persistent setting lives in /etc/selinux/config. Set SELINUX=enforcing (or permissive) there. Changing to or from disabled requires a reboot and a full filesystem relabel — adding autorelabel=1 to the kernel command line or touching /.autorelabel before rebooting.
sudo sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
Reading the Audit Log
Every denial generates an AVC (Access Vector Cache) message in the audit log. On Fedora, RHEL, Rocky, and most SELinux-enabled systems:
sudo ausearch -m AVC,USER_AVC -ts recent | tail -40
Or watch it live:
sudo tail -f /var/log/audit/audit.log | grep AVC
A raw AVC message is dense. audit2why translates it:
sudo ausearch -m AVC -ts today | audit2why
You'll see output like:
type=AVC msg=audit(1700000000.123:456): avc: denied { read } for pid=1234
comm="nginx" name="app.sock" dev="tmpfs" ino=789
scontext=system_u:system_r:httpd_t:s0
tcontext=system_u:object_r:var_t:s0 tclass=sock_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.
Read the denial: the httpd_t domain tried to read a socket file labeled var_t. The fix is almost never to disable SELinux — it is to give the file the right label or enable the right boolean.
Fixing Label Problems
The most common SELinux problem is a file with the wrong context — usually because it was created in the wrong directory, copied with cp (which does not preserve contexts), or moved with mv.
Restoring Default Labels
If a file or directory should have whatever the policy defines as its default context, use restorecon:
sudo restorecon -Rv /var/www/html/
The -R flag recurses; -v prints what changed. This is the first tool to reach for after deploying web content or configuration files.
Checking and Setting File Contexts
Query what the policy says the default context should be:
semanage fcontext -l | grep '/var/www'
If your application stores data outside its standard directory (e.g., serving files from /srv/myapp instead of /var/www/html), add a file context rule and then apply it:
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/myapp(/.*)?"
sudo restorecon -Rv /srv/myapp/
This survives relabels and reboots. Never use chcon as a permanent fix — it changes the runtime label only and is lost on relabel.
Using Booleans
SELinux ships with hundreds of named booleans that toggle specific policy rules without writing custom policy. They cover common scenarios: Apache serving home directories, services connecting to databases, NFS home directories, and more.
List all booleans and their current state:
getsebool -a
semanage boolean -l # also shows description and persistence
Search for booleans relevant to a service:
getsebool -a | grep httpd
Enable a boolean temporarily (resets on reboot):
sudo setsebool httpd_can_network_connect on
Make it persistent:
sudo setsebool -P httpd_can_network_connect on
Common booleans worth knowing:
| Boolean | What it allows |
|---|---|
httpd_can_network_connect | Apache to make outbound TCP connections (e.g., proxy, PHP curl) |
httpd_can_network_connect_db | Apache to connect to database ports |
httpd_enable_homedirs | Apache to serve ~/public_html |
samba_enable_home_dirs | Samba to share home directories |
container_manage_cgroup | Podman/Docker containers to manage cgroups |
Generating Custom Policy Modules
When no boolean covers your case and the file context is already correct, you may need a small custom module. Use audit2allow to generate one from your denials. Review the output before applying it — audit2allow is a power tool, not a magic fix-all.
sudo ausearch -m AVC -ts today | audit2allow -M myapp_fix
This produces myapp_fix.te (human-readable policy) and myapp_fix.pp (compiled module). Read the .te file:
cat myapp_fix.te
If the rules look reasonable (narrow, specific types — not unconfined_t or wildcard), load the module:
sudo semodule -i myapp_fix.pp
List installed modules:
sudo semodule -l | grep myapp
Remove it later if unneeded:
sudo semodule -r myapp_fix
Diagnosing With sealert
On RHEL-family systems, install setroubleshoot-server for human-readable denial reports:
sudo dnf install setroubleshoot-server
Then run:
sudo sealert -a /var/log/audit/audit.log
sealert groups denials, explains the likely cause, and suggests the specific restorecon, setsebool, or audit2allow command to fix each one. It is the fastest path from a confusing denial to a working fix.
Verification
After applying a fix, confirm SELinux is still enforcing and the denial is gone:
getenforce # should print Enforcing
sudo ausearch -m AVC -ts recent | grep -c denied # should drop to 0
Then exercise the application — restart the service, make a real request — and check the log again. If it stays quiet, you fixed it properly.
Troubleshooting
- Service works in permissive but not enforcing: Pure SELinux denial. Put it in permissive, reproduce the failure, then check
ausearch -m AVC -ts recent | audit2why. - No AVCs but service still fails: Not an SELinux issue — check
journalctl -u servicenamefor application errors. - Everything is labeled
unlabeled_tafter re-enabling: The filesystem was not relabeled after a period of running disabled. Touch/.autorelabeland reboot. - audit2allow suggests allowing
unconfined_taccess: Stop. That rule is too broad. Investigate why the process is unconfined instead. - Fedora/RHEL only —
semanagenot found: Installpolicycoreutils-python-utils.
sudo dnf install policycoreutils-python-utils # Fedora / RHEL
sudo apt install policycoreutils python3-policycoreutils # Debian / UbuntuFrequently asked questions
- Is it ever acceptable to disable SELinux permanently?
- Rarely. On production systems, disabling SELinux removes a kernel-enforced security layer that even root cannot bypass. Use permissive mode temporarily to diagnose, then fix the root cause and return to enforcing.
- What is the difference between chcon and semanage fcontext?
- chcon changes the runtime label on a file directly and is lost during a filesystem relabel or reboot. semanage fcontext writes a persistent policy rule; use restorecon afterward to apply it.
- My application works fine but audit2allow keeps suggesting broad rules — should I apply them?
- No. Review the .te file before loading any module. Rules granting access to unconfined_t or using wildcards are a sign you need to investigate why the process has the wrong domain, not paper over it with permissive policy.
- Does SELinux work on Ubuntu and Debian?
- Yes, but those distros ship AppArmor by default. You can install and enable SELinux (selinux-basics, selinux-policy-default packages), but expect less out-of-the-box policy coverage than on Fedora or RHEL.
- Why do I see no AVC messages even though my service fails in enforcing mode?
- SELinux may not be the cause. Some denials are silenced by dontaudit rules in the policy. Run semodule -DB to temporarily disable dontaudit rules and check again, or look at journalctl for application-level errors.
Related guides
AppArmor Explained
Learn how AppArmor profiles work, how to switch between enforce and complain mode, create new profiles, and diagnose access denials on Ubuntu, Debian, and Arch.
How to Audit a Linux System with auditd
Set up auditd on Linux to track file access, syscalls, and privilege use. Covers persistent rules, file watches, ausearch, and aureport across major distros.
How to Audit Linux Hardening with Lynis
Run Lynis to audit your Linux server, interpret the hardening index and warning output, and work through findings from critical to low-effort wins.
How to Enable Automatic Security Updates
Enable automatic security updates on Debian, Ubuntu, Fedora, and RHEL using unattended-upgrades and dnf-automatic — configured to patch safely without manual effort.