Linux权限管理详解:从ls -l、chmod到SGID与ACL实战

发布时间:2026/10/24 5:45:55
Linux权限管理详解:从ls -l、chmod到SGID与ACL实战

如果你在 Linux 上敲过一次ls -l,就一定见过那一串像密码一样的-rw-r--r--;如果你还写过脚本、部署过服务,大概率也被Permission denied拦下来过。权限是 Linux 里最基础、也最容易被跳过的一课:会用chmod 777不算会权限管理,真正理解“谁能对什么做什么”,才能避免误删、越权访问和线上故障。本文围绕 Linux 权限管理的第一层——权限本身,从ls -l的输出逐列拆解讲起,覆盖 rwx 对文件和目录的不同含义、数字与符号两种改权限写法、属主属组、umask 默认权限、SUID/SGID/Sticky 特殊位,最后用一个多用户协作目录的完整实战把这些知识串起来。无论你是刚接触 Linux 的学生,还是需要经常在服务器上排查问题的后端开发者,都可以跟着本文的命令在虚拟机里逐条跑一遍。

1. Linux 权限管理到底在管什么

1.1 从一次 Permission denied 说起

在 Linux 里,几乎所有的“权限问题”最终都会以两种形式暴露出来:一种是命令直接报错,比如bash: /root/test.sh: Permission denied、cp: cannot open 'x.log' for reading: Permission denied;另一种更隐蔽,命令不报错但结果不对,比如服务启动日志里出现“无法写入日志文件”,或者上传文件后属主变成了www-data导致后续处理失败。这两种现象看起来完全不同,根因却往往是同一个:当前进程的有效用户身份,对目标文件缺少它需要的那一位权限。

所以要理解权限,先要建立一个核心认知:Linux 的权限判定不是“你是管理员就全能”,而是“每一个进程都带着一个身份,内核拿这个身份去和目标文件的权限位做比对”。这个身份由三部分组成:真实用户 ID(real UID)、有效用户 ID(effective UID)、所属组集合(supplementary groups)。内核在做权限检查时,主要看的是有效 UID 和有效 GID。这就是为什么同一台机器上,你用root登录能改/etc/shadow,切换成普通用户就提示“权限不够”,文件本身没动,变的是请求者的身份。

理解了这一点,后面所有的chmod、chown、umask才有意义——它们本质上都是在调整“文件的权限位”或者“进程的身份”,让比对结果符合预期。

1.2 权限的三要素:属主、属组、其他

Linux 的权限模型是经典的 UGO 模型,即 User(属主)、Group(属组)、Other(其他人)。每一个文件或目录,都同时绑定了两个身份标签和一个权限三元组:

  • 属主(owner):文件的创建者默认就是属主,但可以通过chown修改。
  • 属组(group):文件归属的组,默认是属主的“主组”,也可以通过chgrp或chown :group修改。
  • 其他(other):不属于上面两类身份的任何人。

内核判断权限时的顺序是固定的,并且是“短路”的:先看你是不是属主,是就只按属主位判断,不再看组位;不是属主再看你是不是属组成员,是就只按组位判断;都不是,才走 other 位。这个顺序非常重要,因为它意味着权限位之间不是“或”的关系,而是“就近优先”。

举个容易踩坑的例子:某文件权限是-rw-r-----,属主alice,属组devs,你是devs组成员但不是 alice。这时你按组位判断,有读权限,能cat。但如果文件是-rw-------,属主 alice、属组 devs,你虽然是同组,组位是---,照样读不了。很多人误以为“我是这个组的,组位给了就有”,实际上组位是---就等于没给,组身份只是让你“有资格”走组位判断而已。

1.3 为什么普通开发者也需要掌握权限

有一种常见的说法是“权限是运维的事”。但在实际项目中,权限问题几乎必然会落到开发者头上:容器镜像里用非 root 用户启动进程,日志目录写不进去;CI 流水线拉代码后执行脚本,提示没有执行权限;上传目录被设成了777,安全扫描直接报高危;定时任务用crontab跑脚本失败,手动sh script.sh却能跑通(因为脚本没有 x 权限,你用sh显式解释执行绕过了检查)。

这些场景都不需要你成为运维专家,但需要你能读懂ls -l的九位权限、知道chmod改的是谁、知道目录的 x 位和文件的 x 位含义完全不同。本文后面的内容就按这个目标来组织:先把现象和表示法讲透,再进入实操。

