我见过太多人被Linux权限拦住,是在屏幕上突然跳出一行Permission denied。密码是对的,文件就在眼前,可系统就是不让碰。这台机器没坏,也没有故意针对谁,只是你还没把它的权限模型读透。如果你正照着教程学到这一篇,看见标题里的“4.”应该能猜到大意:这是一个系列的第四篇,权限这一关。Linux里面权限是真正的分水岭,文件系统安全、多用户协作、服务运行环境,全都建立在这套规则之上。这篇文章我就把权限相关的核心内容一次讲透,从权限位的读法开始,到chmod、chown、chgrp的取舍,再到SUID、SGID、Sticky Bit这几个特殊位,最后把umask、ACL和完整排查链路都走一遍。
1. 从ls -l那串字符开始:rwx编码、文件类型与目录权限的差异
1.1 第一眼看到这串字符怎么读
不管你是刚接触Linux还是已经写过不少脚本,每天都免不了和ls -l打交道。但很多人对它的理解停留在“能看到权限”这个层面,真要他解释第三列第四列第五列分别代表什么,就会卡壳。
随便拿一条常见的输出:
drwxr-xr-x 2 owner group 4096 Mar 12 10:00 mydir -rw-r--r-- 1 owner group 1234 Mar 12 10:00 file.txt第一列就是关键。把它拆开看:第一位是文件类型,后面9个字符分成三组,每三个一组对应一个角色。
d是目录,-是普通文件,l是符号链接,b是块设备,c是字符设备,s是套接字,p是管道。大多数时候你只会看到前三种。
后面的9个字符分三组:
- 第一组:属主(文件所有者)的权限
- 第二组:属组(文件所属组)成员的权限
- 第三组:其他人的权限
每组三个字符,顺序固定:r(读)、w(写)、x(执行)。没有对应权限就用-补位。
举个例子,-rw-r--r--的意思是:普通文件,属主可读可写,属组成员可读不可写,其他所有人只读。这里没有执行权限,所以你不能直接把它当程序跑。
很多人问我,为什么有时候执行.sh脚本系统说Permission denied,明明自己就是文件的创建者?你看一眼ls -l,如果权限是-rw-r--r--,那就没有任何一个角色拿到x。文件创建默认不给执行权限,这是一个贯穿全篇的关键逻辑,后面讲umask时还会再提。
1.2 rwx的数字编码是这么来的
很多人学chmod,先背的是755、644、600这一堆数字。背是背下来了,但要真理解为什么不写成666,还得回到编码本身。
权限位在Linux内部是按二进制计算的。读权限的权值是4,写权限是2,执行权限是1,三者相加得到一个0到7的数字。
| 权限组合 | 读 | 写 | 执行 | 数值 |
|---|---|---|---|---|
| --- | 0 | 0 | 0 | 0 |
| --x | 0 | 0 | 1 | 1 |
| -w- | 0 | 1 | 0 | 2 |
| -wx | 0 | 1 | 1 | 3 |
| r-- | 1 | 0 | 0 | 4 |
| r-x | 1 | 0 | 1 | 5 |
| rw- | 1 | 1 | 0 | 6 |
| rwx | 1 | 1 | 1 | 7 |
为什么要用4、2、1,而不是1、2、3?因为4、2、1恰好是二进制的100、010、001,三位二进制位正好表示“是否有读”“是否有写”“是否有执行”。系统在判断权限时能用位运算快速比对,这是Unix设计里非常优雅的一笔。
所以rwxr-xr-x读成数字就是:7(属主)、5(属组)、5(其他),即755。rw-r--r--是644。这样你看到数字再也不想背了,直接脑内拆开成二进制位就行。
我遇到过有人用chmod 777跑通一切权限问题,其实这就是把三组权限全部放满。短时间内好用,长期看这是一颗定时炸弹。后面我会专门说为什么。
1.3 文件权限和目录权限完全是两套逻辑
很多新手栽跟头,是把文件和目录的权限混着理解。同一个字母,放在文件和目录上含义差别很大。
文件权限:
r:能读取文件内容w:能修改文件内容x:能作为程序或脚本执行
目录权限:
r:能列出目录里的文件名w:能在目录里创建、删除、重命名文件或子目录x:能进入目录,并访问目录里的文件
关键在于目录的x权限。它是访问文件的“入场券”,没有它,即使你拿到了某个文件的完整路径,也无法打开。目录的r只管你能不能“看到”文件名,但看不到文件的属性信息;x才决定你能不能真正“碰到”文件。只有r没有x时,ls可以看到一串名字,但执行stat查详细属性会被拒,因为没法穿过目录访问文件。
我经常打一个比方:目录权限像小区门禁,文件权限像房间钥匙。目录的读权限等于给你一张住户名单,但你进不去;目录的执行权限才是门禁卡,决定你能不能走进楼里,走到对应房间门口。而房间钥匙(文件权限)决定你能不能开门进去。
这也是为什么一个目录如果只有rw-没有x,你看着能用编辑器打开里面的文件,结果编辑器直接报错。路径里每一层目录都必须有x权限,权限链才算通。
1.4 目录权限最容易踩的两个坑
第一个坑是删不了文件却可以写文件。
你有一个目录权限是drwxrwx-wx,属组里的人能创建文件,但无法删除别人的文件。因为对目录而言,删除文件依靠的是目录的写权限,而不是文件本身的写权限。换句话说,能不能删除一个文件,取决于你对它所在目录有没有写权限,而不是你对文件有没有写权限。
这个机制最初可能反直觉,但恰恰是Sticky Bit存在的前提,也是后面讲公共目录协作时的重要基础。
第二个坑是改了目录权限但忘了调整属主属组。
我见过一个场景:某公司两台服务器之间同步文件,同步过去后文件属主变成了同步账户,原运维用chmod 777匆匆绕过。结果Web服务读取倒是没问题,但任何用户都能改上面的文件,最终被安全扫描揪出来。这里正确的做法应该是把目录属主设置成服务运行账户,再给到合适的组权限,而不是一刀切放给所有人。
2. 改权限之前先想清楚:chmod两种语法和属主属组变更怎么配合
2.1 先分清楚“改谁的权限”和“怎么改”
我见过不少人一上手就是chmod 777 -R,根本原因是没分清两个维度:文件“属于谁”和文件的“权限位”是两码事。
一个文件有三重属性:属主是谁、属组是哪个组、三组角色的权限位分别是什么。其中前两个决定“谁有关系”,第三个决定“有关系的人能干什么”。
所以动手前先问自己一个问题:现在问题是身份不对,还是权限不够?
- 如果文件属于A用户,而你想让B用户访问,那是属主属组的问题,应该用
chown、chgrp。 - 如果身份没问题,只是某个角色缺了读或写,那才是
chmod的活。
举个最常见的例:用nginx或者www-data跑Web服务,结果服务访问不了某个目录。你以为加个o+r就行,结果服务还是403。再看一眼,目录属主是root,其他用户连进入目录的x都没有。这时候需要调整的是目录的other权限,或者干脆把目录属组改成服务账户所在的组。身份和权限要一起考虑,单独改一个经常不解决问题。
2.2 chmod数字模式和符号模式怎么选
chmod有两种写法,我身边一直有人只用其中一种,问起来各有理由。我的建议是两种都要会,因为它们适合不同场景。
数字模式适合批量、统一的修改:
chmod 750 myscript.sh chmod 644 config.conf chmod 755 /data/web符号模式适合精确、局部的调整:
chmod u+x myscript.sh # 给属主加执行权限 chmod g-w file.txt # 去掉属组的写权限 chmod o=r file.txt # 其他人的权限精确设为只读 chmod a+r README.md # 所有角色都加读权限符号模式里的角色字母要记住:u是user(属主),g是group(属组),o是other(其他),a是all(全部)。操作符有+加权限、-减权限、=精确赋值。
实际使用中,我给脚本补执行权限基本只写chmod +x script.sh,除非明确要求收紧才补chmod 750。但如果要统一部署一批文件,数字模式更可靠,因为它一次性完整设置三组权限,不受当前权限值影响。符号模式是在当前权限基础上做加减,写错了可能导致权限越加越宽。
2.3 chown/chgrp:属主属组变更是另一门功课
修改属主和属组用的是chown和chgrp。一般写法是:
chown alice:devgroup /data/project chown alice /data/project # 只改属主 chgrp devgroup /data/project # 只改属组第一个命令把目录属主设为alice,属组设为devgroup。这是最常用的一种写法,一条命令同时解决身份问题。
递归修改用-R,比如把整个目录树都划给某个账户:
chown -R alice:devgroup /data/project这里有一个值得单独说的细节:chown递归时,如果目录里包含符号链接,不同版本的系统处理方式略有差异。GNU版chown默认会跳过符号链接本身,只作用于它指向的目标文件;如果你想修改符号链接自身的属主,需要加-h参数。所以在涉及带链接的目录时,我建议先跑一遍chown -R --verbose,看清楚它到底改了哪些文件,避免误判。
另外一点容易被忽略:chown会把一个文件的SUID、SGID特殊权限位清掉(部分系统上),设计意图是防止你把一个root属主的带SUID程序转给别人后留下提权漏洞。如果你发现改完属主后某个程序之前能提权的行为消失了,不用惊慌,这是安全机制在起作用。
2.4 一个真实场景:web项目目录应该怎么给权限
光讲命令太干了,我拿一个实际部署场景说一说。
假设某个Web服务的运行账户是www,它的主组是www。网站目录放在/data/web,计划让www来读写,另外让一组专门做发布的操作人员也能通过SFTP更新文件,而其他用户不准看。
第一步确定目录结构归属:
chown -R www:www /data/web chmod 750 /data/web这样目录的属主是www,可以完整读写执行;属组是www组,组内成员可以读和执行,但不能写;其他用户完全无法访问。发布人员如果也在www组里,那他们只能读,不能写,依然没法发布。
如果发布人员也需要写,就需要考虑组权限加写:
chmod 775 /data/web这时目录权限是rwxrwxr-x,www和www组成员都能写,其他用户只能读和执行。要注意这里other还有读和执行权限,如果连这个都不想让外人看,就要再加一层ACL或者调整权限为770。传统权限只有三组,灵活性上限在这里,后面讲ACL时你会看到它怎么突破这个边界。
如果不是Web服务的整个目录都要写,而只是某个上传目录需要写,那就单独处理:
chmod 770 /data/web/uploads这样最小化授权,才是看了这篇文章之后应该有的习惯。
2.5 chmod 777真的不建议
我知道很多人遇到权限问题第一反应就是chmod 777,因为最省事。省事的代价是安全边界的消失。
777意味着任何用户,包括那些没有账号、通过服务漏洞进入系统的进程,都可以读取、修改、执行文件。一个网站配置文件如果是777,攻击者拿到权限之后可以直接改配置,把服务变成一个任意执行命令的入口。
我的建议是:先用ls -ld看目录,用id看当前身份,想清楚到底是哪一环缺了什么权限,再动手改。哪怕最后真的要给777,也只是临时调试手段,调试完要立刻收回。改完用ls -l验证一遍,确认和预期一致再离开。
3. SUID、SGID与Sticky Bit:三个特殊权限位的功与过
3.1 SUID:普通用户凭什么能改密码
普通用户没有root权限,却可以执行passwd修改自己的密码,而密码文件/etc/shadow只有root能写。这中间靠的就是SUID。
你看一下passwd程序的权限:
-rwsr-xr-x 1 root root ... /usr/bin/passwd属主权限里的x位置变成了s。这个s就是SUID(Set User ID)。
当一个可执行文件设置了SUID,进程运行时就以文件属主的身份运行,而不是以执行者的身份运行。passwd文件属主是root,所以普通用户执行它时,这个进程的有效身份被临时提升为root,于是可以写入shadow文件。
理解了机制,也能明白为什么ls -l里rwx看到的是rws而不是rwx。本质上就是s覆盖了属主的x位置。如果属主没有执行权限又设了SUID,显示为S(大写),这种状态是无效的,不用纠结。
3.2 别给解释器加SUID:一次误配置的风险警示
SUID最容易出事的地方,是你觉得“给某个程序加个SUID就能让普通用户跑管理命令”。
我之前在一个模拟的运维项目里看到过这么一个错误:有人为了让某个监控脚本能读取系统日志,直接对/usr/bin/python执行了chmod u+s。问题很大,因为python是解释器,任何用户只要能执行python,就能通过它读取任意文件、写任意内容,甚至以root权限执行系统命令。一个原本只打算开放日志读取的脚本,直接变成了可提权的通道。
原则是:绝不要对解释器、编辑器、Shell这类能执行任意命令的程序设置SUID。bash、python、perl、vim、less都不行。
如果非要做某个需要提升权限的操作,更稳妥的做法是用sudo,把允许执行的命令精确地写进sudoers里。例如允许某个账户执行指定的服务重载命令:
user ALL=(root) /usr/bin/systemctl reload nginx这样提升的是特定命令的权限,而不是把整把钥匙交给所有人。另外建议定期扫描系统里已有SUID标记的文件:
find / -perm -4000 -type f 2>/dev/null看到不认识的SUID程序,要核实来源。
3.3 SGID用于共享目录:自动继承属组的巧妙设计
SGID和SUID类似,区别在于是以文件属组的身份运行进程。符号位显示在属组权限的x位置,数字对应的是2000。
相比SUID,SGID在目录上有一个非常实用的特性:目录开了SGID后,在它里面新建的文件和子目录,都会自动继承目录的属组,而不是创建者当前的默认组。
这解决了一个长期困扰多用户协作的问题。举个例子,一个目录属于team组,里面的文件应该归team组所有,方便组内互相协作。但如果某成员的默认组是userA,他往里放文件,文件的属组就会变成userA,其他人即使也在同一个共享目录里也改不动这个文件。给目录加上SGID之后:
chmod g+s /data/shared新建文件的属组会自动变成team,协作就顺畅了。
使用场景上,任何“多人共同维护一个目录”的场景都值得考虑它。数字写法是2770,符号写法是chmod 2770或者分开写chmod g+s。注意设置SGID的目录也要保证组内有写权限,否则继承属组没有意义。
3.4 Sticky Bit:/tmp目录为什么能挡住乱删
Sticky Bit准确翻译叫粘滞位,符号是t,显示在other权限的x位置。它和SUID、SGID不一样,它不是用来提权的,而是用来保护公共目录的。
它解决的问题是:在一个777的公共可写目录里,任何用户都能创建文件,但如果目录没有做限制,任何用户也都能删除别人的文件。因为删除文件依赖的是目录的写权限,不是文件本身的权限。
所有Linux发行版的/tmp目录都是drwxrwxrwt,最后的t就是Sticky Bit。它的规则是:在设置了Sticky Bit的目录下,只有满足以下条件之一的人才能删除或重命名文件:
- 你是该文件的属主
- 你是该目录的属主
- 你是root
给共享目录设置类似机制也很简单:
chmod +t /data/public或者数字写法1777。我推荐公共可写目录一律加上它,别裸奔。
3.5 特殊权限位速查表
| 特殊权限 | 数字位 | 符号 | 文件效果 | 目录效果 |
|---|---|---|---|---|
| SUID | 4000 | s(属主x位) | 以文件属主身份运行 | 无实用意义 |
| SGID | 2000 | s(属组x位) | 以文件属组身份运行 | 新建文件继承目录属组 |
| Sticky Bit | 1000 | t(other x位) | 无实用意义 | 仅属主可删除自己的文件 |
数字模式和符号模式可以一起用,比如chmod 4755一次性设置SUID加rwxr-xr-x,也可以单独用chmod u+s。查看目录能否写进数字形式:stat -c '%a %n' /usr/bin/passwd,输出类似4755,第一位数就是特殊权限位。
4. umask决定“出厂权限”:为什么你新建的文件总和你预期不一样
4.1 新建文件默认权限是“最大权限减掩码”
很多人第一次困惑来自这个场景:新建一个脚本,明明想让它可执行,但ls -l一看,权限是-rw-r--r--。你什么都没做错,只是没理解新建文件的默认权限由umask决定。
umask是shell的一个内置值,它像一个“蒙版”,把默认权限里某些位遮掉。Linux新建文件时,会先取一个最大权限基准,普通文件是666,目录是777,然后在此基础上去掉umask中对应的权限位。
注意文件的最大基准为什么是666而不是777?因为系统默认不给新建文件执行权限,防止意外创建一堆可执行脚本。这也解释了为什么你新建的.sh没有x,你本来就需要单独chmod +x。
umask的默认值通常是022。文件就是666去掉组和其他用户的写权限,得到644;目录就是777去掉组和其他用户的写权限,得到755。
4.2 手工算一遍umask 022
假设某用户的umask是027,那么:
- 文件:666减去027中对应的“组写”和“其他人所有权限”,得到640
- 目录:777减去027,得到750
这个计算本质上是对每一位做掩码扣除。你可以用下面的规则心算:
- umask的某一位是0,就保留最大权限的对应位
- umask的某一位是1,就去掉该位上的权限
比如umask是027,写成三位是000 010 111。对文件666来说,属主位完全保留(rw),属组位去掉写(r),其他位全部去掉(无权限),于是是640。对目录777来说,属主rwx,属组r-x,其他无,750。
当前终端的umask值直接敲umask查看,临时改直接umask 027。改完立刻新建文件验证,每次新建都会应用当前值。
4.3 umask该在哪里设置:临时、用户级、系统级
umask的作用范围有三个层级,搞清楚层级能省很多排查时间。
- 临时层:在当前终端直接执行
umask 002,只影响当前shell及它启动的子进程,关掉终端就失效。 - 用户层:写在
~/.bashrc或~/.zshrc里,该用户每次打开交互Shell都会生效。 - 系统层:写在
/etc/profile或/etc/bash.bashrc里,影响所有登录用户。
一个容易踩的坑是:通过systemd启动的服务进程,它的umask未必继承你在shell里设置的值。systemd服务有自己的环境配置逻辑,某些服务默认umask是022,某些有自己的指定值。你在命令行手动运行脚本没问题,一放到服务里权限就变了,这种情况优先去看服务单元文件里有没有UMask=配置,以及它的环境变量。
4.4 共享协作目录建议的umask值
在选择umask值的时候,先想清楚你要的协作边界。
| umask值 | 文件权限 | 目录权限 | 协作效果 |
|---|---|---|---|
| 022 | 644 | 755 | 属主可写,组和其他只读,最常用 |
| 002 | 664 | 775 | 属主和同组成员可写,适合团队协作 |
| 027 | 640 | 750 | 属主可写,同组可读可执行,其他完全不可见 |
| 077 | 600 | 700 | 只有属主可访问,私密性最强 |
如果你在一个团队目录里工作,希望同事改你上传的文件,把umask设成002就会顺手很多。但要注意,如果目录本身开了SGID,文件继承的是目录的属组,配合002才能让组内协作真正成立。如果什么文件都不想让组外成员读,027是更稳妥的选择。
5. 当传统权限不够用时:ACL访问控制列表的引入与实战
5.1 传统权限模型的三坑位限制
传统权限只有三个角色:属主、属组、其他人。这个模型的简洁性很好,但现实需求经常超过它的表达能力。
举个例子:有一个项目目录/data/project,属主是alice,属组是dev。需求是alice可读写,dev组的成员可读写,另外要给ops组的某个同事只读权限,其他人不给权限。
传统的做法难住很多人。把ops加进dev组?那他会获得组权限的全部内容,可能是读写,不是只读。单独改other权限?那所有不在dev组的人都能读。怎么都绕不开三坑位的限制。
ACL(Access Control List)就是为打破这种边界而生的。
5.2 用setfacl给指定用户开一条权限通道
ACL允许你在文件上追加多组“用户/组+权限”条目,传统的三组权限只是ACL的基础部分。命令工具是setfacl和getfacl,大多数发行版自带或可通过包管理器安装。
给指定用户加权限:
setfacl -m u:alice:rwx /data/project给指定组加权限:
setfacl -m g:dev:rwx /data/project删除某个条目,用-x:
setfacl -x u:alice /data/project清除文件上所有ACL条目,恢复传统权限:
setfacl -b /data/project用getfacl查看现有ACL:
# file: data/project # owner: alice # group: dev user::rwx user:alice:rwx group::rwx mask::rwx other::---看到这些条目,你就知道ACL是给固定角色之外的人单独开路口的。这比反复调整传统权限要清晰得多。
5.3 mask是什么:ACL里的“有效权限上限”
初次接触ACL的人,最容易对mask困惑。mask不是某个真实用户的权限,它是一道“有效权限上限”。
规则是这样的:文件上所有命名的用户(如user:alice)和命名的组(如group:dev)的ACL条目,它们的实际有效权限等于“条目里设置的权限”和“mask里的权限”的交集。
举个例子:你给alice设置了rwx,但mask值是r-x,那alice实际能用的权限就是r-x,写权限被mask挡掉了。
这意味着当你给某用户设置权限,之后又用chmod改了这个文件的组权限位,ACL mask很可能会随之变化,并悄悄影响其他ACL条目的有效权限。这是一个非常隐蔽的坑:条目里的权限明明没变,但用户突然访问不了,多半就是mask被动过。
排查这类问题很简单,getfacl输出里看看每一行是“实际权限”还是“effective权限”,加了mask且有冲突时,会标出effective字段。
5.4 实战:日志目录单独开放给只读用户
我有一个实际场景,某个服务的日志目录/var/log/myapp,属主是服务账户,默认权限700,root之外谁都进不去。现在要让一个专门做日志分析的账户logreader能只读访问,但不想把logreader加进root组,也不想改动整个目录的属主属组。
ACL就是为这个场景准备的:
setfacl -m u:logreader:r-x /var/log/myapp setfacl -R -m u:logreader:r-x /var/log/myapp第一行给目录本身放开进入和读取权限,第二行递归给目录里已有文件加同样的权限。注意这里必须给目录加x,否则logreader无法穿过目录访问里面的文件。
传统权限一行命令解决不了的事情,ACL三行搞定,而且不会改变文件原有的属主、属组成员关系,能做到最小侵入。
5.5 ACL的备份与恢复
ACL条目不像普通权限那样在ls -l里一眼能看全,所以备份时容易忽略。好在工具提供了导出恢复功能:
getfacl -R /data/project > /backup/project.acl setfacl --restore=/backup/project.acl第一条命令把目录树里每个文件、目录的ACL导出到文件里,第二条恢复。注意普通cp -a或rsync -a在复制文件时会尝试保留ACL,但如果不带相关参数,ACL可能丢失。批量迁移目录时,我建议先导出ACL,再复制数据,再恢复ACL,三步走比较稳妥。
另外,复制文件是否保留ACL也取决于目标文件系统的支持情况,某些老的非Linux文件系统不认识ACL,复制时会告警或丢弃。遇到这种问题,先看挂载参数,再谈恢复。
6. 权限报错排查链路:从一个Permission denied到定位全过程
6.1 内核到底按什么顺序检查权限
遇到权限报错,第一步不是猜,而是理解内核的检查顺序。Linux对文件的访问权限判断是“先命中,先生效”,而不是叠加取最大值。
大致逻辑是:
- 如果进程的有效用户ID等于文件属主ID,按属主权限检查
- 否则如果进程的有效用户ID属于文件属组(或者命中了ACL里匹配该用户的条目),按属组权限检查
- 否则按其他用户权限检查
这里的关键是:系统只会采用第一个匹配到的身份对应的权限,其余不再考虑。比如你是属主,但属主权限是r--,即使其他用户有rwx,你也只能读,因为流程在第一步就结束了。
ACL的检查顺序会更细,但大体也是“属主优先级最高,然后匹配的命名用户/组,最后是other”。
6.2 常规六步排查链路
我处理权限问题有一套固定流程,基本能覆盖九成以上情况。
第一步,确认当前身份:
id看清楚你是哪个用户、属于哪些组。很多人以为自己是某个组的成员,实际上当前会话并没有加载那个组的权限,这个信息必须以id输出为准。
第二步,确认目标的属主、属组和权限位:
ls -l /path/to/file先看文件本身。
第三步,从根到目标逐层检查目录权限:
ls -ld / /data /data/web /data/web/uploads这一步非常关键。路径上每一层目录都需要执行权限,缺一层就穿不透。很多权限问题根本不是出在目标文件上,而是出在某层目录上。
第四步,查看ACL:
getfacl /path/to/file确认没有ACL条目在起作用,尤其注意mask。
第五步,查看文件属性:
lsattr /path/to/filechattr +i设置的文件即使权限为777也无法修改或删除,这类隐藏属性经常让人误以为权限坏了。
第六步,确认挂载参数:
mount | grep /path如果文件系统是以只读方式挂载的,或者挂载选项里有noexec(禁止执行二进制),那权限再正确也会报错。这一步在容器、NFS、USB驱动器场景里尤其常见。
6.3 一个403的真实复盘
我用一个虚构但很典型的例子走一遍全流程。
某开发者在部署一个Web服务时,发现服务访问/media/uploads/avatar.jpg返回403,但日志目录里其他文件访问正常。
先用id确认服务运行账户:
uid=33(www) gid=33(www) groups=33(www)接着ls -ld一层层看:
drwxr-xr-x root root /media drwxr-x--- root www /media/uploads问题清楚了一半。/media/uploads的属组是www,服务运行账户www正好在组里,目录权限是750,组有读和执行,理论上可以访问。继续往下看文件:
-rw-r--r-- root root /media/uploads/avatar.jpg这里就是症结——文件的属主和属组都是root,对www来说,它既不是属主也没命中属组,只能归入“其他用户”,而其他用户权限是r--。对普通文件来说读够了,但父目录/media/uploads是750,其他用户没有任何权限,所以www根本穿不过这个目录,403由此而来。
修复方案有两种:把目录权限改为755(其他用户可读可进入),或者把文件属组改成www:
chown -R www:www /media/uploads我倾向于第二种,因为它不扩大对外暴露面,既解决了服务访问,又不会让所有人都能进这个目录。
6.4 连root都可能遇到的情况:挂载只读、隐藏属性、网络共享
有时候你以root身份操作依然碰壁,这种时候就要往系统机制层面去想。
root绕过的是传统的文件权限检查,但绕不过这几样:
- 只读挂载:
mount -o remount,rw才能重新获得写能力,前提是硬件允许。USB盘如果带了物理写保护开关,连重挂载都没用。 - 文件隐藏属性:
chattr +i和chattr +a会阻止一切写入和删除,root也不例外。用lsattr查看,用chattr -i解除,前提是文件系统支持这些属性。 - NFS的root压缩:在某些NFS挂载模式下,root用户会被映射成匿名用户,权限被压制到了
nobody级别,这取决于服务端的root_squash设置。 - SELinux这类安全模块:即使传统权限通过,安全策略仍可能拒绝访问。查看对应的审计日志,别只在文件权限层面打转。
排查时如果常规六步都没发现问题,试着查看系统日志,比如dmesg或安全模块的审计记录,里面的拒绝信息会明确指出是哪一层拦的。
顺便说一句,真正常见的权限问题其实都集中在“身份是不是匹配”“每一层目录能不能穿透”这两个点上。搞定这两点,系统里大部分权限报错都会迎刃而解。
我自己这几年的习惯是:拿到一台新机器先看一眼默认umask;给目录分配权限时先定属主属组,再定权限位,最后验证;每次改完权限,习惯性跑一遍ls -l和id确认效果。权限改动不像写代码有报错提示,很多时候失误是静默的,等出问题再回头查,代价往往更大。这一篇的内容如果认真跑一遍,以后遇到Permission denied基本能条件反射地开始按链路排查了。