Check the labels before you blame SELinux
Most SELinux denials are a wrongly labelled file, not a policy gap. How to audit labels after an install, fix them properly and enforce safely on remote hosts.

When an application breaks on a host with SELinux enforcing, the reflex in many teams is to switch SELinux to permissive and move on. We keep it enforcing on every host we run, and in our experience the cause is usually much smaller than the reflex suggests: a file carrying the wrong label. This article shows how to find that quickly, fix it the right way, and avoid locking yourself out while you do it.
What a label is, and how it goes wrong
SELinux makes decisions based on labels. Every file and every process carries a security context, and the policy says which process types may do what to which file types. The policy also says what label each path should have: /var/www content should be httpd_sys_content_t, an executable under /usr/bin should be bin_t, and so on. Those expected labels are the file context rules.
A file gets its label when it is created, normally inherited from its parent directory or set by a rule in the policy. That works well until files arrive by an unusual route:
mvkeeps the original label, so a file created in a home directory and moved into a web root still says it belongs in a home directory.- Archives unpacked without SELinux attributes, or restored from a backup that did not keep them, get whatever the target directory hands out.
- Content placed in a custom location, such as
/srv/app, gets a generic label because the policy has no rule for that path yet.
In every one of those cases the policy is fine. The file is simply wearing the wrong label, and the process that needs it is refused.
Read the denial first
Before changing anything, read what SELinux actually refused:
ausearch -m AVC -ts recent
Each denial names the process type (scontext), the target type (tcontext), the object class and the permission. If the target type looks wrong for that file (a web server denied reading something labelled user_home_t, for example), you are almost certainly looking at a labelling problem, not a policy gap.
Compare what is there with what should be there
Three commands answer the question directly:
ls -Z /path/to/file # the label it has
matchpathcon /path/to/file # the label the policy expects
restorecon -nv /path/to/file # dry run: what restorecon would change
restorecon -n changes nothing. It only reports the difference between the current label and the expected one, which makes it the perfect audit tool. Run it across a whole tree after an install, a restore or a migration:
restorecon -Rnv /etc /usr /var /srv 2>/dev/null
An empty result means every file under those paths carries the label the policy expects. Any line in the output is a file that does not, and that list is usually short and very telling. We run this kind of check on new builds as a matter of routine, and our install templates relabel critical paths in their post-install step, so a new host starts life with correct labels before anyone logs in.
Fix it the right way
For a file that simply has the wrong label, relabel it to what the policy expects:
restorecon -Rv /path
For a custom location the policy does not know about, teach the policy the rule first, then relabel, so the fix survives the next full relabel:
semanage fcontext -a -t httpd_sys_content_t "/srv/app(/.*)?"
restorecon -Rv /srv/app
chcon also changes labels, but only until the next relabel, after which the problem returns. Use it for experiments, not fixes.
If the labels are right and the denial is about behaviour, check the booleans before writing policy. Many common needs, such as a web server opening outbound network connections, are a single switch:
getsebool -a | grep httpd
setsebool -P httpd_can_network_connect on
Be suspicious of audit2allow
audit2allow reads denials and writes a policy module that permits them. It will always produce something that works, which is exactly the danger. If the real cause is a mislabelled file, the generated module grants a broad permission to make the symptom go away, and every other file and process of that type inherits the weaker policy. Read what it proposes. A rule allowing one domain to transition into a very privileged one, or to read everything of a common type, is a strong hint that the label is wrong and the module is the wrong fix. Custom modules are sometimes correct, for in-house software with no policy of its own, but they should be the last step, not the first.
Enforce safely on a remote host
Switching a remote machine from permissive to enforcing is one of those changes that can cut off your own session if something is still mislabelled. Arm a safety net first, so the host puts itself back if you lose access:
systemd-run --on-active=240 --unit=selinux-net /usr/sbin/setenforce 0
setenforce 1
# open a NEW ssh session and confirm it works
systemctl stop selinux-net.timer
systemd fires the timer whether or not your session survives. Cancel it only after a brand new login has succeeded. The same pattern works for firewall, SSH and routing changes on a machine you cannot walk up to.
A short checklist
- Run
restorecon -Rnvover the system paths after every install, restore or migration. - Read the AVC denial and compare
ls -Zwithmatchpathconbefore changing policy. - Use
semanage fcontextplusrestoreconfor custom paths, notchcon. - Check booleans before writing a module.
- Treat
audit2allowoutput as a question, not an answer. - Never flip a remote host to enforcing without a timed way back.
If an application vendor has told you to disable SELinux and you would rather not, we are happy to help find the precise fix. Talk to an engineer
Published 22 October 2026.
All field notes