2. 环境准备与查看权限的基本命令

2.1 实验环境说明

本文所有示例都在标准的 Linux 发行版上验证思路,主流的Ubuntu 20.04/22.04、CentOS 7/Stream、Rocky Linux、Debian都可以复现。由于不同发行版、不同版本在用户创建命令、默认 umask、SELinux 策略上存在差异,具体输出可能与本文示例略有出入,请以你机器的实际输出为准;本文重点讲的是权限模型的通用规则,这些规则在所有 Linux 发行版上是一致的。

准备工作很简单,建议在虚拟机或测试机上操作,不要在生产服务器上练手:

  • 一个可以sudo的普通账号,或者直接使用root(仅限实验环境)。
  • 至少创建两个普通用户,用于验证多用户场景下的权限差异。
  • 一个独立目录(例如/data/lab)作为实验区,方便用完直接删除。

如果你使用的是云主机或容器环境,请务必确认这是你有权操作的测试环境。权限相关的命令中,chown -R、chmod -R这类递归操作一旦作用到错误路径,恢复成本非常高,后面第 5 节会专门讲风险。

2.2 ls -l 输出逐列拆解

先来看最基础的一条命令:

ls -l /etc/passwd

典型输出如下:

-rw-r--r-- 1 root root 2841 Mar 12 10:20 /etc/passwd

这一行包含 8 个字段,逐个看:

字段示例值含义
第 1 列-rw-r--r--文件类型位 + 九位权限位
第 2 列1硬链接数(目录则是子目录数,不含.和..)
第 3 列root属主用户名
第 4 列root属组名
第 5 列2841文件大小(字节)
第 6~8 列Mar 12 10:20最后修改时间
第 9 列/etc/passwd文件名

第一列最关键。把它拆成四段:-rw-r--r--。第一位是文件类型位,后面九位分三组,每组三位,依次对应属主、属组、其他人。所以-rw-r--r--读作:这是一个普通文件,属主可读写,属组可读,其他人可读。

再看一个目录的例子:

ls -ld /tmp

输出:

drwxrwxrwt 15 root root 4096 Mar 12 10:22 /tmp

注意两点:第一,查看目录本身的权限要用ls -ld,直接ls -l /tmp列的是目录里面的内容;第二,这里的权限位是rwxrwxrwt,最后一位是t而不是x,这就是后面要讲的粘滞位(Sticky Bit)。

2.3 文件类型位对照表

第一位的取值有几种常见情况,认得它们能帮你快速判断“这是不是我以为的那个东西”:

字符类型说明
-普通文件文本、二进制、压缩包等
d目录directory
l符号链接类似 Windows 快捷方式
c字符设备如/dev/null、终端设备
b块设备如/dev/sda磁盘
s套接字文件socket,进程间通信
p命名管道FIFO

初学者常犯的一个错误是:看到一个文件是l开头,却拿chmod去改它的权限,改完发现不生效。原因是符号链接在 Linux 里权限位基本没有意义,内核在绝大多数操作中会直接“跟随”链接到目标文件,chmod link实际改的是目标文件(在部分系统上则什么都不做)。遇到这种情况,请先ls -l看链接指向哪里,再改真实路径。

2.4 用 id 和 groups 确认自己的身份

在排查权限问题之前,第一件事应该是确认“我现在到底是谁”。这一步很多人会跳过,结果在一台跳板机上折腾半天,最后发现sudo没加、或者当前 shell 是别的用户的会话。

whoami id groups

id的输出示例:

uid=1000(alice) gid=1000(alice) groups=1000(alice),1001(devs)

解释一下:uid是用户 ID,gid是主组 ID,groups是包含主组在内的所有组。权限判断中的“属组匹配”,看的是这个 groups 列表,而不只是主组。这一点非常关键:用户 alice 的主组是 alice,但她同时属于 devs 组,那么一个属组为 devs 的文件,她就能走组位判断。

如果你刚用usermod -aG devs alice把用户加进组,但groups里看不到 devs,八成是因为当前会话的组信息在登录时就确定了,必须重新登录(或者新开一个终端会话)才会生效。这是排查“明明加了组还是没权限”的头号原因,后面第 9 节会再提一次。

3. 九位权限位与 rwx 的真实含义

