2024 年,我在一台新服务器上部署服务,卡在了一个看起来再简单不过的问题上:服务启动时报 Permission denied,读不了它的配置文件。我看了一眼文件,ls -l 显示属主、属组都对得上,权限也是 644,服务用的就是属主那个账号去读——按我学过的那套 rwx 权限规则,这文件它【就该读得到】。我不信邪,直接 chmod 777 把权限开到最大,所有人可读可写可执行。结果服务再启动,Permission denied 一个字都没变。那一刻我是真的懵了:一个文件,权限已经 777 了,Linux 居然还在拒绝我?这件事逼着我把 Linux 的文件权限、目录权限、SELinux、ACL 这一整套彻底理清了。本文复盘这次实战。
问题背景
环境:CentOS 7(★ 默认启用了 SELinux),部署一个服务
事故现象:
- 服务启动报 Permission denied,读不了配置文件
- ls -l 看属主属组都对、权限也够
- chmod 777 之后,依然 Permission denied
现场排查:
# 1. 看文件的常规权限 —— 看起来毫无问题
$ ls -l /opt/myapp/config.yml
-rw-r--r-- 1 myapp myapp 2048 May 18 10:00 /opt/myapp/config.yml
# 属主 myapp,服务也用 myapp 跑,644 —— 该读得到啊
# 2. chmod 777 也没用
$ chmod 777 /opt/myapp/config.yml
$ ls -l /opt/myapp/config.yml
-rwxrwxrwx 1 myapp myapp 2048 ... # 权限拉满了
# —— 服务再启动,还是 Permission denied
# 3. ★ 看一眼 SELinux 是不是开着的
$ getenforce
Enforcing # ★ SELinux 处于强制模式!
# 4. ★ 看文件的 SELinux 安全上下文(-Z)
$ ls -Z /opt/myapp/config.yml
-rwxrwxrwx myapp myapp unconfined_u:object_r:default_t:s0 config.yml
# ^^^^^^^^^^
# ★ 类型是 default_t —— 这不是服务能访问的类型
# 5. 看 SELinux 的拒绝日志
$ ausearch -m avc -ts recent | tail
type=AVC ... denied { read } ... comm="myapp"
scontext=...:myapp_t:s0 tcontext=...:default_t:s0
# ★ 实锤:SELinux 拒绝了 myapp 进程读这个文件
根因(后来想清楚的):
1. 这文件的常规 rwx 权限,从头到尾【就没问题】——
所以 chmod 777 当然不管用,我修的根本不是病灶。
2. ★ CentOS 默认开着 SELinux。SELinux 是【独立于
rwx 之外的第二套权限系统】。
3. 一次文件访问,要【先过 rwx 这一关,再过 SELinux 这一关】,
两关全部放行才允许;任何一关拒绝,就是 Permission denied。
4. 这个配置文件是我从别处拷过来的,带着错误的
SELinux 类型标签 default_t;而 myapp 进程
(类型 myapp_t)的 SELinux 策略,不允许它读 default_t
类型的文件 —— 于是第二关把它拦下了。
5. ★ rwx 全绿,不代表能访问 —— 还有 SELinux 那一关。
chmod 改的是第一套权限,这次的锁在第二套上。
修复 1:读懂 rwx 与三组权限
# === ls -l 第一列那 10 个字符,逐位拆开 ===
$ ls -l config.yml
-rw-r--r-- 1 myapp myapp 2048 ... config.yml
# 第 1 位:文件类型 - 普通文件 d 目录 l 软链接 c/b 设备
# 第 2-4 位:属主(user)的权限 rw-
# 第 5-7 位:属组(group)的权限 r--
# 第 8-10 位:其他人(other)的权限 r--
# === rwx 三个权限位的含义 ===
# r (read) 读:看文件内容 / 列目录里的文件名
# w (write) 写:改文件内容 / 在目录里增删文件
# x (execute) 执行:把文件当程序运行 / ★ 进入目录(见修复 2)
# === 数字表示法:r=4 w=2 x=1,相加 ===
# 644 = rw- r-- r-- 属主读写,组和其他只读(配置文件常见)
# 755 = rwx r-x r-x 属主全权,其他可读可执行(程序/目录常见)
# 600 = rw- --- --- 只有属主能读写(私钥、密码文件)
# === ★ Linux 怎么用这三组权限判定一次访问 ===
# 它【不是】把三组权限累加,而是【三选一】:
# 1. 你是文件属主? -> 只看属主那 3 位,组和其他【无视】
# 2. 不是属主,但属于属组? -> 只看属组那 3 位
# 3. 都不是? -> 只看其他人那 3 位
# ★ 关键推论:如果你【是】属主,但属主位没有 r,
# 哪怕属组、其他位全是 rwx,你照样读不了 ——
# 因为你被归到了"属主"这一类,只看属主那 3 位。
# === 改属主、属组、权限 ===
$ chown myapp config.yml # 改属主
$ chgrp myapp config.yml # 改属组
$ chown myapp:myapp config.yml # 属主属组一起改
$ chmod 644 config.yml # 数字法
$ chmod u+x,g-w config.yml # 符号法:给属主加x、去掉组的w
$ chmod -R 755 /opt/myapp/ # -R 递归(★ 对目录要小心)
# === 看进程是用哪个用户跑的(权限判定的"你"是谁)===
$ ps -eo user,pid,comm | grep myapp
修复 2:目录的 x 权限——路径上每一层都要能进
# === ★ 一个超高频的坑:目录的 x 权限 ===
# 对【目录】来说,rwx 的含义和文件完全不同:
# r 能列出目录里有哪些文件名(ls)
# w 能在目录里【创建/删除/改名】文件
# x ★ 能"进入"这个目录、能访问目录里的文件
#
# ★ 最反直觉的一点:要访问 /a/b/c/file 这个文件,
# 你必须对【路径上的每一层目录】 /a、/a/b、/a/b/c
# 都拥有 x 权限 —— 缺任何一层的 x,都进不去,
# 哪怕 file 本身权限是 777。
# === 这次之外,最常见的"文件权限没问题却访问不了" ===
$ ls -ld /opt /opt/myapp
drwxr-xr-x 5 root root ... /opt
drwxr-x--- 5 myapp myapp ... /opt/myapp # ★ 其他人没有 x
# 如果一个【不属于 myapp 组】的用户想读 /opt/myapp 里的文件,
# 它在 /opt/myapp 这一层就被挡住了 —— 因为 other 位没有 x。
# === 一条命令检查整条路径的权限 ===
$ namei -l /opt/myapp/config.yml
f: /opt/myapp/config.yml
drwxr-xr-x root root /
drwxr-xr-x root root opt
drwxr-x--- myapp myapp myapp # ★ 一眼看出哪一层卡住
-rw-r--r-- myapp myapp config.yml
# ★ namei -l 把路径上【每一层】的权限都列出来 ——
# 排查"权限没问题却访问不了",这是第一神器。
# === r 和 x 对目录的微妙区别 ===
# 只有 x、没有 r:能进目录、能访问你【已知名字】的文件,
# 但 ls 列不出来(不知道有哪些文件)
# 只有 r、没有 x:能 ls 出文件名,但【进不去】、访问不了内容
# ★ 所以目录通常 r 和 x 一起给(5=r-x 或 7=rwx)。
# === 给目录批量改权限,别用 chmod -R 777 ===
# chmod -R 会把目录和文件【一视同仁】地改成同样的位。
# 想"目录 755、文件 644",用 find 分开处理:
$ find /opt/myapp -type d -exec chmod 755 {} \; # 目录 755
$ find /opt/myapp -type f -exec chmod 644 {} \; # 文件 644
修复 3:chmod 777 还是 Permission denied——SELinux
# === ★ SELinux:独立于 rwx 之外的【第二套权限系统】 ===
# 传统 rwx 叫 DAC(自主访问控制)。
# SELinux 叫 MAC(强制访问控制)。
# CentOS/RHEL 默认【开启】SELinux。
# ★ 一次访问:先过 DAC(rwx),再过 SELinux(MAC)——
# 两关都放行才允许。所以 rwx 给到 777,
# SELinux 那一关不放,照样 Permission denied。
# === 看 SELinux 当前是什么模式 ===
$ getenforce
# Enforcing 强制模式 —— 真的会拦截、会拒绝
# Permissive 宽容模式 —— 不拦截,但会把"本该拒绝"的记进日志
# Disabled 彻底关闭
# === ★ 看文件/进程的安全上下文(关键:-Z 参数)===
$ ls -Z config.yml
... unconfined_u:object_r:default_t:s0 config.yml
# 用户:角色:【类型】:级别
# ★ 排查 SELinux,核心就看那个【类型】(type,以 _t 结尾)。
$ ps -eZ | grep myapp # 看进程跑在什么类型(域)下
# === ★ 确认是不是 SELinux 在拦:看 AVC 拒绝日志 ===
$ ausearch -m avc -ts recent
$ grep denied /var/log/audit/audit.log | tail
# 看到 type=AVC ... denied —— 实锤是 SELinux 拒绝的。
# scontext 是发起方(进程)、tcontext 是被访问的对象。
# === sealert:把晦涩的 AVC 翻译成人话,还给修复建议 ===
$ sealert -a /var/log/audit/audit.log
# 它会直接告诉你"问题是什么、该执行什么命令修"。
# === ★ 正确的修法:修文件的类型标签,不是关 SELinux ===
# 临时改类型(重打标签时会被还原):
$ chcon -t etc_t /opt/myapp/config.yml
# ★ 永久修法:用 semanage 登记规则,再 restorecon 应用:
$ semanage fcontext -a -t etc_t '/opt/myapp/config.yml'
$ restorecon -v /opt/myapp/config.yml
# restorecon = 把文件的上下文,恢复成策略规定的【正确值】。
# === ★ 一个高频场景:拷贝文件丢了正确的上下文 ===
# cp 一个文件,新文件会带【目标目录】的默认上下文;
# mv 则会【保留源文件】的上下文 —— 这正是这次的坑:
# 文件是从别处拷/移过来的,带着错误的 default_t。
# 对策:文件就位后,统一 restorecon 一遍。
$ restorecon -Rv /opt/myapp/ # -R 递归整个目录
# === 实在要排除 SELinux 嫌疑,临时转 Permissive ===
$ setenforce 0 # 临时转宽容模式(重启失效)
# 如果转 0 之后问题消失 -> 实锤就是 SELinux。
# ★ 但别就让它一直关着 —— 定位后,用 restorecon 正经修。
修复 4:ACL——比 rwx 更精细的权限
# === rwx 只能管"属主/属组/其他"三类,不够用时上 ACL ===
# 场景:一个文件,属主是 A,你想让【特定用户 B】也能读,
# 但又不想把 B 加进属组、更不想对 other 开放 ——
# 传统 rwx 做不到,ACL 可以。
# === 看一个文件/目录的 ACL ===
$ getfacl /opt/myapp/config.yml
# user::rw-
# group::r--
# other::r--
# ★ ls -l 的权限位末尾若有个 "+",表示这文件【有 ACL】:
$ ls -l config.yml
-rw-rw-r--+ 1 myapp myapp ... # 注意那个 +
# === 给特定用户/组单独授权 ===
$ setfacl -m u:deploy:rx /opt/myapp/config.yml # 用户 deploy 可读+执行
$ setfacl -m g:ops:r /opt/myapp/config.yml # 组 ops 可读
$ setfacl -x u:deploy /opt/myapp/config.yml # 撤销 deploy 的 ACL
$ setfacl -b /opt/myapp/config.yml # 清空所有 ACL
# === ★ 目录的"默认 ACL":让新建文件自动继承权限 ===
$ setfacl -d -m u:deploy:rx /opt/myapp/
# -d = default。设了默认 ACL 后,这个目录里【今后新建】的
# 文件,会自动带上 deploy 可读执行的权限 —— 不用每次手动设。
# === ★ 排查时:别忘了 ls -l 末尾那个 "+" ===
# 一个文件如果有 ACL,光看 ls -l 的 rwx 会【看走眼】——
# 真实权限要 getfacl 才看得全。
# "rwx 看着对、就是访问异常",查一下有没有 ACL。
# === mask:ACL 的"权限天花板" ===
$ getfacl config.yml | grep mask
# mask::r-- # ★ mask 会【限制】group 和具名用户的最大权限
# 即使你 setfacl 给了某用户 rwx,但 mask 是 r,他实际也只有 r。
$ setfacl -m m:rwx config.yml # 需要时把 mask 放开
修复 5:特殊权限位与 umask
# === rwx 之外,还有三个特殊权限位 ===
# === SUID(4):执行时,临时获得【文件属主】的身份 ===
$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root ... # ★ 属主位是 s,不是 x
# 普通用户能改自己密码(要写 /etc/shadow,本只有 root 能写),
# 靠的就是 passwd 这个程序带 SUID,执行时临时变成 root。
$ chmod u+s 文件 # 加 SUID
# ★ 安全敏感:可写的 SUID 程序是巨大的提权漏洞,定期审计:
$ find / -perm -4000 -type f 2>/dev/null # 找出所有 SUID 程序
# === SGID(2):目录上常用 —— 新建文件自动继承目录的属组 ===
$ chmod g+s /shared/project
# ★ 在加了 SGID 的目录里新建的文件,属组会自动跟目录一致——
# 多人协作的共享目录,几乎必加 SGID。
# === Sticky(1):目录上 —— 文件只有属主能删 ===
$ ls -ld /tmp
drwxrwxrwt 1 root root ... # ★ 其他位是 t
# /tmp 人人可写,但加了 sticky 位后,你只能删【自己的】文件,
# 删不了别人的 —— 防止公共目录里互相乱删。
$ chmod +t /some/shared/dir
# === ★ umask:新建文件/目录的【默认权限】从哪来 ===
$ umask
0022
# 新文件的权限 = 基准权限 - umask:
# 文件基准 666,umask 022 -> 新文件 644
# 目录基准 777,umask 022 -> 新目录 755
# ★ 一个隐蔽的坑:某服务用了过严的 umask(如 077),
# 它新建的文件别的账号一律读不了 ——
# "服务自己建的文件,别人访问不了",查它的 umask。
$ vim /etc/profile # 全局 umask 配在这类文件里
# === 看文件全部的特殊位 ===
$ stat config.yml # 详尽信息:权限、属主、时间戳…
修复 6:权限排查纪律
# === 这次事故暴露的权限认知盲区,定几条纪律 ===
# === 1. ★ chmod 777 解决不了的 Permission denied,查 SELinux ===
$ getenforce # 在 Enforcing?
$ ls -Z 文件 # 看安全上下文类型对不对
$ ausearch -m avc -ts recent # 看有没有 AVC denied
# rwx 全绿还被拒,病灶在 rwx 之外。
# === 2. 权限判定是"三选一",不是"三累加" ===
# 是属主就只看属主位 —— 属主位没 r,组位再全也白搭。
# === 3. ★ 文件权限对、却访问不了,查【路径上每层目录】的 x ===
$ namei -l /完整/路径/文件
# 路径上缺任何一层目录的 x,都进不去。
# === 4. ls -l 末尾有 "+",说明有 ACL,要 getfacl 看全 ===
# 别只信 ls -l 的 rwx,ACL 会让真实权限和它不一致。
# === 5. cp 会带目标目录的 SELinux 上下文,文件就位后 restorecon ===
$ restorecon -Rv /目录/
# 这次的坑就是拷贝文件丢了正确上下文。
# === 6. 别动不动 setenforce 0 / 关 SELinux 了事 ===
# 临时 setenforce 0 只用来【确认】是不是 SELinux;
# 确认后用 chcon / semanage + restorecon 正经修,再开回来。
# === 7. 排查权限问题的命令链 ===
$ ls -l 文件 # ① 常规 rwx、属主属组
$ namei -l /路径/文件 # ② 路径每层目录的 x 够不够
$ getfacl 文件 # ③ 有没有 ACL 在影响
$ getenforce; ls -Z 文件 # ④ SELinux 模式和上下文
$ ausearch -m avc -ts recent # ⑤ 有没有 SELinux 拒绝日志
# 按这个顺序,权限问题基本能定位。
命令速查
需求 命令
=============================================================
看文件权限/属主 ls -l 文件
看路径上每层目录的权限 namei -l /完整/路径/文件
改权限 / 改属主 chmod 644 文件 / chown 用户:组 文件
看 SELinux 模式 getenforce
看文件的 SELinux 上下文 ls -Z 文件
看 SELinux 拒绝日志 ausearch -m avc -ts recent
修复 SELinux 上下文 restorecon -Rv /目录/
登记 SELinux 上下文规则 semanage fcontext -a -t 类型 路径
看 ACL getfacl 文件
给特定用户授权(ACL) setfacl -m u:用户:rx 文件
找出所有 SUID 程序 find / -perm -4000 -type f
口诀:chmod 777 还报 denied 就查 SELinux -> ls -Z 看上下文
文件权限对却进不去 -> namei -l 查路径每层目录的 x
避坑清单
- chmod 777 仍报 Permission denied,病灶在 rwx 之外,优先查 SELinux
- SELinux 是独立于 rwx 的第二套权限,两关都放行才允许访问
- 权限判定是三选一:是属主就只看属主位,组和其他位被无视
- 访问文件要对路径上每一层目录都有 x 权限,缺一层都进不去
- 目录的 x 是"能进入",r 是"能列文件名",通常要一起给
- ls -l 末尾有 + 号说明文件有 ACL,真实权限要 getfacl 才看得全
- cp 会带上目标目录的 SELinux 上下文,文件就位后要 restorecon
- 别用 setenforce 0 关 SELinux 了事,要用 restorecon/semanage 正经修
- chmod -R 会把目录和文件改成同样权限,应该用 find 分开处理
- 服务新建的文件别人读不了,可能是它的 umask 设得过严
总结
这次"chmod 777 之后依然 Permission denied"的事故,纠正了我一个埋藏得极深的认知:我一直把 Linux 的文件权限,理解成一套【唯一】的、由 rwx 那九个字符完整定义的系统。在我的脑子里,判断一个进程能不能访问一个文件,只有一道关卡,那就是 rwx;而 chmod 777,则是这道关卡的"万能钥匙"——把权限开到所有人可读可写可执行,这个世界上就不该再存在 rwx 拦得住的访问了。正是这个根深蒂固的"唯一一道关卡"的认知,让我在 chmod 777 之后还看到 Permission denied 时,陷入了彻底的困惑——在我那套世界观里,这是一件不可能发生的事。复盘到根上,我才终于明白,我的世界观里少了一整套东西。rwx 那套权限,有一个正式的名字,叫 DAC,自主访问控制——它的特点是"自主",文件的属主可以自己决定把权限放给谁。但在 CentOS、RHEL 这类发行版上,默认还【同时】运行着另一套完全独立的权限系统:SELinux,它属于 MAC,强制访问控制。这两套系统的关系,是我这次才真正想清楚的关键:它们不是替代关系,而是【串联】关系。一个进程要访问一个文件,这次访问会被【先后送过两道独立的关卡】——第一道是 rwx,第二道是 SELinux;只有当两道关卡【全都放行】,访问才被允许;而无论哪一道关卡拒绝,用户在上层看到的,都是同一句、毫无区别的 Permission denied。这就解释了我那个"不可能的故障":我的 chmod 777,确确实实把第一道关卡 rwx 开到了最大,这道关卡此后对谁都放行。可问题是,我那个配置文件的病灶,从一开始就不在第一道关卡上——ls -l 显示的属主、属组、权限位,本来就全是对的。真正拦住它的,是第二道关卡 SELinux。这个配置文件是我从别处拷贝过来的,而 SELinux 给每个文件都打着一个叫"安全上下文"的标签,其中最关键的是一个"类型";拷贝这个动作,让这个文件带上了一个错误的类型标签 default_t,而我那个服务进程所运行的 SELinux 域,其策略根本不允许它去读 default_t 类型的文件。于是,无论我把 rwx 这第一道关卡开得多大,第二道关卡 SELinux 都纹丝不动地把它拦在门外。我之前所有的努力,都用错了地方——我拿着第一道关卡的钥匙,反复去开一把根本锁在第二道关卡上的锁。想通这一层,正确的排查路径就清晰了:用 getenforce 确认 SELinux 确实处于强制模式,用 ls -Z 看到那个扎眼的、错误的 default_t 类型,再用 ausearch 在审计日志里找到那条白纸黑字的 AVC denied 记录——三步下来,真凶无所遁形。而真正的修复,也不是我一开始想用的那种粗暴办法(setenforce 0 直接把 SELinux 关掉),而是用 restorecon 把这个文件的安全上下文,恢复成 SELinux 策略所规定的那个正确的类型。这次事故让我刻进脑子里的一条纪律是:当一个 Permission denied,连 chmod 777 都无法将它消除时,这本身就是一个极其强烈的信号——它在告诉你,真正的病灶,根本不在 rwx 这道关卡上,你该立刻把目光投向 rwx 之外,投向 SELinux,投向 ACL,投向那些你平时根本意识不到、却实实在在串联在访问路径上的其他关卡。这次从一个"不可能"的报错出发,我最大的收获,是终于拆掉了脑子里那个"权限只有 rwx 一道关"的简化模型,换上了一个更接近真相的认识:在一台现代 Linux 服务器上,一次文件访问的背后,是一道接一道的关卡,chmod 能打开的,永远只是其中的第一道而已。
—— 别看了 · 2026