刚接触Linux的人,十个里面有八个是被一条Permission denied吓退的。我第一次折腾服务器的时候也是这样:明明文件就在眼前,cat一下就被拒之门外,当时的心理活动就是"这系统是不是在故意刁难我"。后来才明白,Linux从出生那天起就是给多人同时使用的操作系统,权限不是刁难,而是一套路线清晰的访问控制规则。掌握了这套规则,你不仅能看懂那些rwxr-xr-x的怪字符串,还能避免很多"权限设太大被入侵、设太小自己没法干活"的纠结。
这篇就当是Linux权限的完整梳理,从最基础的三种身份和三种权限讲起,把chmod、chown、特殊权限、umask和 ACL 挨个说透,最后给一套排查Permission denied的实操套路。不管你是刚上手的运维新人,还是被权限问题折磨过的后端开发者,这份内容应该都能帮你省下不少搜索时间。
1. 权限到底在管什么:三种身份与三件权力的底层逻辑
1.1 从ls -l的输出认识权限串
先丢一条最常见的命令:ls -l。它输出的第一列长这样:
-rw-r--r-- 1 zhangsan dev 1234 Feb 20 10:30 app.conf别被这串字符吓到,按位拆开其实非常直白。第一个字符表示文件类型,-是普通文件,d是目录,l是软链接,b和c分别对应块设备和字符设备。后面那9个字符才是权限本体,分成三组,每组三个:
- 第1-3位:文件所有者(owner)的权限,权限串里记作
u - 第4-6位:所属组(group)的权限,记作
g - 第7-9位:其他所有人(others)的权限,记作
o
三位一组的顺序永远是rwx:r是可读(read),w是可写(write),x是可执行(execute)。有对应权限就显示字母,没有就显示-。
上面的例子就是:所有者 zhangsan 有读写权限没有执行权限(rw-),组 dev 只有读权限(r--),其他人也只有读权限(r--)。合起来rw-r--r--,这其实就是配置文件最常见的权限形态,数字表示法下是644。
1.2 文件权限和目录权限是两套规则
很多人栽的第一个跟头,就是把文件权限的逻辑直接套到目录上。文件上的r是能读取内容,目录上的r是能列出目录里有哪些文件名;文件上的w是能修改文件内容,目录上的w是能在这个目录里新建和删除文件——注意,删除文件看的是目录的写权限,而不是文件本身的写权限,这点极其反直觉。
目录上的x理解成一个"通行证"更合适。你只有对目录拥有x权限,才能cd进去,才能访问里面的文件。哪怕你对某个文件有完整的rwx,只要它所在的目录你没有x,照样会被挡在外面。这就是为什么常见的目录权限是755(rwxr-xr-x):所有人可以看文件名,但只有所有者能增删文件。
这里有一个很多人不知道的点:想访问一个深层路径里的文件,路径上的每一级目录都必须有x权限。比如说要读/var/www/html/index.html,那么/、/var、/var/www、/var/www/html每一层的x都不能缺。中间任何一级缺了,都只能得到一句含糊的Permission denied。
排查权限问题时我习惯先用
namei -l /var/www/html/index.html一次性列出所有路径段的权限,比一级一级ls -ld快得多。这条命令后面还会再提到。
1.3 三类身份在系统里到底是谁
所有者u很好理解,就是文件创建者,或者后来被chown指定的用户。关键在组这一层:一个用户可以同时属于多个组,/etc/group里定义了所有组和成员关系,当前会话的生效组用id命令能看到。系统判定"你是组内成员吗"用的是当前生效的组身份,newgrp可以临时切换生效组。
还有一点值得强调:判断一个用户属于哪一类,是按"匹配优先级"来的。系统先看你是不是所有者,是就不用管后面了;不是所有者再看你是不是在组里;组也进不去,才算"其他用户"。不存在"所有者没有读权限,但作为其他用户有读权限"这种叠加逻辑——你永远只按第一个命中的身份算。这就是为什么设置权限时要格外小心:千万别以为"我组里有权限,所以所有人都能享受"。
2. chmod实战:符号模式与数字模式,两种改法都要顺手
2.1 符号模式:适合改单个权限时用
chmod是修改权限的主命令,第一种写法叫符号模式,语法是"谁 + 操作 + 什么权限":
- 谁:
u(所有者)、g(组)、o(其他人)、a(所有人) - 操作:
+(加权限)、-(减权限)、=(把权限设置成精确值) - 什么权限:
r、w、x,特殊权限还有s、t,后面单独讲
几个最常见的组合:
chmod u+x script.sh # 给所有者加执行权限 chmod g-w app.conf # 去掉组的写权限 chmod o=r readme.txt # 把其他人的权限精确设置为只读 chmod a+x run.sh # 所有人都能执行符号模式的最大优点是精细、不会误伤。比如chmod u+x只动x位,文件其他权限原封不动。这在交互式操作时特别顺手,我调脚本权限时几乎都是这么干的。
2.2 数字模式:配置文件和脚本里的主流写法
数字模式的原理其实是把rwx当成一个三位的二进制数:r是 4,w是 2,x是 1,加起来就是该组合的数值。全权限rwx是 7,rw-是 6,r--是 4,--x是 1。下面的对照表直接背下来就行:
| 数字 | 权限 | 含义 |
|---|---|---|
| 7 | rwx | 读、写、执行 |
| 6 | rw- | 读、写 |
| 5 | r-x | 读、执行 |
| 4 | r-- | 只读 |
| 3 | -wx | 写、执行 |
| 2 | -w- | 只写 |
| 1 | --x | 只执行 |
| 0 | --- | 无权限 |
一位数字对应一类身份,按"所有者-组-其他"顺序排三个数字就是完整的权限。chmod 754 file的含义是:所有者rwx(7),组r-x(5),其他人r--(4)。
数字模式适合批量操作和写进脚本,因为它表达的是"最终状态",不受之前权限影响,执行结果是确定性的。我在部署脚本里一律用数字模式,比如chmod 644设置配置文件、chmod 755设置目录,方便审计也知道最终形态是什么。
2.3 chmod -R 的陷阱:递归改权限没那么简单
递归改权限看似一句chmod -R 755 /some/dir搞定,但坑就藏在"一刀切"里:目录和文件混在一起时,所有文件都会被加上执行位。网站项目目录很典型,你本来只想让 PHP 文件可读,结果-R 755下去,所有图片和日志文件全都变成"可执行",既不符合最小权限原则,也存在一定风险。
正确做法是分而治之:目录统一设为755,普通文件统一设为644。用两条命令分流:
find /var/www/myapp -type d -exec chmod 755 {} \; find /var/www/myapp -type f -exec chmod 644 {} \;find的-type d和-type f分别只选中目录和普通文件,{}是找到的每个路径的占位符,\;是-exec命令的结束标记。之后如果需要个别脚本可执行,再单独chmod +x指定文件。
2.4 一个真实场景:搭建 Web 项目时的标准权限分配
举个综合例子。假设你给某公司部署一个 PHP 项目到/var/www/myapp,参与的人有:项目所有者wwwroot、维护组webdev、以及其余系统用户。传统方案是:
chown -R wwwroot:webdev /var/www/myapp find /var/www/myapp -type d -exec chmod 750 {} \; find /var/www/myapp -type f -exec chmod 640 {} \;目录750表示组内成员可以进入并读取,但不能在根目录乱建文件;文件640表示组内成员可读,只有所有者可写。这样设置,系统里的其他用户连目录内容都看不见,等于把访问范围明确锁定到了维护组,比直接用常见的755/644更克制。
3. 所有权不等于权限:chown与chgrp的正确用法
3.1 为什么所有权和权限是两件事
权限管的是"能不能动",所有权管的是"你是文件的主人吗"。ls -l输出里用户名和组名那一列,就写着这个文件归谁所有、归属于哪个组。所有权决定了u那一组权限对谁生效,也决定了谁能修改这个文件的权限。
Linux 默认规则是:文件的所有者可以chmod改变自己文件的权限,也可以chgrp把文件组改成自己所属的组;但改变文件的owner,只有root能办到。这个设计是有道理的:如果任何用户都能把别人的文件chown成自己,那权限系统就彻底形同虚设了。
3.2 命令细节与容易踩的坑
改所有者用chown,改组用chgrp,也可以一条命令同时改:
chown zhangsan file # 只改所有者 chgrp dev file # 只改组 chown zhangsan:dev file # 同时改所有者和组,这是最推荐的写法 chown :dev file # 只改组,效果等于 chgrp chown -R zhangsan:dev dir # 递归修改整个目录下所有内容用冒号:连接用户名和组名是现代的推荐写法,chown zhangsan.dev file这种点号形式是旧语法,虽然很多老系统仍然兼容,但遇到用户名本身就带点号的情况会解析出歧义,不建议再用。
另一个高频坑是:tar以root身份解压别人打包的文件,解出来一堆root:root所有权的文件,普通用户只能看不能改。这种情况先用chown -R把所有权还给实际使用者,再做权限调整。我在某项目里接过一套历史遗留的代码包,解压后 2000 多个文件全是root所有,没有先修正所有权就直接改权限,结果chmod倒是成功了,可代码运行要写缓存时还是报错——因为写缓存的文件根本不属于服务账号。
3.3 多用户协作时的所有权设计思路
多人共用的服务器上,最稳妥的做法是:创建一个专用组,把需要协作的账号都加进去,文件组设为这个组,然后目录开组写权限。以某个技术部共享目录/srv/teamdata为例:
groupadd techshare usermod -aG techshare zhangsan usermod -aG techshare lisi chown -R root:techshare /srv/teamdata chmod -R 2770 /srv/teamdata这里的2770涉及目录的 setgid 位(开头的 2),它保证目录里新建的文件自动继承techshare组,而不是创建者的私有组。这是团队协作目录里最重要的一个细节,下一节会专门展开讲。
4. 三位特殊权限:setuid、setgid与sticky bit背后的门道
4.1 setuid:执行时"变身"为文件所有者
正常情况下,你运行一个程序,程序以你的身份权限去干活。但某些系统程序需要突破这层限制,最经典的例子是passwd。普通用户要改自己的密码,就必须写/etc/shadow,而这个文件只有 root 能写。如果让每个用户都能直接写/etc/shadow,那密码系统等于裸奔。解决办法就是给passwd程序加上 setuid 位:用户执行它的时候,进程以文件所有者 root 的身份运行,从而获得写 shadow 文件的资格,但用户依然不能直接读 shadow 文件本身。
看下效果:
ls -l /usr/bin/passwd -rwsr-xr-x 1 root root ...所有者权限位里的x变成了s,这就是 setuid 的标志。设置方法:
chmod u+s /path/to/program chmod 4755 /path/to/program # 数字模式,4开头关于 setuid 有三条硬经验:
- 绝对不要给脚本(shell、Python 等)设置 setuid。绝大多数系统的脚本解释器出于安全考虑会忽略甚至拒绝这个位,行为很不可控。setuid 只应对编译好的二进制程序使用。
- setuid 程序本身必须是可信的。它相当于给所有能执行它的用户发了一张"临时 root 卡",程序一旦有漏洞,攻击者就可能借机提权。
- 有些文件系统挂载时带了
nosuid选项,这种位置上的 setuid 位不生效,后面排查权限问题时值得留意。
4.2 setgid:让新建文件自动继承目录的组
setgid 对文件和对目录的含义不同。对可执行文件来说,它让进程以文件所属组的身份运行;对目录来说,它让所有在目录里新建的文件自动继承目录的所属组。第二条才是日常用得最多的场景。
回到上一节的共享目录例子。如果不设置 setgid,用户 zhangsan 在/srv/teamdata里新建的文件,组会被设成 zhangsan 的私有组,lisi 就访问不了了。加上 setgid 位之后,新建文件自动变成techshare组,协作立刻顺畅:
chmod g+s /srv/teamdata ls -ld /srv/teamdata drwxrws--- 2 root techshare 4096 ...组权限位的x变成了s,或者用数字模式chmod 2770,开头的 2 就是 setgid。
4.3 sticky bit:防删除的最后一道闸门
sticky bit 对普通文件用处不大,它的主战场是目录。带 sticky bit 的目录里,用户虽然对目录有写权限(能新建和删除文件),但只能删除自己拥有的文件。目录所有者和 root 除外。
这个机制最经典的用途就是/tmp。/tmp是全局共享的临时目录,所有人都有写权限,如果没有任何约束,任何一个用户都能删掉别人的临时文件,这在多用户系统上会乱套。加上 sticky bit 之后,大家谁的文件归谁管,别人动不了:
ls -ld /tmp drwxrwxrwt 1 root root ...末尾的t是标志。设置方法:
chmod +t /tmp/shared chmod 1777 /tmp/shared # 开头的1是sticky bit4.4 数字模式的特殊权限位对照
特殊权限在数字模式的开头再跟一位:4 代表 setuid,2 代表 setgid,1 代表 sticky bit。三个位可以叠加,比如6755就是 setuid+setgid 一起开。但实际使用要极为克制,能不开就不开,开一个就多一个风险面。我见过有人图省事把整个脚本目录chmod -R 4755,这基本等于给系统开了一扇侧门。
5. umask:新建文件的默认权限是被"扣"出来的
5.1 为什么新文件不是777
很多人的直觉是:既然默认权限应该越开放越好,那新建文件为什么不是666?这里的关键在于,Linux 的默认权限是"安全上限"减去umask屏蔽位。文件的基础上限是666,目录是777,umask负责把一些位扣掉,防止创建出世界可写的东西。
umask查看当前值:
umask 0022输出通常是四位,第一位 0 是特殊权限屏蔽,后三位022是给 u、g、o 的屏蔽掩码。计算方式是"基础权限掩码与 umask 取反后按位与",用大白话讲就是:文件用666对照umask 022,022表示"组去掉写,其他人去掉写",最终结果是644;目录同理算出755。常见组合速查:
| umask | 新文件 | 新目录 | 适用场景 |
|---|---|---|---|
| 022 | 644 | 755 | 服务器默认,兼顾共享与安全 |
| 002 | 664 | 775 | 团队协作目录,组内可写 |
| 077 | 600 | 700 | 单人专用,完全不给别人看 |
| 027 | 640 | 750 | 组内可读,其他人无权访问 |
5.2 为什么文件永远带不上执行位
细心的读者会发现:umask 022下新目录是755带x,新文件却是644不带x。原因很简单,普通文件的基础权限上限是666,根本没有执行位,所以怎么扣也扣不出x来。这是一道刻意的安全设计——只有当你明确chmod +x时,文件才被赋予执行属性。理解了这一点,就不会再问"为什么我新建的脚本不能直接跑"。
5.3 在哪设置 umask
临时测试直接在 shell 里输入umask 077,只在当前会话生效。永久设置取决于你改哪个文件:想改用户级,写在~/.bashrc或~/.profile;想做全系统默认,写进/etc/profile或/etc/bash.bashrc。注意顺序:登录 shell 会先读系统级再读用户级,后读取的覆盖先读取的。
我在管理服务器时,会给不同用途的账号配不同 umask:普通运维账号保持022,跑权限敏感任务的账号用077。别小看这个细节,很多"莫名奇妙多了个世界可写文件"的安全告警,根源就是 umask 设得太宽。
6. ACL:当"三个身份"装不下更细的授权需求
6.1 传统权限的边界在哪
传统权限只有 u、g、o 三类身份,面对这种场景就非常尴尬:某公司运维部有个项目数据目录,所有者是opadmin,组是ops,目录权限是750。现在测试部的一个测试账号tester01需要读取里面的日志文件,但你又不想把这个账号加进ops组——因为加进去它就能看到整个组的其他敏感资源。用传统权限,要么改o的权限(等于对全世界开放),要么创建一个新组(要改一堆配置),都不够干净。
6.2 setfacl与getfacl的基础操作
ACL(访问控制列表)就是解决这类"精确授权"问题的。它可以绕过 u/g/o 的框架,给任意一个指定用户或组单独设置权限:
setfacl -m u:tester01:rx /srv/logs/app.log # -m 表示修改,u:用户名:权限 是给特定用户授权 getfacl /srv/logs/app.loggetfacl输出里除了原有的owner/group/other三行,会多出user:tester01:r-x这样的扩展条目。用ls -l看这个文件,权限串末尾会多出一个+标记。
常见的 ACL 操作我列一下:
setfacl -m u:zhangsan:rw file # 给指定用户读写 setfacl -m g:devteam:rx file # 给指定组读执行 setfacl -x u:zhangsan file # 删除指定用户的ACL条目 setfacl -b file # 清空所有ACL条目 setfacl -m d:u:zhangsan:rwx dir # 给目录设置默认ACL,新建文件自动继承d:前缀的默认 ACL 是目录场景下的利器,它和 setgid 一样解决"新建文件自动继承"的问题。设置默认 ACL 之后,在该目录下新建的任何文件都会自动对指定用户或组开放对应权限,免去每次新建都要补授权的麻烦。
6.3 mask字段是怎么回事
使用 ACL 时,getfacl输出里有个mask字段,它是所有具名用户和具名组权限的"上限"。即使某个用户 ACL 条目写了rwx,只要 mask 是r-x,实际生效的就是r-x。这是 ACL 的一层保护机制,防止授权越给越多。用setfacl -m m::rx可以调整 mask。
这个字段坑过不少人:明明setfacl给了用户完整权限,测试时却发现只能读不能写,一查才发现 mask 把权限压住了。遇到 ACL 相关的怪问题,先看getfacl的 mask,这是最常用的排查入口。
6.4 ACL的适用边界
ACL 很好用,但别滥用。如果业务场景里只有两三个额外授权需求,ACL 恰到好处;如果一个目录的 ACL 条目泛滥成灾,几十行扩展条目比传统权限还难维护,这时候重新设计组结构反而更清爽。我的习惯是:临时性授权用 ACL,长期稳定协作用组加 setgid。
7. 排查权限问题:从"Permission denied"到定位问题的一条龙思路
7.1 第一步先分清是哪种"拒绝"
Permission denied这个报错很偷懒,它不告诉你缺的到底是哪个权限。所以第一件事是分析操作类型:
- 读文件被拒:可能缺文件的
r,也可能缺路径上任一目录的x - 写文件被拒:可能缺文件的
w,或者缺所在目录的w(新建/删除时) - 执行脚本被拒:可能缺文件的
x,或者脚本解释器读不了脚本 - 进不了目录:缺目录的
x - 列不出目录内容:缺目录的
r
把这个映射关系记熟,比盲目chmod -R 777靠谱一万倍。
7.2 用三条命令完成常规体检
排查时我基本只用三条命令:
id # 看当前用户是谁、属于哪些组 ls -ld /path/to/each/level # 逐级查看路径目录权限 getfacl /target/path # 看有没有ACL条目在起作用id输出的组列表要跟文件所属组仔细核对。有一个高频陷阱:管理员用usermod -aG newgroup zhangsan把用户加进了新组,但该用户当前登录会话里id根本看不到新组——组信息只在登录时加载一次。遇到"我明明加了组还不行"的报错,先让对方重新登录,或者用newgrp newgroup刷新当前会话,比啥都管用。
7.3 一条命令直接看全路径
如果路径层级多,逐级ls -ld太累,namei能一口气输出从根目录到目标文件每层的权限:
namei -l /srv/logs/app.log f: /srv/logs/app.log Drwxr-xr-x root root / drwxr-xr-x root root srv drwx--x--x root ops logs -rw-r----- root ops app.log一眼就能看出卡在哪一层。比如上面这个例子,文件本身640没有问题,但/srv/logs的权限是drwx--x--x,其他人(非 root 非 ops)只有x没有r,照样进不去目录。这种藏在中间层的问题,单看文件权限绝对发现不了。
7.4 几个真实场景的排查复盘
场景一:用户在共享目录里建了文件,但同事说看不到。
先getfacl看目录有没有默认 ACL,再看目录有没有 setgid 位。如果都没有,大概率是新建文件的组仍然是创建者的私有组。解决方案就是前面说的给目录设 setgid,或者配置默认 ACL,让继承机制接管。
场景二:某个 setuid 程序执行后权限跟预期不符。
检查挂载参数:mount | grep nosuid。如果程序所在分区挂了nosuid,setuid 位会被内核无视。这种问题用ls -l看权限位是正常的s,但行为完全不对,不查挂载参数根本找不到原因。
场景三:文件在本地正常,拷贝到服务器就报权限错。
十有八九是拷贝工具没保留权限信息。用scp、普通cp拷贝时,新文件的权限取决于目标端的umask和文件本身的写入模式。如果必须保留原始权限,用cp -p或rsync -a,还可以用tar打包再解包来保留完整的元数据。
7.5 关于 root 和 sudo 的权限边界
root 在传统 DAC 权限体系下几乎是"无视一切"的:常规的rwx检查对 root 形同虚设。但有两个细节:root 执行一个没有x位的脚本文件时,如果脚本没有指定解释器,一样会被拒绝;另外文件系统挂载为read-only时,root 也写不进去——那是 VFS 层的限制,跟权限位无关。
日常操作中尽量少用sudo兜底。真正合理的姿势是:先想清楚"我需要的到底是哪个权限",再用chmod或chown精准调整,最后用普通用户验证。sudo chmod 777一时爽,但等于把整个安全边界拱手让出,等系统被写坏或者被入侵之后再后悔就晚了。
8. 一些我在实际运维中沉淀下来的习惯
最后说几个纯粹是踩出来的经验。
第一,写脚本时给权限多写一条注释。我看到过太多部署脚本里出现chmod -R 777 /data,作者早忘了当初为什么这么写。现在我要求自己所有涉及权限的命令都必须伴随注释说明意图,比如chmod 750 /srv/app # 组内可进,其他人禁止。半年后回看,这些注释能救命的。
第二,尽量用组合拳而不是单条chmod -R。前面提到的find -type d和find -type f分流是基本操作,配合-maxdepth限制深度更安全。永远不要在菜鸟阶段就养成全局-R的习惯,权限是精确到具体对象的能力,模糊处理只会换来更多的麻烦。
第三,权限调整完了别急着走,用最小权限用户实测一遍。我见过太多人改完权限后高高兴兴关掉终端,第二天业务告警——因为测试时用的是自己的高权限账号,根本模拟不了真实服务账号的受限视图。正确做法是切到服务账号下,或者用sudo -u serviceuser touch /path/test验证一遍,确认无误才算收工。
Linux 权限这个东西,说到底是"身份 + 能力"的组合管理。把文件的所有者、所属组、目录的继承机制、特殊权限位的语义都理解透了,绝大多数权限问题都可以在 30 秒内定位。希望这篇整理能帮你少走一些弯路,至少下一次看到Permission denied,你能先心平气和地问一句:是哪个身份、缺了哪个权限。