3.1 rwx 对文件的意义

对普通文件来说,r、w、x的含义相对直观:

  • r(read,4):可以读取文件内容。对应命令cat、less、cp(作为源)、grep等。
  • w(write,8):可以修改文件内容,包括追加和截断。注意它不包含删除文件的权力。
  • x(execute,1):可以把文件当作程序执行。对应./script.sh、直接运行二进制。

这里有两个高频误区值得展开。

第一个误区是“有 w 就能删文件”。实际上,删除一个文件本质上是修改它所在的目录(从目录的条目中移除这一项),所以能否删除文件取决于父目录是否给了你w和x,跟文件本身有没有w没关系。你可以做一个小实验:在一个你自己有写权限的目录里,创建一个权限为000的文件,然后尝试rm它,会发现能删掉。

第二个误区是“有 r 就能执行脚本”。脚本的x位决定的是“能否作为程序直接运行”。如果脚本只有r没有x,./script.sh会报Permission denied,但bash script.sh或sh script.sh依然能跑通,因为你只是把文件内容当作bash的输入读进去,bash并不要求输入文件有执行位。这就是“手动能跑、cron 跑不了”的经典原因之一——cron 里写的是/path/script.sh直接执行,而这个文件没有x。

3.2 rwx 对目录的意义(重点)

目录的权限含义和文件完全不同,这是 Linux 权限里最容易被忽略、也最容易出错的部分。请把下面这张表记住:

权限对目录的含义对应操作
r可以列出目录中的条目名ls、ls -l的文件名部分
w可以在目录中创建、删除、重命名条目touch、rm、mv、mkdir
x可以进入目录、访问其中的文件cd、按路径访问目录内文件

关键在于:目录的x位是“通行证”,r位只是“看得见”。这带来几种组合,理解它们就理解了目录权限:

  • r--(只有读):可以ls看到文件名,但ls -l拿不到文件属性(因为需要stat目录内的每个条目,涉及进入目录),也会提示Permission denied;cd进不去,也读不了里面的任何文件。
  • --x(只有执行):ls会报Permission denied,但如果你知道确切文件名,可以直接cat /dir/known.txt读到内容。这在实际中常被用来做“可猜测路径的弱保护”,但绝不能当作安全手段。
  • rwx:完整权限,能进、能看、能改。

还有一个非常重要的推论:访问一个深层文件,路径上每一级目录都需要x权限。比如/data/project/src/main.c,你要读main.c,需要对/、/data、/data/project、/data/project/src每一级目录都有x,同时main.c本身有r。少任何一环,都会报Permission denied,而且报错信息往往只指向最终文件,容易误导你去改错地方。

3.3 权限与常见操作的对应关系

为了便于排错,把“操作 → 需要什么权限”整理成一张速查表:

操作目标对象需要的权限
cat file文件文件r+ 路径上所有目录x
echo >> file文件文件w+ 路径上所有目录x
./script.sh文件文件r+ 文件x+ 目录x
rm file文件所在目录目录w+ 目录x
mv file dir/两边目录源目录w+x,目标目录w+x
cd dir目录目录x
ls dir目录目录r
ls -l dir目录目录r+ 目录x

这张表建议直接存下来。下次遇到Permission denied,先定位“是哪一步操作、作用于哪个对象、需要哪一位”,比盲目chmod 777有效得多,也安全得多。

4. 权限的两种表示法与 chmod 实战

4.1 八进制数字表示法

每一位权限都可以用一个数字表示:r = 4,w = 2,x = 1,把需要的加起来就是这一位的值。三组权限连起来就是一个三位八进制数,比如755:

  • 属主:4+2+1 = 7(rwx)
  • 属组:4+0+1 = 5(r-x)
  • 其他:4+0+1 = 5(r-x)

常用组合的含义如下:

数字符号形式典型用途
644rw-r--r--普通文本文件、配置文件
600rw-------私钥、密码文件
755rwxr-xr-x可执行程序、目录
700rwx------私有目录
664rw-rw-r--组内协作的普通文件
775rwxrwxr-x组内协作的目录
777rwxrwxrwx所有人可读写执行(生产环境慎用)

数字法写起来快,适合一次性设定完整权限。命令格式:

chmod 644 /data/lab/config.ini chmod 755 /data/lab/run.sh chmod 700 /data/lab/private

