Linux 里rwx这三个字母,放到文件和目录上完全不是一回事。同样是r,在文件上是"能不能读内容",在目录上是"能不能列出里面有哪些文件";同样是x,在文件上是"能不能当程序执行",在目录上是"能不能进入这个目录"。很多人第一次遇到Permission denied就卡在这里:明明ls -l看着权限挺正常,cat却读不了;明明文件是777,非属主用户还是删不掉。问题不在文件本身,而在它所在的目录。
这篇文章把这件事拆开讲清楚。先从ls -l的权限位读法讲起,再搭一个干净的测试环境,把文件上的r、w、x逐个验证一遍,再把目录上的r、w、x逐个验证一遍,中间穿插组合矩阵和真实报错。后面继续讲数字权限、chmod、chown、umask、setuid/setgid/sticky 这几位特殊权限,再给一份按现象查原因的排错表,最后落到工程上的几条实践建议。
适合谁看:刚接触 Linux 的后端开发、需要维护服务器的运维同学、经常在容器里改挂载目录权限的人,以及被Permission denied反复折腾过的读者。全文的命令都可以直接复制执行,实验目录统一放在/tmp/perm_lab,不会污染系统目录。
1. rwx 权限位速览:文件与目录的语义差异
先给结论,再讲细节。Linux 的权限模型把每个文件的访问者分成三类:所有者(owner,简写 u)、所属组(group,简写 g)、其他人(others,简写 o)。每一类都有一套独立的rwx三位权限,合起来就是ls -l里那串 9 个字符。这套模型对普通文件和目录一视同仁地套用,但同一个字母在两种对象上表达的能力完全不同,这是所有混淆的源头。
文件上的三位权限含义比较直观:
| 权限位 | 对文件的作用 | 典型操作 |
|---|---|---|
r | 读取文件内容 | cat、less、head、grep、被程序读取 |
w | 修改文件内容 | echo >>、vim保存、sed -i、截断写入 |
x | 把文件当作程序执行 | ./script.sh、执行二进制、被解释器加载 |
目录上的三位权限则是一套"目录项管理"的语义,重点在于目录本质是一个名字到 inode 的映射表:
| 权限位 | 对目录的作用 | 典型操作 |
|---|---|---|
r | 列出目录内的条目名称 | ls dir、find dir -maxdepth 1 |
w | 在目录内创建、删除、重命名条目 | touch、mkdir、rm、mv、ln |
x | 进入目录、穿透目录访问其下条目 | cd dir、cat dir/file、stat dir/file |
把这两张表放在一起看,一个关键差异就出来了:目录的w权限管的是"目录项"的增删改,而不是"目录里文件的内容"。也就是说,一个用户能不能删除某个文件,取决于他对该文件父目录有没有w和x,跟文件自己的w位没有关系。这一点在后面第 5 节会用实验完整验证,也是日常排障里最高频的认知偏差。
还有一个容易被忽略的细节:目录的x权限是"穿透"权限。没有x,即使你有r能看到文件名,也无法cd进去,无法cat里面的文件,无法stat里面的 inode。反过来,只有x没有r,你进得去、能访问已知路径下的文件,但ls会失败。这四种组合(r、x、rx、无)在实际使用中都有明确用途,后面会给完整矩阵。
2. ls -l 输出怎么读:权限串、链接数、属主属组
在动手做实验之前,先把ls -l的每一列讲清楚,不然后面看到输出还是懵。执行一条命令,输出大致长这样:
ls -l /etc/passwd /etc/shadow /tmp-rw-r--r--. 1 root root 2846 Mar 12 09:11 /etc/passwd ----------. 1 root root 1523 Mar 12 09:11 /etc/shadow drwxrwxrwt. 5 root root 4096 Mar 14 20:02 /tmp第一列是权限串,共 10 个字符。第 1 位是文件类型:-表示普通文件,d表示目录,l表示符号链接,c表示字符设备,b表示块设备,s表示 socket,p表示命名管道。第 2 到第 4 位是所有者权限,第 5 到第 7 位是所属组权限,第 8 到第 10 位是其他人权限。上面的/etc/passwd是-rw-r--r--,翻译过来就是:所有者可读写、组可读、其他人可读。
第二列是硬链接数,对目录来说它至少是 2(.和父目录里的条目),每多一个子目录就加 1。第三列是所有者,第四列是所属组,第五列是大小(目录的大小通常是 4096 字节,指的是目录项占用的块,不是里面文件的总大小),后面是修改时间和路径。注意/etc/shadow那串----------,只有 root 能读写,普通用户连看都看不到内容,这就是r位在文件上的直接体现。
再补充一个更好用的命令stat,它会把八进制权限和符号权限一起打出来:
stat -c '%A %a %U:%G %n' /etc/passwd /etc/shadow /tmp-rw-r--r-- 644 root:root /etc/passwd ---------- 000 root:root /etc/shadow drwxrwxrwt 1777 root:root /tmp%a输出的是八进制形式,/tmp显示成1777,多出来最前面的那个1就是粘滞位(sticky bit),这个在第 7 节详细讲。日常排障时建议养成用stat看权限的习惯,它比ls -l更精确,尤其是在排查特殊权限位和 ACL 的时候。
3. 测试环境准备:造一个干净的权限实验室
讲权限最怕纸上谈兵,所以下面所有结论都用命令验证一遍。准备一台 Linux 机器,Ubuntu 20.04 及以上或 CentOS 7 及以上都行,物理机、虚拟机、云主机、本地 WSL 都可以。需要 root 和一个普通用户,下面的例子用tester作为普通用户,如果你的系统里没有,用useradd建一个。
先建实验目录和测试用户:
# 需要 root 权限执行 useradd -m tester 2>/dev/null || echo "tester 已存在" mkdir -p /tmp/perm_lab cd /tmp/perm_lab # 造一个测试文件和一个测试目录 echo "hello permission" > file.txt mkdir dir_a echo "inside dir_a" > dir_a/inner.txt # 确认实验对象 ls -l /tmp/perm_lab-rw-r--r-- 1 root root 17 Mar 14 20:10 file.txt drwxr-xr-x 2 root root 24 Mar 14 20:10 dir_a默认情况下,root 创建的普通文件是644,目录是755,这是由umask决定的,后面第 6 节会讲。现在文件属主是 root,tester属于 others,others 位是r--,所以tester能读不能写。这个基线正好用来做后面的对照实验。
还有一个必须注意的环境细节:如果你登录的账号本身是 root,很多实验会"看起来都成功",因为 root 几乎不受权限位限制。所以后面测权限时,要么用su - tester切过去,要么用sudo -u tester单条命令执行。建议统一用sudo -u tester,不切换会话,输出更干净:
# 用 tester 身份执行单条命令,推荐这种写法 sudo -u tester cat /tmp/perm_lab/file.txt # 或者切到 tester 的会话里做连续操作 su - tester4. 文件权限实测:r、w、x 分别能做什么
这一节把文件上的三位权限逐个验证。每次改权限都用chmod,测试用sudo -u tester以普通用户身份执行,观察返回结果。
4.1 文件 r 权限:能不能读内容
先把文件权限收紧到000,只给 others 读权限,其他都去掉。为了干净,直接改成004(只有其他人可读):
chmod 004 /tmp/perm_lab/file.txt stat -c '%A %a %U:%G %n' /tmp/perm_lab/file.txt-------r-- 004 root:root /tmp/perm_lab/file.txt现在tester能读不能写,验证一下:
sudo -u tester cat /tmp/perm_lab/file.txthello permission读成功。再把读权限去掉,改成000,再试一次:
chmod 000 /tmp/perm_lab/file.txt sudo -u tester cat /tmp/perm_lab/file.txtcat: /tmp/perm_lab/file.txt: Permission denied报错是Permission denied,不是No such file or directory。这个区别很重要:如果报的是"没有这个文件",说明问题出在路径解析上,多半是某个上级目录缺x;如果是"权限不够",说明路径能走到,卡在目标文件自身的权限上。排障时先看报错类型,能省一半时间。
值得单独提一句的是,r位对"读取"是统一的,但对"执行"没有帮助。有些人以为r就代表文件可以跑,其实脚本能不能执行取决于x位,而解释器(bash、python)读取脚本内容时才需要r。所以一个--x的脚本文件,如果你用bash script.sh方式调用,会因为 bash 读不到内容而失败;只有./script.sh这种方式才只看x。
4.2 文件 w 权限:能不能改内容
继续测写权限。把文件恢复成644,让tester属于 others,此时 others 只有r:
chmod 644 /tmp/perm_lab/file.txt sudo -u tester sh -c 'echo "append" >> /tmp/perm_lab/file.txt'sh: 1: cannot create /tmp/perm_lab/file.txt: Permission denied写入失败,符合预期。现在把 others 的写权限打开,改成646:
chmod 646 /tmp/perm_lab/file.txt sudo -u tester sh -c 'echo "append line" >> /tmp/perm_lab/file.txt' cat /tmp/perm_lab/file.txthello permission append line写入成功。这里有个细节需要注意:>>是追加写,不会截断;如果用>覆盖写,同样是看w权限。但vim这类编辑器的行为和重定向不一样,它通常是"写临时文件再 rename",所以在某些目录权限受限的场景下,vim保存会失败并提示只读,而echo >>却可能成功。这不是权限模型不一致,而是编辑器自己做了保护性判断,排障时不要被这个现象带偏。
还要提醒一个常见误区:给文件加了w权限,不代表别人能删掉它。删除操作检查的是父目录的w权限,这一点第 5 节会专门验证。
4.3 文件 x 权限:能不能作为程序执行
x权限在文件上专门管"能不能被当作程序启动"。Linux 判断一个文件是不是可执行,看的就是x位,跟文件扩展名没有关系。准备一个脚本验证:
cat > /tmp/perm_lab/hello.sh <<'EOF' #!/bin/sh echo "script executed by $(id -un)" EOF chmod 644 /tmp/perm_lab/hello.sh sudo -u tester /tmp/perm_lab/hello.shbash: /tmp/perm_lab/hello.sh: Permission denied644没有x,直接执行被拒绝。加上x:
chmod 755 /tmp/perm_lab/hello.sh sudo -u tester /tmp/perm_lab/hello.shscript executed by tester执行成功。这里可以顺带验证一个反差:即使没有x,用解释器显式调用通常也能跑,因为这时只要求解释器能读到文件内容:
chmod 644 /tmp/perm_lab/hello.sh sudo -u tester sh /tmp/perm_lab/hello.shscript executed by tester这就是x和r的分工:x决定"能不能被内核当程序加载",r决定"能不能被读取内容"。对于一个744的脚本,所有者能直接跑,其他人只能读、不能跑;对于一个711的脚本,其他人能跑但读不到内容,所以不能复制、不能查看源码。生产环境上,把脚本设成711其实是保护源码的一种土办法,代价是排查问题时看不到内容,可维护性变差,一般不建议。
4.4 文件权限的最小充分组合
把上面的实验收一收,可以归纳出几种常见组合的用途:
| 八进制 | 符号 | 常见用途 |
|---|---|---|
600 | rw------- | 私钥、.env、凭据文件,只有属主可读写 |
644 | rw-r--r-- | 普通配置文件、静态资源,所有人可读、属主可写 |
640 | rw-r----- | 属组内可读的配置,如日志、数据库配置文件 |
660 | rw-rw---- | 属组内协同编辑的文件 |
700 | rwx------ | 私有脚本、私有目录 |
755 | rwxr-xr-x | 可执行脚本、可被所有人读取的目录 |
750 | rwxr-x--- | 属组内可执行的脚本、属组内可进入的目录 |
777 | rwxrwxrwx | 谨慎使用,几乎总是过宽 |
600和644是最常打交道的两种。像 SSH 私钥这类文件,OpenSSH 会强制检查权限,如果被改成644会直接拒绝使用并提示 "UNPROTECTED PRIVATE KEY FILE",这不是 OpenSSH 多事,而是权限模型本来就不该让其他人读到私钥内容。类似的检查在很多工具里都有,遇到这类报错,先stat看一眼八进制权限,比翻文档快。
5. 目录权限实测:r、w、x 的真实边界
这一节是全文的重点。目录的权限语义和文件完全不同,而且组合起来的表现更反直觉,必须靠实验建立直觉。
5.1 目录 r:能不能列出目录内容
先看r在目录上的作用。把dir_a改成744,也就是 others 只有r:
chmod 744 /tmp/perm_lab/dir_a stat -c '%A %a %n' /tmp/perm_lab/dir_adrwxr--r-- 744 /tmp/perm_lab/dir_a现在tester是 others,有r没有x。试两件事,一件是ls,一件是直接读里面的文件:
sudo -u tester ls /tmp/perm_lab/dir_a sudo -u tester cat /tmp/perm_lab/dir_a/inner.txtinner.txt cat: /tmp/perm_lab/dir_a/inner.txt: Permission denied结果很典型:ls成功,能看到文件名inner.txt;cat失败。原因就是ls只需要目录的r权限(读取目录项列表),而cat需要先"穿过"目录找到inner.txt的 inode,这一步需要目录的x权限。只有r没有x的目录,能看到名字,但拿不到内容,也进不去。
这种权限在现实中几乎不会主动配置,因为它既不安全也不实用。但它解释了一个常见报错:cat: xxx: Permission denied而你ls明明看得到文件,八成就是上级某个目录缺x。
5.2 目录 x:能不能进入和穿透
把权限反过来,只给x不给r,改成711:
chmod 711 /tmp/perm_lab/dir_a stat -c '%A %a %n' /tmp/perm_lab/dir_a sudo -u tester ls /tmp/perm_lab/dir_adrwx--x--x 711 /tmp/perm_lab/dir_a ls: cannot open directory '/tmp/perm_lab/dir_a': Permission deniedls失败,因为列目录要r。但此时如果知道确切的文件路径,依然能访问:
sudo -u tester cat /tmp/perm_lab/dir_a/inner.txt sudo -u tester cd /tmp/perm_lab/dir_a && pwdinside dir_a /tmp/perm_lab/dir_a能读、能cd。这就是x的本质:它是"路径穿透"权限。只要路径上的每一级目录都有x,你就能走到目标文件;至于中间目录里还有什么别的文件,你不需要知道,也不能列出来。这种配置在 Web 服务器上非常常见:把静态目录设成711,外部进程可以通过已知 URL 路径取到具体文件,但列不出目录清单,起到一定的信息隐藏作用。
这里要强调一个高频事故场景:/home/xxx目录默认是700或750。当 Nginx、PHP-FPM、Tomcat 这类以独立用户运行的服务需要读取用户家目录下的文件时,经常报Permission denied,而文件本身明明是644。原因就是/home/xxx这个目录对服务用户没有x,路径走不通。解决方式是给目录补x(比如chmod o+x /home/xxx),或者更稳妥地把资源移出用户家目录,放到/var/www这类专用位置。
5.3 目录 w:创建和删除看的是父目录
这是权限模型里最容易被误解的一点。先看一个反直觉的现象:文件权限设成000,但父目录可写,文件照样能被删除。
chmod 755 /tmp/perm_lab/dir_a chmod 000 /tmp/perm_lab/dir_a/inner.txt sudo -u tester rm -f /tmp/perm_lab/dir_a/inner.txt ls -l /tmp/perm_lab/dir_atotal 0文件被删掉了,尽管它的权限是000。原因在于rm操作的本质是"从父目录的目录项里移除一条记录",修改的是父目录这个数据结构,跟文件自身权限无关。所以判断能不能删,看的是父目录有没有w(写目录项)和x(定位到条目)。
反过来验证:文件是777,但父目录不可写,也删不掉。
echo "test" > /tmp/perm_lab/dir_a/locked.txt chmod 777 /tmp/perm_lab/dir_a/locked.txt chmod 755 /tmp/perm_lab/dir_a sudo -u tester rm -f /tmp/perm_lab/dir_a/locked.txtrm: cannot remove '/tmp/perm_lab/dir_a/locked.txt': Permission denied755的目录,others 只有r-x,没有w,所以删不掉。给目录加上w:
chmod 757 /tmp/perm_lab/dir_a sudo -u tester rm -f /tmp/perm_lab/dir_a/locked.txt echo "exit code: $?"exit code: 0删除成功。这个实验的意义在于纠正一个非常普遍的误解:文件的权限位保护不了文件不被删除,保护文件不被删除的是目录权限。想阻止别人删你的文件,正确做法是把父目录设成不可写,而不是把文件改成444或者000。
顺带说明,目录的w单独存在没有意义,它必须配合x才能生效。因为要在目录里创建文件,得先"进得去"。一个200权限的目录,实际上什么也做不了。所以w和x在目录上基本是绑定使用的,-wx(3)和r-x(5)、rwx(7)才是有效组合。
5.4 目录 r、x 组合矩阵
把四种组合摊开看,一目了然:
| 目录权限 | 符号 | 能ls | 能cd | 能访问已知路径文件 | 能创建/删除 |
|---|---|---|---|---|---|
0 | --- | 否 | 否 | 否 | 否 |
1 | --x | 否 | 是 | 是 | 否 |
2 | -w- | 否 | 否 | 否 | 否 |
3 | -wx | 否 | 是 | 是 | 是 |
4 | r-- | 是 | 否 | 否 | 否 |
5 | r-x | 是 | 是 | 是 | 否 |
6 | rw- | 是 | 否 | 否 | 否 |
7 | rwx | 是 | 是 | 是 | 是 |
2、3、6这几种带w不带x的组合没有实际意义,4只能看名字不能访问内容,实际场景里基本只有1、5、7三种会在生产环境出现。5(r-x)就是最常见的目录权限,7用于需要协同写入的共享目录,1用于隐藏目录内容但保留路径可达。
6. 数字权限、chmod、chown 与 umask
前面一直用八进制改权限,这里把规则补全。rwx三位按r=4、w=2、x=1相加,得到 0 到 7 的单个数字,三组拼起来就是三位八进制数:
| 数字 | 符号 | 说明 |
|---|---|---|
0 | --- | 无权限 |
1 | --x | 仅执行 / 穿透 |
2 | -w- | 仅写 |
3 | -wx | 写 + 执行 |
4 | r-- | 仅读 |
5 | r-x | 读 + 执行 |
6 | rw- | 读 + 写 |
7 | rwx | 全部权限 |
chmod支持两种写法,八进制和符号。八进制适合精确定位到某个值,符号适合"在现有基础上增删某一位",各有适用场景:
# 八进制:直接设定完整权限 chmod 640 /tmp/perm_lab/file.txt # 符号:在现有基础上操作 chmod u+x /tmp/perm_lab/hello.sh # 属主加执行 chmod g-w /tmp/perm_lab/file.txt # 属组去掉写 chmod o=r /tmp/perm_lab/file.txt # 其他人设为只读 chmod a+r /tmp/perm_lab/file.txt # 所有人加读 chmod u=rwx,g=rx,o= /tmp/perm_lab/hello.sh # 一次设三组符号写法里的a代表 all(等价于ugo),=是"设为精确值",+和-是"增量调整"。批量修权限时,=比+和-更安全,因为它不依赖当前状态,结果是确定的。
关于chmod -R一定要谨慎。它是"递归地把同一个权限模式套用到目录下所有对象",而文件需要x、目录需要x的语义完全不同。一条chmod -R 777 /var/www会把所有 PHP 文件、所有静态资源全变成可执行,既破坏语义也埋下安全隐患。正确的递归写法应该分开处理:
# 目录统一 755,文件统一 644,这种写法比 chmod -R 777 安全得多 find /var/www -type d -exec chmod 755 {} \; find /var/www -type f -exec chmod 644 {} \; # 只对 shell 脚本补执行位 find /var/www -type f -name '*.sh' -exec chmod 750 {} \;chown负责改归属,chgrp负责改属组,chown也能一次改两者:
chown tester /tmp/perm_lab/file.txt chown tester:users /tmp/perm_lab/file.txt chown -R tester:tester /tmp/perm_labumask决定新建文件和目录的默认权限。它不是"直接设置权限",而是"从基准值里扣掉某些位"。文件基准是666,目录基准是777,umask的位会从基准里被减去。常见的umask 022意味着:文件666 - 022 = 644,目录777 - 022 = 755。如果umask是027,那文件是640,目录是750。验证方式很简单:
umask touch /tmp/perm_lab/newfile.txt mkdir -p /tmp/perm_lab/newdir stat -c '%A %a %n' /tmp/perm_lab/newfile.txt /tmp/perm_lab/newdir-rw-r--r-- 644 /tmp/perm_lab/newfile.txt drwxr-xr-x 755 /tmp/perm_lab/newdirumask可以在~/.bashrc、/etc/profile或服务的 systemd unit 里设置,属于"从源头控制默认权限"的手段,比事后chmod补救更可靠。
7. 特殊权限位:setuid、setgid、sticky
权限串一共 10 位,前面用了 9 位,还有一组特殊位会覆盖在x的位置上显示。这是权限管理里必须知道的一块,因为它在系统里到处都是。
| 特殊位 | 数值 | 出现在 | 文件 / 目录上的作用 |
|---|---|---|---|
| setuid | 4000 | 属主x位,显示为s或S | 文件:以文件属主身份执行;目录:Linux 上基本无效 |
| setgid | 2000 | 属组x位,显示为s或S | 文件:以文件属组身份执行;目录:新建文件继承目录属组 |
| sticky | 1000 | 其他人x位,显示为t或T | 目录:只有文件属主或 root 能删除该文件 |
/usr/bin/passwd是最经典的 setuid 例子。普通用户改自己的密码需要写/etc/shadow,而那个文件是000,普通用户根本没权限。之所以能改成功,是因为passwd这个二进制带了 setuid 位,运行时临时获得 root 身份:
stat -c '%A %a %U:%G %n' /usr/bin/passwd-rwsr-xr-x 4755 root:root /usr/bin/passwd注意属主位显示的是rws而不是rwx,那个s就是 setuid 的标志。如果大写S,说明 setuid 位设了但对应的x位没设,这个组合实际上是失效的,属于配置错误。
setgid 在目录上的用法更实用:给一个共享目录设 setgid 后,里面新建的文件和子目录会继承父目录的属组,而不是创建者自己的主组。团队协作目录基本都这么配:
mkdir -p /srv/team_share chown :devteam /srv/team_share chmod 2775 /srv/team_share stat -c '%A %a %U:%G %n' /srv/team_sharedrwxrwsr-x 2775 root:devteam /srv/team_share属组位的s出现,表示 setgid 生效。这样devteam组里任何人新建文件,属组都是devteam,组内成员都能读写,不需要每次手动chgrp。
sticky 位最典型的场景就是/tmp。/tmp是1777,所有人都能写,但每个人只能删自己的文件,靠的就是粘滞位:
stat -c '%A %a %n' /tmp mkdir -p /tmp/perm_lab/shared chmod 1777 /tmp/perm_lab/shared sudo -u tester touch /tmp/perm_lab/shared/tester_file sudo -u nobody touch /tmp/perm_lab/shared/nobody_file sudo -u nobody rm -f /tmp/perm_lab/shared/tester_filedrwxrwxrwt 1777 /tmp rm: cannot remove '/tmp/perm_lab/shared/tester_file': Permission deniednobody删不掉tester的文件,尽管目录是777。这就是粘滞位的价值:在"所有人可写"的目录里加一道"只能删自己的"限制。共享上传目录、临时交换目录都应该配1777。
8. 常见 Permission denied 排查表
权限问题报错信息往往很模糊,但结合操作类型和对象类型,基本能定位到原因。下面这张表按现象整理,覆盖日常遇到的大部分场景:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
ls能列出文件,cat报 Permission denied | 上级目录缺x穿透权限 | 逐级stat -c '%A %a %n'检查路径上每一级目录 | 给缺失的目录补x,如chmod o+x |
ls dir报 cannot open directory | 目录缺r权限 | stat看目录权限位 | 补r,或用已知路径直接访问 |
文件是777却删不掉 | 父目录缺w权限 | stat看父目录权限 | 给父目录补w,或换用有权限的目录 |
cd进不去目录 | 目录缺x | 检查目录 others/group 位 | 补x权限 |
脚本./run.sh报 Permission denied | 缺x位 | ls -l run.sh | chmod +x run.sh |
脚本./run.sh报 bad interpreter | shebang 路径错误或文件是 CRLF 换行 | head -1 run.sh、file run.sh | 修正 shebang,dos2unix转换换行 |
| SSH 私钥无法使用,提示 UNPROTECTED PRIVATE KEY | 私钥权限过宽 | stat看权限 | chmod 600 id_rsa |
| 服务读不到用户家目录下的文件 | 家目录700,服务用户无x | 检查/home/xxx权限 | 移资源到/var/www,或补o+x |
| 容器内写挂载目录失败 | 容器内 UID 与宿主目录属主不匹配 | id对比容器内外 UID,看宿主目录属主 | 对齐 UID,或调整目录属主/权限 |
| 共享目录新建文件属组不对 | 目录未设 setgid | stat看是否有s | chmod 2775并设好属组 |
| 所有人都能删别人文件 | 共享目录缺粘滞位 | stat看是否有t | chmod 1777 |
| root 也执行不了某个文件 | 文件所有x位全被去掉 | stat确认 | root 也至少需要一个x位,补上即可 |
最后一条值得展开说。很多人以为 root 完全不受权限约束,其实在"执行文件"这一件事上是有例外的:如果文件三个x位全是0,root 也会被拒绝执行。读、写文件 root 确实可以无视权限位,但执行不行。所以在000权限的文件上,哪怕用 root 也会报Permission denied,需要先补一个x位。这个细节在排查"我是 root 为什么也跑不了"的时候很有用。
另外,容器环境里的权限问题往往更绕。Docker 默认以 root 运行,但很多镜像为了安全会切到非 root 用户,此时容器内的 UID 和宿主目录的属主 UID 对不上,写挂载卷就会失败。排查方法是分别在容器内和宿主机执行id,对比 UID,然后决定是调整宿主目录属主,还是在启动时用--user指定 UID。
9. ACL 与 sudo:权限不够时的两种补充手段
传统rwx模型只有三组身份,遇到"只给某一个人单独开权限"的需求就没办法了。这时候有两个选择:ACL 和 sudo,用途不同,不要混用。
ACL 是在原有三组权限之外,给特定用户或组单独授权,属于权限模型的扩展:
# 查看 ACL getfacl /tmp/perm_lab/file.txt # 给 tester 单独授予读写权限 setfacl -m u:tester:rw /tmp/perm_lab/file.txt # 给 devteam 组授予只读权限 setfacl -m g:devteam:r /tmp/perm_lab/file.txt # 设置默认 ACL,让目录下新建文件自动继承 setfacl -d -m u:tester:rw /tmp/perm_lab/dir_a # 撤销 setfacl -x u:tester /tmp/perm_lab/file.txt设了 ACL 的文件,ls -l的权限串末尾会多一个+,例如-rw-rw-r--+。看到这个符号就要想到"还有 ACL 规则",单纯看rwx是不够的,必须用getfacl才能看到全貌。这是排查权限问题时很容易漏掉的一环。
sudo 解决的是另一个问题:临时提权执行某个命令。它和文件权限是两套机制,sudo走的是/etc/sudoers配置,不改文件的rwx位:
# 查看当前用户可用的 sudo 权限 sudo -l # 以指定用户身份执行命令,不切换会话 sudo -u tester cat /tmp/perm_lab/file.txt # 编辑 sudoers,务必用 visudo 做语法检查 visudo -c选择建议很明确:需要"某个用户长期访问某个文件或目录"用 ACL 或调整属组,需要"某个用户临时执行某类管理命令"用 sudo。不要用chmod 777去解决访问问题,那是把权限模型整个放弃掉,后续排查会更难。
10. 落地实践:几条能直接用的权限管理习惯
把上面这些实验收成几条可以直接照做的习惯,都是踩过坑之后总结出来的。
第一条,改权限之前先stat。不要凭ls -l的九位权限串下判断,stat -c '%a'能一眼看出八进制值、特殊位和 ACL 标记。排查Permission denied时,沿着路径从根到目标逐级stat,找到第一个缺x的目录,问题基本就定位了。这个动作花不了十秒,比反复试错快得多。
第二条,永远不要用777解决问题。777意味着任何人都能改、任何进程都能写,一旦出问题无法追溯,而且会掩盖真正的权限配置错误。需要共享写权限,用"设置属组 +2775+ setgid";需要临时目录,用1777加粘滞位;需要单人授权,用 ACL。这三种方案都比777精确。
第三条,新建服务的目录结构时,先定属主再定权限。顺序应该是:chown把目录交给服务运行用户,然后目录755、文件644,需要写的目录单独给775。反过来做(先chmod再chown)容易在中间状态触发问题,尤其是在服务已经启动的情况下。
第四条,敏感文件的权限要固定下来并做检查。私钥、.env、数据库配置、证书这类文件,统一600或640。如果做镜像或部署包,可以在构建阶段加一条校验,权限过宽直接让流水线失败,比事后审计有效。
第五条,chmod -R尽量不用,用find分开处理目录和文件。前面给过写法,这里再强调一次:目录和文件的权限语义不同,递归套用同一个值是错的。
第六条,排查容器内的权限问题时,先确认 UID。容器里id一下,宿主机id一下,再stat挂载目录,三个信息放在一起,问题基本就浮出来了。很多"容器写不进去"的报障,最后都落在 UID 不匹配上。
第七条,共享目录一律考虑粘滞位和 setgid。凡是"多个用户都要往里写"的目录,1777或2775是起点而不是终点,具体选哪个取决于要不要让用户之间互相删除文件。这个决定最好在设计阶段就定好,别等出事再补。
最后回到文章标题那句话:rwx对文件和目录的作用完全不同,而且目录的x比w更容易被忽略。记住两个判断就够用——能不能访问某个文件,看路径上每一级目录有没有x;能不能删除某个文件,看父目录有没有w和x。这两条覆盖了日常八成的权限困惑,遇到更复杂的场景,再回来翻第 5 节的组合矩阵和第 8 节的排查表。