$linuxjunkies
>

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.

AdvancedUbuntuDebianFedoraArch12 min readUpdated May 26, 2026

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:

BooleanWhat it allows
httpd_can_network_connectApache to make outbound TCP connections (e.g., proxy, PHP curl)
httpd_can_network_connect_dbApache to connect to database ports
httpd_enable_homedirsApache to serve ~/public_html
samba_enable_home_dirsSamba to share home directories
container_manage_cgroupPodman/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 servicename for application errors.
  • Everything is labeled unlabeled_t after re-enabling: The filesystem was not relabeled after a period of running disabled. Touch /.autorelabel and reboot.
  • audit2allow suggests allowing unconfined_t access: Stop. That rule is too broad. Investigate why the process is unconfined instead.
  • Fedora/RHEL only — semanage not found: Install policycoreutils-python-utils.
sudo dnf install policycoreutils-python-utils   # Fedora / RHEL
sudo apt install policycoreutils python3-policycoreutils   # Debian / Ubuntu
tested on:Fedora 40Rocky 9.3RHEL 9.4Debian 12

Frequently 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