4.2 符号表示法

符号法的格式是[ugoa][+-=][rwx],可以针对某一类身份做增量修改,这在“只想给组加一个写权限,不想动其他位”时特别方便:

  • u属主,g属组,o其他,a全部(等价于ugo)。
  • +增加,-移除,=直接赋值(会覆盖原有权限)。
chmod u+x run.sh # 给属主加执行权限 chmod g+w shared.txt # 给属组加写权限 chmod o-rwx secret.txt # 移除其他人的所有权限 chmod a+r public.html # 所有人可读 chmod u=rw,g=r,o= log.txt # 直接赋值,逗号分隔多组

符号法还有一个组合写法很实用:chmod +x file(省略身份,等价于a+x,但要受 umask 影响,准确写法还是显式写chmod u+x或chmod a+x)。在脚本里,建议显式写出身份,可读性更好,行为也更确定。

4.3 递归修改与 X 权限位

当目标是目录时,-R会递归处理目录下的所有内容:

chmod -R 755 /data/lab/static

但这里有个坑:755会把目录下所有文件也变成带x的权限,而普通文本文件通常不需要执行位。这时可以用大写的X(注意是大写):

chmod -R u=rwX,go=rX /data/lab/static

大写X的规则是:只有当目标本身是目录,或者文件原本就有任意一位执行权限时,才设置执行位。这正好符合“目录给 x、普通文件不给 x”的需求,是部署静态资源、批量设置项目目录权限时的标准做法。

4.4 关于 777 的误区

chmod 777是新手最容易形成的条件反射:改不好就 777。但 777 意味着任何人都能读、写、执行,在 Web 目录上等于给了上传者一个可写入并执行脚本的入口,在共享机器上等于放弃了对数据的控制。更现实的问题是,777 会掩盖真正的问题——你原本只需要给属组加w,结果把整个权限模型都拆了,后续排查问题时反而看不清权限设计意图。

正确的做法是:先确定最小必要的权限,再落地成具体数字。一个典型 Web 目录的合理权限是目录755(属主www-data)、上传目录775且属组正确、配置文件640。如果确实需要多用户协作,请用“建组 + 组权限 + SGID / ACL”来解决,而不是 777。第 8 节的实战会完整演示这个流程。

5. 属主与属组管理:chown、chgrp

5.1 chown 的基本用法

chown(change owner)用于修改文件或目录的属主和属组,需要root或具有CAP_CHOWN能力的用户执行。普通用户即使用sudo,也只在被授权时才能改。

chown alice app.log # 只改属主 chown alice:devs app.log # 同时改属主和属组 chown :devs app.log # 只改属组(等价 chgrp devs app.log) chown alice: app.log # 属主改 alice,属组改为 alice 的主组 chown -R alice:devs /data/lab # 递归修改整个目录

注意chown alice:devs和chown alice.dev两种写法在大多数 GNU coreutils 环境下都支持,但为了可读性和兼容性,推荐统一使用冒号。

chgrp是专门改属组的命令,适合只需要改组不改属主的场景:

chgrp devs report.txt chgrp -R devs /data/project

5.2 递归修改的风险与正确姿势

chown -R是权限操作里最危险的一类命令,因为它的作用范围由路径决定,而路径写错一个字符,影响范围可能完全不同。常见的错误有三种:

第一种是路径末尾多写或漏写斜杠,导致作用到错误的层级;第二种是在/或/usr、/var这类系统目录上误操作,导致系统命令或服务异常;第三种是没有确认当前目录,误把.或*展开成意料之外的文件集合。

降低风险的做法是固定的三步:先确认路径、再预览、最后执行。

# 第一步:确认目标目录存在且正确 ls -ld /data/project # 第二步:利用 find 预览将被影响的文件(不实际修改) find /data/project ! -user alice -ls | head -20 # 第三步:确认无误后再执行 sudo chown -R alice:devs /data/project

对于生产环境,还应该在变更前做备份或快照,并把命令写成可回滚的形式。涉及大批量属主变更时,建议分批执行并观察服务日志,因为在变更过程中服务可能因为无法读取文件而短暂报错。

5.3 属主与属组的实际场景

属主属组的调整在几种场景下几乎不可避免。

