很多 Linux 服务器上的“灵异”故障,根因其实不是代码也不是网络,而是 SELinux。典型表现是:服务进程明明在跑、端口也在监听,可一访问就 403 或连接被拒,关掉防火墙也没用。本文以 Nginx 反代、自定义 Web 目录 403、容器挂载为例,手把手用 SELinux 三板斧(getenforce / ausearch / semanage)定位并修复 AVC 拒绝,既保住系统安全又解决访问问题。
一、SELinux 到底拦了什么
SELinux(Security-Enhanced Linux)是内核级的强制访问控制(MAC)机制,它和传统的 DAC(文件 rwx 权限)是两套独立体系。一句话理解:即使文件权限是 755、进程以 root 运行,只要 SELinux 的类型(type)规则不允许,访问照样被拒——这就是所谓的“默认拒绝”。
在 SELinux 的 Targeted 策略里,每个进程运行在某个域(domain)中,每个文件/端口带一个类型(type)标签。规则只允许“域”访问特定“类型”。例如 Nginx 进程跑在 httpd_t 域,默认只能读 httpd_sys_content_t 类型的网页文件;如果你把站点放在 /srv/web 却没改标签,403 就来了。
为什么“关了防火墙还是不行”
这是新手最容易踩的坑:firewalld / iptables 管的是“网络能不能进来”,SELinux 管的是“进程能不能碰这个资源”。两者是叠加关系,关掉前者并不能绕过后者。排查访问类故障时,一定要把 SELinux 纳入怀疑名单,尤其是刚迁移目录、改了端口、启了容器之后。
二、三类最常被 SELinux 拦住的场景
下面结合真实排障经验,列出最高频的三类“服务在跑却用不了”的 SELinux 拦截。
1. 端口被拒:Nginx 反代连不上后端
把 Nginx 配成反向代理,后端监听在非标准端口(如 8080、9000)时,httpd_t 域默认只允许连接 http_port_t 里的端口。结果就是 Nginx 启动正常,但 proxy_pass 时报“Permission denied”。相关内容可参考本站 Nginx 反向代理实战。
2. 文件被拒:自定义 Web 目录返回 403
把网站根目录从 /var/www 挪到 /srv/web 或 /home 下,文件标签没跟着变,Nginx 读不到就 403。这不是权限问题,是类型不匹配。
3. 容器挂载目录被拒
Docker 把宿主机目录挂进容器后,容器内进程访问宿主机文件时也可能被 SELinux 拦,表现为“挂载成功却读不了”。这点常和 Docker 运维 的权限问题混淆。
这三类问题有一个共同信号:在日志和进程层面往往看不到明确的权限报错,但功能就是不通。养成一个肌肉记忆——只要遇到“明明该能访问却访问不了”的怪事,第一时间 getenforce 看一眼模式,常常能省下几个小时瞎猜防火墙、查网络、翻代码的弯路。
三、三板斧快速定位 AVC 拒绝
AVC(Access Vector Cache)拒绝会被记到审计日志里。先用下面两条确认当前状态:
# 查看当前模式:Enforcing / Permissive / Disabled
getenforce
# 查看详细状态(模式、策略、布尔开关统计)
sestatus如果处于 Enforcing,而服务又异常,下一步直接挖 AVC 拒绝日志:
# 查看最近的 AVC 拒绝记录
ausearch -m AVC -ts recent
# 把拒绝记录转成可读的允许规则(仅用于分析,别直接管道执行!)
ausearch -m AVC -ts recent | audit2allow
# 实时跟踪:开两个终端,一边复现故障一边看拒绝
tail -f /var/log/audit/audit.log | grep AVC看到形如 denied { read } for pid=... comm="nginx" name="index.html" dev="sda1" scontext=... tcontext=... tclass=file 的记录,基本就能锁定是哪个域、哪个目标类型被拦了。
四、临时与永久关闭(应急止血)
定位期间,可以先把 SELinux 切到 Permissive(宽容)模式:它会记录拒绝但不真正拦截,方便你确认“是不是 SELinux 的锅”。
# 运行时切换到 Permissive(立即生效,重启后失效)
setenforce 0
# 切回 Enforcing
setenforce 1要永久生效,改配置文件(重启才生效):
# /etc/selinux/config
SELINUX=permissive # enforcing | permissive | disabled
SELINUXTYPE=targeted注意:disabled 和 permissive 不一样。disabled 是彻底关闭,重新开启需要重启并给全文件系统重新打标签(relabel),耗时且易出事;permissive 只是不拦截,标签体系还在。应急尽量用 permissive,不要直接 disabled。
五、推荐做法:最小权限放行(而不是关掉它)
SELinux 拦你是有道理的,正确姿势是按最小权限放行,而不是一怒之下关掉。下面三招覆盖 90% 场景。
5.1 放行自定义端口
# 查看 http 相关端口类型已允许的端口
semanage port -l | grep http_port_t
# 把 8080 加入 http_port_t,Nginx 反代即可连通
semanage port -a -t http_port_t -p tcp 80805.2 修复文件安全上下文
用 semanage fcontext 登记“目录→类型”的映射,再用 restorecon 应用,比临时 chcon 更持久(chcon 在 relabel 后会丢失)。
# 登记规则:/srv/web 及其子内容都标为 httpd_sys_content_t
semanage fcontext -a -t httpd_sys_content_t "/srv/web(/.*)?"
# 立即应用(递归、显示变更)
restorecon -Rv /srv/web
# 校验某个文件的当前类型
matchpathcon /srv/web/index.html5.3 打开布尔开关
很多“服务能不能干某事”被抽象成布尔开关,改它最干净:
# 列出与 httpd 相关的布尔开关
getsebool -a | grep httpd
# 允许 httpd 发起网络连接(反向代理刚需)
setsebool -P httpd_can_network_connect on
# 允许 httpd 访问 NFS/CIFS 等共享文件
setsebool -P httpd_use_nfs on这些开关与 Linux 服务器安全加固 是同一体系,加固时不该粗暴关 SELinux,而该精准放行。
一个容易踩的小坑:chcon 只是临时改标签,一旦文件系统重新打标签(relabel)或你手动执行了 restorecon,改动就会丢失,因此它只适合应急验证“是不是类型不对”。真正要持久生效,一律走 semanage fcontext 登记规则、再 restorecon 应用的组合,这样即使重打标签也不会回退。
六、容器场景:Docker 与 SELinux
Docker 默认会给挂载的卷打上 svirt_sandbox_file_t 等容器类型,但经常需要显式声明。最实用的两个挂载后缀:
# :z 让挂载目录在多个容器间共享(重新打标签)
docker run -v /data/app:/app:z myimage
# :Z 私有标签,仅当前容器可访问
docker run -v /data/app:/app:Z myimage
# 完全禁用对该容器的 SELinux 限制(不推荐,仅调试)
docker run --security-opt label=disable myimage如果容器要给 cgroup 写数据,还需:setsebool -P container_manage_cgroup on。记住优先用 :z/:Z 而不是 label=disable,保留隔离收益。
七、排障清单:症状 → 命令 → 修复
| 症状 | 快速诊断 | 修复手段 |
|---|---|---|
| Nginx 反代连接被拒 | ausearch -m AVC 见 comm=nginx | semanage port -a -t http_port_t -p tcp 端口 |
| Web 目录 403 | matchpathcon 类型不符 | semanage fcontext + restorecon |
| 进程读文件 denied | scontext/tcontext 不匹配 | chcon 临时或 fcontext 持久化 |
| 容器挂载读不了 | 宿主机文件类型非容器型 | 挂载加 :z / :Z |
| 服务起不来也不报明显错 | setenforce 0 后恢复 | 定位后最小权限放行,再 setenforce 1 |
八、小结
SELinux 看似“多管闲事”,实则是生产环境重要的纵深防御。排障口诀就一句:Permissive 定位、Enforcing 上线、别用 disabled 糊弄。先用 getenforce 确认模式,用 ausearch 抓 AVC 拒绝,再用 semanage + restorecon / setsebool 精准放行。把这套三板斧记牢,下次再遇到“服务在跑却用不了”的怪事,就能十分钟内定位收工。




