<!DOCTYPE html>
很多站长拿到新 VPS 后的第一件事,就是照着网上的"优化教程"把 SELinux 关掉。理由无非是"影响性能""配置太麻烦""关了省事"。但 SELinux 是 Linux 内核级的强制访问控制(MAC)机制,是纵深防御里非常重要的一层。真正的问题不是 SELinux 本身难用,而是我们不了解它。这篇文章不讲玄学,直接从原理讲到实战排错,让你能放心地把 SELinux 开起来,并且遇到问题能在十分钟内定位、修复。
一、SELinux 是什么:三个必须搞懂的核心概念
传统 Linux 权限(rwx)属于自主访问控制(DAC):文件的所有者可以任意修改自己的权限。而 SELinux 属于强制访问控制(MAC):即使是 root,也要遵守策略里定义好的规则。理解 SELinux 只需要抓住三个概念:主体、客体和类型强制。
- 主体(Subject):发起访问的进程,比如 Nginx、SSHD。
- 客体(Object):被访问的资源,比如文件、目录、端口、Socket。
- 类型强制(Type Enforcement):SELinux 给主体和客体都打上"类型标签",规则描述"哪个类型的主体可以访问哪个类型的客体"。
在 CentOS / AlmaLinux / Rocky Linux 上,先确认当前状态:
# 查看 SELinux 状态(模式 + 策略类型)
sestatus
# 更简单的命令,只输出当前模式
getenforce
输出大致如下:
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
这里的 targeted 是默认策略:只对网络服务(httpd、sshd、named 等)进行约束,普通用户进程基本不受影响,这也是它性能开销极低的原因。
二、三种模式与切换:enforcing / permissive / disabled
SELinux 有三种模式,很多新手把 permissive 和 disabled 搞混:
- enforcing(强制):违反策略的操作被直接拒绝,并写入审计日志。生产环境应该用这个。
- permissive(宽容):违反策略的操作不拦截,只记录日志。这是排错时的"调试模式"。
- disabled(禁用):SELinux 完全不加载,连标签都不打。想重新启用必须改配置后重启。
临时切换模式(重启后失效):
# 切到 permissive,只记日志不拦截
setenforce 0
# 切回 enforcing
setenforce 1
# 查看当前是否生效
getenforce
永久修改写入 /etc/selinux/config:
# 修改这一行为 enforcing / permissive / disabled
SELINUX=enforcing
# 修改后必须重启才生效
reboot
排错要点:如果你的 SELinux 是 disabled 状态,直接 setenforce 1 会报错 setenforce: SELinux is disabled。此时必须修改配置文件并重启系统才能重新启用。反过来,只把模式设为 permissive 而不设为 disabled,就能保留完整的标签信息,方便排查。
三、文件与进程标签:ls -Z 与 restorecon
SELinux 一切规则都围绕"标签"展开。文件有文件标签,进程有进程标签。查看标签要用带 -Z 的选项:
# 查看文件的 SELinux 标签
ls -Z /usr/share/nginx/html/index.html
# 查看进程的 SELinux 标签
ps -Z | grep nginx
# 查看端口上下文
semanage port -l | grep http
输出类似:
system_u:object_r:httpd_sys_content_t:s0 /usr/share/nginx/html/index.html
system_u:system_r:httpd_t:s0 12345 ? 00:00:00 nginx
标签格式为 用户:角色:类型:级别,其中最重要的是第三段的类型。进程类型是 httpd_t,它能读的类型是 httpd_sys_content_t(静态内容)、httpd_sys_rw_content_t(可读写内容)等。
当站点目录被放到自定义位置(比如 /data/www)时,新目录没有正确的标签,Nginx 就会 403。修复命令:
# 1. 给目录定义默认标签规则(永久生效)
semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?"
# 2. 重新应用标签(把现有文件的标签刷成规则定义的值)
restorecon -Rv /data/www
# 3. 确认标签已经正确
ls -Zd /data/www
restorecon 按 fcontext 里登记的规则还原标签,是最安全的修法;chcon 只是临时改标签,重启或执行 restorecon 后会被还原,不建议依赖它。
四、布尔值开关:不写规则也能放行
很多常见需求(比如让 Nginx 反向代理时主动连接后端端口、连接数据库)SELinux 已经预置了开关,叫"布尔值"(boolean)。开开关比写自定义策略简单得多:
# 列出所有布尔值
getsebool -a
# 只看 httpd 相关的
getsebool -a | grep httpd
# 临时开启(重启后失效)
setsebool httpd_can_network_connect on
# 永久开启(-P 参数,写入策略,重启保留)
setsebool -P httpd_can_network_connect on
# 查看某个布尔值当前状态
getsebool httpd_can_network_connect
几个高频使用的布尔值:
# Nginx 作为反代连接后端端口
setsebool -P httpd_can_network_connect on
# 连接 MySQL / PostgreSQL
setsebool -P httpd_can_network_connect_db on
# 允许读写用户主目录(如 /home 下的站点)
setsebool -P httpd_read_user_content on
setsebool -P httpd_enable_home_dirs on
# 允许通过 NFS / CIFS 挂载点提供内容
setsebool -P httpd_use_nfs on
setsebool -P httpd_use_cifs on
排错要点:反代连不上后端、PHP 连不上数据库,先别急着改 iptables,用 getsebool 检查对应的布尔值往往一秒解决。
五、实战排错:Nginx 站点 403 的完整定位流程
下面演示一个真实场景:把站点从 /usr/share/nginx/html 挪到 /data/www/blog 后,浏览器访问直接 403。先确认不是权限问题:
# 权限和属主都正常
ls -ld /data/www/blog
# drwxr-xr-x root root /data/www/blog
那八成是 SELinux 标签不对。第一步,确认当前模式和是否有 AVC 拒绝日志:
# 查看最近 10 分钟内的 AVC 拒绝记录
ausearch -m AVC -ts recent
# 只查 httpd 相关的拒绝
ausearch -m AVC -c httpd -ts recent
会看到类似这样的记录:
type=AVC msg=audit(1699000000.123:456): avc: denied { read } for pid=12345
comm="nginx" name="index.html" dev="vda1" ino=67890
scontext=system_u:system_r:httpd_t:s0
tcontext=unconfined_u:object_r:default_t:s0
tclass=file permissive=0
关键在最后三行:主体 httpd_t 想读类型为 default_t 的文件被拒绝。说明新目录继承了默认标签而不是 httpd_sys_content_t。用 audit2why 还能给出修复建议:
ausearch -m AVC -ts recent | audit2why
修复就是第三节的方法,重新定义标签:
semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?"
restorecon -Rv /data/www
如果 Nginx 还要写文件(比如上传目录、FastCGI 缓存),把这个目录的类型换成可写类型:
# 上传目录需要可读写
semanage fcontext -a -t httpd_sys_rw_content_t "/data/www/blog/wp-content(/.*)?"
restorecon -Rv /data/www/blog/wp-content
# FastCGI 缓存目录
semanage fcontext -a -t httpd_cache_t "/var/cache/nginx(/.*)?"
restorecon -Rv /var/cache/nginx
六、实战排错:绑定非标准端口被拒
Nginx 监听 8080 端口启动失败,日志里却没有任何配置错误,多半也是 SELinux:策略默认只允许 httpd 监听 80、443 等标准端口,其余端口需要显式登记。报错信息可在审计日志中确认:
ausearch -m AVC -c nginx -ts recent
# 或者直接看 systemd 日志
journalctl -u nginx --no-pager | tail -30
修复:把端口登记到 http_port_t 类型下:
# 永久登记端口
semanage port -a -t http_port_t -p tcp 8080
# 查看当前已登记的 http 端口
semanage port -l | grep http_port_t
# 删除不再需要的登记
semanage port -d -t http_port_t -p tcp 8080
同理,SSH 改了非标准端口连不上时,用 semanage port -a -t ssh_port_t -p tcp 2222 即可。
七、排错方法论:三步定位法
遇到任何 SELinux 相关问题,按下面三步走,基本不会绕弯路:
- 确认模式:
getenforce。permissive 下还出问题,那根本不是 SELinux 拦截,回去查权限、防火墙、配置。 - 查 AVC 日志:
ausearch -m AVC -ts recent或journalctl | grep "avc:"。没有记录就继续查别的原因。 - 对症修复:按拒绝记录里给出的主体和客体类型,选择
semanage fcontext + restorecon、setsebool或semanage port三件套中的对应工具。
几个容易踩的坑:
- 不要用
setenforce 0长期运行,那是调试手段不是运维方案。 chcon改的标签在restorecon或系统重启后会被刷掉,要持久化必须用semanage fcontext。- 改了
/etc/selinux/config后必须重启,setenforce改不了 disabled 状态。 - CentOS 8+ 默认安装了
container-selinux,Docker 无法启动时先rpm -qa | grep selinux看策略包是否齐全。
八、常用命令速查表
sestatus # 查看整体状态
getenforce # 查看当前模式
setenforce 0|1 # 临时切换 permissive/enforcing
getsebool -a # 列出全部布尔值
setsebool -P 布尔值 on # 永久开启布尔值
ls -Z 路径 # 查看文件标签
ps -Z # 查看进程标签
restorecon -Rv 目录 # 按规则还原目录标签
chcon -t 类型 文件 # 临时修改标签
semanage fcontext -l # 列出自定义标签规则
semanage fcontext -a -t 类型 "路径" # 新增标签规则
semanage port -l # 列出端口上下文
semanage port -a -t 类型 -p tcp 端口 # 登记端口
ausearch -m AVC -ts recent # 查最近的 AVC 拒绝
audit2why # 分析拒绝原因并给建议
SELinux 不是"关掉就完事"的麻烦,而是 Linux 安全模型的最后一道防线。把常用的几个布尔值和 semanage fcontext 用熟,它带来的安全收益远大于配置成本。下次再看到"请先关闭 SELinux"的教程,你可以放心地把那条命令划掉。