Web 服务目录:Nginx / Apache 以www-data(Debian 系)或nginx、apache(RHEL 系)身份运行,如果站点目录的属主是root且权限是755,服务读得了但写不了上传和缓存目录。常见做法是站点代码属主为root:www-data、权限644/755,仅上传目录设为www-data:www-data且权限755。

容器与挂载卷:容器内的进程通常以非 root 用户(比如 UID 1000)运行,而宿主机挂载进去的目录属主可能是 root。这时容器内写文件就会Permission denied。解决办法是让宿主机目录属主与容器内 UID 对应,或者启动容器时用--user指定 UID 并确保目录对该 UID 可写。

代码交付:从压缩包解压出来的文件,属主可能是打包人的 UID,落到服务器上变成一个陌生的数字 UID。ls -l里看到1001 1001这种数字而不是用户名,就是这个原因——系统里没有对应的用户条目。这种情况下chown -R deploy:deploy是常规操作。

6. 默认权限与 umask

6.1 umask 的作用机制

新建文件的权限不是靠“创建时就指定”,而是有一套默认规则:文件的基准权限是666(不给执行位),目录的基准权限是777,然后减去 umask 中对应的位。注意这里是“按位清除”,不是数学减法。

umask的查看很简单:

umask # 输出如 0022 umask -S # 符号形式输出,如 u=rwx,g=rx,o=rx

常见的默认值:root用户通常是0022,普通用户在部分发行版上是0002(组内可写)。这个差异解释了一个常见困惑:为什么在有的机器上,新建文件是644,在另一些机器上是664。

6.2 计算示例

以umask 022为例:

  • 新建普通文件:666按位减去022→644,即rw-r--r--。
  • 新建目录:777按位减去022→755,即rwxr-xr-x。

以umask 002为例:

  • 新建普通文件:666减去002→664,即rw-rw-r--。
  • 新建目录:777减去002→775,即rwxrwxr-x。

可以用一条命令验证:

umask 022 touch /data/lab/umask_test.txt mkdir -p /data/lab/umask_dir ls -ld /data/lab/umask_test.txt /data/lab/umask_dir

预期输出类似:

-rw-r--r-- 1 alice devs 0 Mar 12 11:00 /data/lab/umask_test.txt drwxr-xr-x 2 alice devs 6 Mar 12 11:00 /data/lab/umask_dir

6.3 临时设置与永久生效

umask是进程级属性,不是系统全局的。直接执行umask 002只影响当前 shell 及其子进程,关掉终端就失效。

如果想让某个用户长期使用特定 umask,写入 shell 配置文件:

# 追加到 ~/.bashrc echo 'umask 002' >> ~/.bashrc

如果要系统级统一,可以修改/etc/profile或/etc/login.defs(不同发行版位置和优先级不同,修改前请先备份并确认发行版差异)。对于组内协作目录,除了 umask,更可靠的方式是用 SGID + 默认 ACL(见第 8 节),因为 umask 只影响“新建”,对已经存在的文件无效,而且用户自己随时可以改。

7. 特殊权限位:SUID、SGID、Sticky

7.1 SUID:以文件属主身份执行

SUID(Set User ID)位设置在可执行文件上时,进程运行期间的有效 UID 会临时变成文件属主的 UID。最典型的例子是/usr/bin/passwd——它需要改写/etc/shadow,而普通用户没有这个权限,所以它带着 SUID 位运行,临时获得 root 身份。

查看方式:

ls -l /usr/bin/passwd

输出中属主位的x会变成s:

-rwsr-xr-x 1 root root 59976 Feb 1 09:15 /usr/bin/passwd

设置与取消:

chmod u+s /path/to/program chmod 4755 /path/to/program chmod u-s /path/to/program

需要强调两点。第一,SUID 只对二进制可执行文件有意义,对 shell 脚本通常无效,因为 Linux 内核出于安全考虑会忽略脚本上的 SUID 位。第二,SUID 是提权相关的高危配置,在生产环境中应该严格审查。系统里存在哪些 SUID 文件可以定期核查:

find / -perm -4000 -type f 2>/dev/null

如果你在排查安全问题时看到来源不明的 SUID 文件,应当视为需要立即确认的可疑项。

7.2 SGID:对文件和目录的两种语义

SGID(Set Group ID)在不同对象上含义不同,这一点经常被混淆。

应用在可执行文件上时,进程的有效 GID 会变成文件属组,用途同样是临时借用组身份。

