SELinux "avc: denied": read the denial, then fix the label, the boolean or the port
Fix SELinux "avc: denied" on RHEL, Rocky and AlmaLinux: read the AVC line, then fix file labels with restorecon and semanage, booleans with setsebool, or ports.
by OhSudo ··
type=AVC msg=audit(1762941600.123:456): avc: denied { read } for pid=1234 comm="nginx" name="index.html" dev="dm-0" ino=67890 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0The service gets Permission denied or 403 Forbidden, even though the file permissions are fine, and this line is in the audit log. That’s SELinux: a second set of rules on RHEL and its relatives, on top of the usual permissions. Each process and each file has a label, and the policy says which labels may touch which.
Reading this one line usually tells you the fix. Turning SELinux off is never needed.
- nginx (httpd_t)wants to read index.htmlchecked by
- File permissionsallow it(works)then
- SELinux labeluser_home_t: denied(breaks here)
TL;DR
sudo ausearch -m AVC -ts recentsudo ls -Z /srv/www/index.htmlsudo semanage fcontext -a -t httpd_sys_content_t '/srv/www(/.*)?'sudo restorecon -Rv /srv/wwwThat’s for a web server reading files from a new folder. Sections 3 to 5 cover the other two common cases.
1. Read the denial
Find recent denials:
sudo ausearch -m AVC -ts recentEach part of the line answers a question:
| Part | Here | Means |
|---|---|---|
{ read } |
read | What it tried to do |
comm="nginx" |
nginx | Which program |
name="index.html" |
index.html | Which file, port or object |
scontext=...:httpd_t:... |
httpd_t |
The program’s label, or domain |
tcontext=...:user_home_t:... |
user_home_t |
The target’s label |
tclass=file |
file | What kind of object |
So, nginx (httpd_t) may not read a file labeled user_home_t. The file came from someone’s home folder, and was moved, keeping its old label.
2. Ask for an explanation
sudo ausearch -m AVC -ts recent | audit2whyaudit2why says which kind of problem this is: a wrong label, or a boolean that’s off, and names the boolean. On a machine with setroubleshoot-server installed, sealert gives a fuller report, with commands:
sudo sealert -a /var/log/audit/audit.logBoth come from the policycoreutils-python-utils and setroubleshoot-server packages.
3. A wrong file label
This is the most common case. See the label:
ls -Z /srv/www/index.htmlunconfined_u:object_r:user_home_t:s0 /srv/www/index.htmlmv keeps a file’s label, and cp gives it the label of where it lands. That’s why a moved file fails, and a copied one works. Reset labels to what the policy expects for that place:
sudo restorecon -Rv /var/www/htmlFor a folder the policy doesn’t know about, like /srv/www, tell it first, then apply:
sudo semanage fcontext -a -t httpd_sys_content_t '/srv/www(/.*)?'sudo restorecon -Rv /srv/wwwUse httpd_sys_rw_content_t for folders the web app must write to, such as uploads. Avoid chcon: its changes are lost the next time anything runs restorecon, while semanage fcontext is permanent.
4. A boolean that’s off
Booleans are switches for common needs. A web server proxying to an app, which is the cause of many 502 errors on RHEL, needs this:
getsebool httpd_can_network_connectsudo setsebool -P httpd_can_network_connect on-P keeps it across reboots. Others you’ll meet: httpd_can_network_connect_db for a web app reaching a database, httpd_enable_homedirs for ~/public_html, and httpd_use_nfs for content on NFS. List them all with getsebool -a.
5. A port that’s not allowed
Each service may only listen on ports labeled for it. Nginx on port 8081 fails to start, and the denial says name_bind with src=8081. See the labeled ports, then add yours:
sudo semanage port -l | grep -w http_port_tsudo semanage port -a -t http_port_t -p tcp 8081The same goes for SSH on another port, with ssh_port_t. The Port Lookup shows what each port is usually for.
6. Test, without turning it off
To check whether SELinux is really the cause, switch it to permissive for a moment: it logs, and doesn’t block.
getenforcesudo setenforce 0# testsudo setenforce 1If it works in permissive mode, the fix is one of the above. Turn enforcing back on right away. It resets to the config file’s setting on reboot anyway.
If none of the cases fit, audit2allow can write a small custom policy from the denials. Read what it proposes: it allows exactly what was denied, which may be more than you want.
Warning
Don’t set SELINUX=disabled, as many guides suggest. It removes a layer that contains a compromised service, and turning it back on later means relabeling the whole disk at boot. Fix the label, boolean or port, which takes one command.
Shell, we have a problem!
Similar, but different: a plain Permission denied with no AVC line is ordinary permissions: see Nginx 403 Forbidden. Job for service failed often hides an AVC denial, so check ausearch when a service won’t start. No route to host on RHEL is usually firewalld, not SELinux.
Found this useful? Share it.
Found a mistake? Let us know at contact@ohsudo.com.