应用在目录上时,效果是:在该目录下新建的文件和子目录,会自动继承该目录的属组,而不是创建者的主组。这是组内协作场景的核心工具。

设置方式:

chmod g+s /data/project # 符号法 chmod 2770 /data/project # 数字法,2 是 SGID 位

设置后ls -ld的输出中,属组位的x变成s:

drwxrws--- 2 root devs 4096 Mar 12 11:20 /data/project

这里有一个必须知道的坑:SGID 只保证“属组继承”,不保证“组有写权限”。也就是说,新建文件的属组会是 devs,但它的权限位仍然由创建者的 umask 决定。如果 umask 是022,新文件是644,组内其他人只有读没有写,协作写入依然做不到。解决办法是配合 umask002,或者在目录上设置默认 ACL。这个坑在第 8 节会实际演示。

7.3 Sticky Bit:限制他人删除

粘滞位(Sticky Bit)只对目录生效,作用是:即使所有人都对目录有写权限,也只允许文件属主、目录属主和 root 删除或重命名目录内的文件。

最经典的例子是/tmp:

ls -ld /tmp
drwxrwxrwt 15 root root 4096 Mar 12 10:22 /tmp

其他位是rwt,x变成了t。这样/tmp虽然人人可写,但你删不掉别人的文件。

设置方式:

chmod +t /data/shared chmod 1777 /data/shared # 1 是粘滞位

7.4 大写 S 和 T 的含义

有时候你会看到大写字母:

-rwSr--r-- drwxr-Sr-x drwxrwxrwT

大写表示“特殊位已设置,但对应的执行位没有设置”。因为s和t是占用x位置的显示,如果原本没有x,就只能显示大写。举个例子,一个文件被chmod u+s但本身没有x,就会显示s变S——这种情况在实际中通常意味着配置有问题,因为一个不可执行的文件带 SUID 并没有实际意义。

遇到大写字母时的排查思路是:ls -l看到S或T,先确认这个对象是否真的需要对应的特殊位,再检查执行位是否被误移除。

8. 完整实战:搭建一个多用户协作目录

8.1 需求描述

假设有一个三人开发小组:alice、bob属于devs组,carol不属于该组。需求是:

  • 创建共享目录/data/project,devs 组成员在其中可以互相读写,包括读取和修改对方新建的文件。
  • 目录及其中新建文件的属组统一为devs。
  • 非 devs 组用户(carol)无法进入该目录。

这个需求用到的知识点正好覆盖本篇文章:组、SGID、目录权限、默认 ACL。

8.2 创建用户与组

以下命令需要 root 或 sudo 权限,请在测试环境执行:

sudo groupadd devs sudo useradd -m -G devs alice sudo useradd -m -G devs bob sudo useradd -m carol

给用户设置密码(便于切换验证):

sudo passwd alice sudo passwd bob sudo passwd carol

注意useradd -G是把用户加入附加组,主组仍然是同名组。不同发行版中useradd和adduser的行为不同,adduser在 Debian 系上是交互式脚本,创建时会顺带建家目录;而useradd默认不建家目录,需要加-m。这是跨发行版脚本容易出错的地方。

创建完成后验证:

id alice

预期输出中包含devs:

uid=1001(alice) gid=1002(alice) groups=1002(alice),1001(devs)

8.3 创建目录并设置基础权限

sudo mkdir -p /data/project sudo chown root:devs /data/project sudo chmod 2770 /data/project

逐条解释:

  • mkdir -p保证父目录不存在时自动创建。
  • chown root:devs把属组设为 devs,属主保持 root(也可以换成某个管理员账号)。
  • chmod 2770中2是 SGID,770表示属主和属组是rwx,其他人没有任何权限。

验证:

ls -ld /data/project

预期:

drwxrws--- 2 root devs 4096 Mar 12 12:00 /data/project

看到属组位的s,说明 SGID 已生效。

8.4 验证 SGID 与组协作写入

先以 alice 身份操作:

su - alice cd /data/project echo "hello from alice" > alice.txt ls -l alice.txt

预期输出:

-rw-r--r-- 1 alice devs 17 Mar 12 12:05 alice.txt

可以看到属组已经是devs(继承自目录),但权限是644。现在切换到 bob 尝试写入:

su - bob cd /data/project echo "append by bob" >> alice.txt

此时大概率会报:

-bash: alice.txt: Permission denied

这正是 7.2 节提到的坑:SGID 解决了属组继承,但文件权限只有属主可写,bob 走组位判断,组位是r--,写不了。

8.5 用默认 ACL 补上组写权限

有两种解决方式。第一种是让所有 devs 组成员把 umask 设为002,这样新建文件默认就是664。但 umask 是用户自己可控的,别人改了 umask 就会破坏协作规则,不够稳。

更可靠的第二种方式是给目录设置默认 ACL,让在该目录下新建的文件自动带上组写权限:

sudo setfacl -m g:devs:rwx /data/project sudo setfacl -d -m g:devs:rwx /data/project sudo setfacl -d -m o::--- /data/project

参数说明:

  • -m表示修改 ACL 条目。
  • g:devs:rwx表示给 devs 组授予读写执行。
  • -d表示 default ACL,只对目录中新创建的对象生效,不影响已有文件。
  • o::---表示其他人类别无权限,保证非 devs 用户无法访问。

验证 ACL:

getfacl /data/project

输出类似:

# file: data/project # owner: root # group: devs user::rwx group::rwx group:devs:rwx other::--- default:user::rwx default:group::rwx default:group:devs:rwx default:other::---

现在再以 alice 新建一个文件,然后看 bob 能否写入:

su - alice cd /data/project echo "new file" > report.txt ls -l report.txt getfacl report.txt

你会看到ls -l的权限位后面多了一个+,表示该文件带有 ACL:

-rw-rw-r--+ 1 alice devs 9 Mar 12 12:15 report.txt

getfacl report.txt会显示group:devs:rwx的条目,说明组写权限已经通过默认 ACL 继承下来。此时 bob 执行echo "bob was here" >> report.txt就能成功。

最后验证 carol 无法访问:

su - carol cd /data/project

预期报错:

-bash: cd: /data/project: Permission denied

因为 other 位是---,没有x就无法进入目录,整个协作用户范围就被限制在 devs 组内了。

8.6 完整流程回顾

把上面的步骤串成一条可直接复制的脚本(仅用于测试环境,执行前请确认/data/project不存在或可覆盖):

#!/bin/bash set -e # 1. 建组建用户 groupadd devs || true useradd -m -G devs alice || true useradd -m -G devs bob || true useradd -m carol || true # 2. 建目录并设置属组与 SGID mkdir -p /data/project chown root:devs /data/project chmod 2770 /data/project # 3. 设置 ACL,保证新建文件继承组写权限 setfacl -m g:devs:rwx /data/project setfacl -d -m g:devs:rwx /data/project setfacl -d -m o::--- /data/project # 4. 输出结果 ls -ld /data/project getfacl /data/project

执行完成后,用 alice 和 bob 互相写文件验证,用 carol 验证拒绝访问,这个流程就算完整跑通了。这套“建组 + SGID + 默认 ACL”的组合,是 Linux 上实现多用户协作目录的标准做法,比 777 安全得多,也比单纯依赖 umask 可靠得多。

9. 常见问题与排查清单

权限问题的排查有一个固定套路:确认身份 → 确认路径 → 确认每一级目录权限 → 确认目标对象权限 → 确认特殊机制(ACL、SELinux、挂载选项)。下面把高频问题整理成表格,方便对照。

问题现象常见原因解决思路
-bash: ./x.sh: Permission denied脚本缺少x位chmod u+x x.sh,或改用bash x.sh
手动能跑,cron 跑不了cron 直接执行脚本,缺x;或环境变量不同给脚本加x位,脚本内使用绝对路径
明明在组里,还是没权限组信息未生效,需重新登录新开会话,或newgrp devs验证
ls能看到文件名,但读不了内容目录有r无xchmod u+x dir或按需调整
知道完整路径就能读文件目录给了x没给r这是设计行为,评估是否需要收紧
改完权限不生效被 ACL 覆盖;或是符号链接getfacl查看,ls -l看+标记
服务写不了目录目录属主/属组与进程运行用户不一致对齐属主属组,仅对必要目录放开写
删除别人的文件失败目录设置了粘滞位确认是否应保留,必要时由属主或 root 操作
容器内写挂载目录失败容器内 UID 与宿主机目录属主不匹配调整宿主机目录属主或容器启动用户
解压后属主显示为数字系统内无对应 UIDchown -R 目标用户:目标组

除了表格里的原因,还有两个容易被忽略的机制。

第一个是SELinux。在启用了 SELinux 的 RHEL 系系统上,文件权限全对,服务依然可能报Permission denied。这时查看/var/log/audit/audit.log或执行getenforce确认状态。SELinux 属于强制访问控制,和本文讲的普通权限是两层独立机制,解决问题需要改上下文或策略,而不是继续chmod。

第二个是挂载选项。某些目录可能以noexec、ro等选项挂载,导致即使文件有x位也无法执行,或者根本无法写入。用mount | grep 目标路径确认挂载参数,能省下大量无效排查。

一个实用的排查清单,遇到权限问题按顺序过一遍:

  1. whoami、id:当前身份和组是否如预期。
  2. ls -ld逐级检查路径上每一级目录的权限。
  3. ls -l检查目标文件本身的权限和属主属组。
  4. getfacl检查是否存在 ACL 覆盖。
  5. getenforce检查 SELinux 状态。
  6. mount检查目标路径的挂载选项。
  7. 检查进程实际运行用户(ps -o user,pid,cmd -p <pid>),而不是“我以为的运行用户”。

10. 最佳实践与学习路线

10.1 权限设计的几条硬规则

最小权限原则。给到刚好够用的权限,不多给一位。Web 目录不需要777,日志文件不需要666,脚本不需要4777。每次要加权限时,先问一句“能不能只给某一个组、某一个用户”。

按组授权而不是按人授权。人多了以后,逐个用户授权必然失控。正确做法是建业务组(如devs、ops、www-data),把用户加入组,用组权限和 SGID 管理目录。这样人员的进出只需要维护组成员关系。

敏感文件收紧到 600 或 640。私钥、数据库密码、.env文件、备份文件都属于这一类。特别注意不要把这些文件放在 Web 根目录下,也不要在打包镜像时把它们带进镜像层。

慎用递归操作。chmod -R和chown -R执行前必须预览路径,必要时用find先列出受影响对象。生产变更前做快照或备份,并准备好回滚命令。

权限变更留痕。在团队环境里,重要的权限调整应该有记录:谁改的、为什么改、改之前是什么。哪怕是写在变更单里的一行备注,出问题时也能节省大量时间。

10.2 常规维护动作

定期检查异常的高权限文件,是一个成本很低但收益很高的习惯:

# 查找系统中所有 SUID 文件 find / -perm -4000 -type f 2>/dev/null # 查找所有人可写的文件(排除临时目录) find /data -type f -perm -o+w 2>/dev/null # 查找属主为陌生 UID 的文件 find /data -nouser -o -nogroup 2>/dev/null | head -20

这几条命令不需要天天跑,但在接手一台旧服务器、做安全自查、或者排查异常行为时非常有用。特别是第三条,能帮你发现从其他机器迁移过来、没有正确重设属主的文件。

10.3 接下来可以学什么

本文覆盖的是 Linux 权限模型的基础层:UGO 三位九位、属主属组、umask、特殊权限位和 ACL。往下还有几个方向值得继续深入。

第一个方向是ACL 的完整用法,包括setfacl -x删除条目、-b清空所有 ACL、mask 的作用,以及如何用getfacl输出做权限审计。协作目录场景下,ACL 比传统 UGO 灵活得多,值得系统学一遍。

第二个方向是sudo 与提权管理。/etc/sudoers的规则写法、NOPASSWD的风险、visudo的校验机制、按命令粒度授权,是团队环境中控制 root 使用的核心。

第三个方向是SELinux / AppArmor。当你发现权限位都正确但服务依然被拒绝时,强制访问控制就是下一层要排查的地方。

第四个方向是容器与权限。Docker 的--user、USER指令、rootless 模式、挂载卷的 UID 映射,都是把本文的权限知识应用到容器场景后的延伸。

建议的学习方式是:在一台测试机上把本文第 8 节的协作目录完整做一遍,然后故意制造几种错误(去掉x位、改错属组、加上粘滞位),观察每种错误的报错表现。权限这块知识的特点是“看懂了”和“会用”之间隔着一层手感,只有真正敲过命令、见过报错,下次在服务器上遇到Permission denied时才能几分钟定位,而不是先去搜索“Linux 权限不够怎么办”。