在服务器上折腾久了,你会发现百分之八十的“诡异问题”最后都能归结到权限上。要么是脚本执行报错Permission denied,要么是某个配置文件写了却读不出来,要么是新同事在共享目录里建了个文件,其他人干瞪眼。这篇基于“4.LInux权限”这个主题,把Linux权限这块从基础模型到实战操作,再到踩坑排错,完完整整梳理一遍。不管你是刚接触Linux的小白,还是被权限折腾过的开发者,这篇文章都能让你少走不少弯路。
1. 先搞清楚Linux的权限模型到底长什么样
1.1 三类身份与三段权限位:UGO模型
Linux权限模型最核心的一点就是“三套身份、三段权限、每个文件一视同仁”。所谓三套身份(UGO),指的是文件的所有者(User)、所属组(Group)、以及其他所有人(Others)。每个文件或目录,系统都会记录这三套身份各自拥有的权限状态。你可以用一条命令去看看实际效果:
ls -l /etc/hosts -rw-r--r-- 1 root root 221 2月 25 10:30 /etc/hosts这串输出里,第一个字符表示文件类型(-普通文件,d目录,l符号链接),后面九位就是权限位的核心。rw-是所有者权限,r--是所属组权限,最后的r--是其他用户权限。每个身份段固定三位,顺序永远是r(读)、w(写)、x(执行),缺哪一位就用-占位。
为什么说这是理解Linux权限的基础?因为在Linux里,一切皆是文件,而每个文件又都围绕UGO这三套身份做权限判定。你登录系统后的用户身份决定你是属于User、Group还是Others。内核在打开文件时只做一件事:看你是谁、你属于哪个组、然后对号入座查权限。没有复杂ACL的情况下,很多人遇到“明明我就是用root建的,为什么别人不能改”的问题,根源就是Others没有给写权限。
1.2 4-2-1权重法:为什么读是4写是2执行是1
权限位怎么记最省事?把r当成4,w当成2,x当成1,这样三个权限位的组合就是一个0到7的数字。比如rwx加起来是7,rw-是6,r-x是5,r--是4。
这套编码的本质是用二进制位来做权限开关。你可以把权限位想象成三个开关:读开关、写开关、执行开关。每个开关只有0和1两种状态,组合起来就是从000到111的八种状态。把二进制转成十进制,就是2^2=4、2^1=2、2^0=1。所以rwx就是4+2+1=7,r-x就是4+0+1=5。
为什么要用这种编码方式?一个字:紧凑。比如chmod 755 file,意思就是所有者权限是rwx(7),所属组是r-x(5),其他用户是r-x(5)。三个数字一次搞定三套身份,不用写一长串符号。而且这种数字表示法在脚本里传递参数特别方便,我写自动化部署脚本时基本全是chmod 750、chmod 600这种写法。
补充一个换算技巧:如果你想验证某个数字权限对应的实际含义,在脑子里拆二进制即可。6=4+2,说明有读有写没有执行;3=2+1,说明有写有执行没有读——这种情况基本不会出现在普通文件上,因为“没有读权限却能执行”往往意味着脚本内容对使用者不可见。
1.3 目录权限和文件权限的差异,特别容易踩坑的地方
这是新手最容易栽的地方。文件权限管的是“文件内容能不能读、能不能改、能不能执行”,目录权限管的则是“目录里有哪些文件名、能不能在里面新建/删除文件、能不能进入目录”。打个比方:目录像一个保险柜,文件是柜子里的文件袋。你如果只有文件袋的读权限,但没有保险柜的进入权限(目录x权限),你根本摸不到文件袋。
具体来说:
- 目录的读权限
r:允许列出目录内容(ls)。 - 目录的写权限
w:允许在目录中创建、删除、重命名文件。注意,只要有目录写权限,你就能删里面的文件,不管文件本身的权限是什么。这个特性让很多人措手不及。 - 目录的执行权限
x:允许进入目录(cd),以及访问目录中已知名称的文件。
所以实际运维里你会发现,给目录权限时通常都是750或者755,很少给640这种。因为只要目录没有x权限,别人连进都进不去,给再多r也没有意义。反过来,如果目录有x但没有r,能进入但列不出内容,只知道文件名才能访问。
我在给某公司做共享目录规划时,曾经把一份材料目录设成了rwxr-----(目录750),同事反馈“能看到目录存在,但打不开” —— 这就是目录少了x。排查到最后才发现,当初建目录的人只想着“别人只能读不能写”,把目录设成了744,恰好漏掉了组的执行权限。
2. 用命令掌握权限的控制权
2.1 ls -l和stat:如何快速读懂权限信息
ls -l是最常用的权限查看命令,但它只显示权限的符号形式,有些细节还要靠stat来补全。
stat /etc/hostsstat给出的信息更完整:文件大小、修改时间、权限编码(八进制和符号两种形式)、属主和属组、甚至文件的inode。比如Access: (0644/-rw-r--r--)这一行,既告诉你数字权限格式是0644,也告诉你符号格式是-rw-r--r--。
组合使用ls -l和stat的顺序建议是:先用ls -l做概览,看文件类型、权限位、属主属组;再用stat查详细属性,特别是八进制权限值,方便后续chmod直接套用。
值得一提的隐藏细节:
ls -l输出里的属主和属组显示的是名字,不是ID。如果系统里删除了某个用户,文件属主可能显示为一串数字ID。这时候stat也会显示Uid: ( 1000/ 1000),但如果你看到Uid: ( 1000/ 未知),说明这个用户已经不存在了。这种情况在做数据迁移时很常见,不要慌,用chown重新指定属主即可。
2.2 chmod:符号模式和数字模式两种改法
chmod是全套权限操作里出现频率最高的命令,没有之一。它有符号模式和数字模式两种写法。
符号模式的基本公式是:chmod [who] [+|-|=] [权限] 文件。who可以是u(所有者)、g(组)、o(其他)、a(所有身份)。举个例子:
# 给所有者添加执行权限 chmod u+x deploy.sh # 给组和其他用户去掉写权限 chmod go-w config.conf # 设置组权限为只读 chmod g=r data.txt数字模式则更直观:
# 设置所有者rwx、组r-x、其他r-x chmod 755 deploy.sh # 设置所有者rw、组无权限、其他无权限 chmod 600 id_rsa两种模式怎么选?我的习惯是:交互式操作时用符号模式,因为不用心算二进制、也不容易手抖;写脚本、批量操作时用数字模式,因为一个数字就能精准表达完整权限语义,脚本可读性反而更高。
# 批量调整目录下所有脚本的权限 find scripts/ -name "*.sh" -exec chmod 755 {} \;这里有个操作细节:chmod支持-R递归参数,比如chmod -R 750 /data/project/。但递归操作要谨慎,因为它会把子目录和普通文件一视同仁地加上执行权限。如果目录里恰好有图片、文档这类不需要执行权限的文件,就会把权限结构搞得不够干净。更精细的做法是分开处理:
# 目录统一750,普通文件统一640 find /data/project -type f -exec chmod 640 {} \; find /data/project -type d -exec chmod 750 {} \;2.3 chown和chgrp:这些文件该归谁
chmod管权限,chown管归属。改了归属,权限模型的三套身份就都齐活了。
# 修改属主 chown alice report.pdf # 同时修改属主和属组 chown alice:team /data/share # 只修改属组 chown :team report.pdfchgrp命令和chown :组名功能相同,但我更喜欢用chown的统一写法,因为一条命令同时处理属主、属组,效率更高。注意chown同样支持-R递归,用法和chmod一致。
chown经常配合chmod一起做项目初始化:先创建目录、设置属主和属组,再给不同身份的权限。比如说:
mkdir -p /srv/www/demo chown -R www-data:devops /srv/www/demo chmod -R 750 /srv/www/demo这段操作的意图是:目录归www-data用户和devops组共同管理,所有者有全部权限,组内有读和执行权限,其他人一律不许进入。这个组合是我在实战中使用频率最高的一套初始配置。
一个常见的坑:
chown之后,如果属主或属组里有任何一个不存在,命令不会报错,但权限语义就乱了。例如chown ghost:ghost config.ini,如果系统里没有ghost这个用户,文件属主就会显示成数字ID。所以在初始化环境时,务必先确认用户名和组名拼写正确,最好用id 用户名验证。
3. 实战:从零搭建一个多用户协作目录
3.1 预演环境规划
纸上谈兵没意思,我们来做一个完整的小实验:创建一个团队共享目录,组内成员可以读写,但外部用户只能读,同时保护组内文件不被外部删除。
先明确角色:
- 用户:
alice(团队组长)、bob(团队成员)、tom(外部访客) - 组:
devteam(团队成员组) - 目录:
/srv/share
要求:alice和bob能在/srv/share里自由建文件、删文件、修改文件;tom能读目录和文件,但不能创建、删除或修改;任何人都不能通过共享目录去修改别人的文件属性和覆盖不属于自己的文件。
3.2 创建用户与用户组,并完成初始授权
先准备用户和组:
# 创建组 groupadd devteam # 创建用户,并附加到devteam组 useradd -m -G devteam alice useradd -m -G devteam bob # 创建外部访客 useradd -m tom确认用户已加入组:
id alice id bob输出里可以看到groups=...,devteam这样的信息。如果看不到devteam,说明用户没有成功加入,后续的组权限设定会失效。这是整个实验里最基础的一个检查点,不要略过。
接下来创建共享目录:
mkdir /srv/share chown root:devteam /srv/share chmod 770 /srv/share这里770的含义是:属主(root)有全部权限,组(devteam)有全部权限,其他人无权限。注意我把目录属主设成了root,初始状态只有root能改属性和目录本身,而devteam组里的成员可以在目录里创建文件、删除文件,因为目录对组的权限是7。
但还有一个问题:tom作为外部访客,按需求要能读。目前770让tom连目录都进不去。要想让外部可读但不可是写,又不能让组内成员受影响,就得用到“其他用户”这一位的权限了。我改成775,组内权限不变,其他人的权限是r-x,即可以读和进入,但没有写权限。
chmod 775 /srv/share现在ls -l /srv应该能看到drwxrwxr-x这样的权限位。tom可以cd /srv/share并阅读文件,但尝试创建文件时会被拒绝。
3.3 设置文件默认权限与控制umask的影响
协作目录建好了,又冒出一个新问题:alice在共享目录里新建的文件,默认权限是什么?每个用户有默认的umask。大多数发行版默认是022,也就是说新建文件的默认权限是644,新建目录是755。
对于共享目录来说,644意味着devteam组的同事只能读、不能改。这显然不符合协作文档的需求。解决思路有两条:
第一条是教团队每个成员自己改umask,让新文件默认对组可写:
umask 002 touch /srv/share/meeting-notes.mdumask 002会让新建文件的权限变成664,组内成员可以编辑。但问题在于,这个设置只对当前会话有效,且依赖每个人的自觉,实际落地效果很一般。
第二条更稳妥的方式是用POSIX ACL做默认权限控制。比如:
setfacl -d -m g:devteam:rwx /srv/share getfacl /srv/share默认ACL(default ACL)会让新建文件自动继承组内的权限。这个方案不需要每个成员额外操作,权限控制更统一。很多长期运行的共享目录,最终都走向ACL方案,原因就在这里。
但在实验环境里,为了把UGO的基本功练扎实,我更推荐先用umask调一次,理解机制,再切换到ACL做正式部署。两者各有优劣,但缺一不可。
3.4 用特殊权限位锁定共享目录安全
还有一个隐患:目录是775,tom没有写权限,但tom只读不写也没问题。现在假设tom对目录有执行权限,他能不能删掉里面的文件?不能,删除文件需要目录的写权限,tom没有。但如果目录被设置成了777,那tom也能删除别人文件了。所以“共享目录”的权限要给得克制,不能图省事直接777。
如果你希望共享目录里的“新建文件”默认属于某个组,而不是个人的私有组,可以给目录设置SGID位。SGID是一个特殊权限位,它会让目录下新建文件的属组自动继承目录的属组,而不是创建者的主组。
chmod g+s /srv/share执行后,/srv/share的权限位变成drwxrwsr-x,注意组权限位的x变成了s。此时alice和bob在目录里新建任何文件,其属组都会自动变成devteam,而不是alice的主组。这样一来,同组成员可以基于组权限协作,不用每次手动chown :devteam。
这个位同时配合ACL使用,效果也稳定。实际项目中,共享目录几乎必开SGID。
4. 特殊权限位与安全底线:SUID、SGID、Sticky Bit
4.1 SUID、SGID、Sticky Bit:三个特殊权限位的作用与风险
除了基础UGO权限,Linux还有三个特殊权限位。它们是权限模型里最容易让人困惑的部分,也是安全风险的高发区。
SUID(4):作用于可执行文件。当一个程序被设置了SUID,它运行时以“文件属主”的身份运行,而不是“当前用户”的身份运行。最经典的例子就是/usr/bin/passwd:
ls -l /usr/bin/passwd -rwsr-xr-x 1 root root 68208 2月 25 10:30 /usr/bin/passwd注意所有者权限位的x变成了s。普通用户执行passwd时,会临时获得root权限去改/etc/shadow。这是SUID的正面用途。反面案例是某些被植入后门的程序:如果可执行文件被设置了SUID且属主为root,那么任何执行它的用户都会获得root身份的部分权限。
SGID(2):作用于目录时(前面已讲),目录中新建文件的属组自动继承目录属组。作用于可执行文件时,程序以文件属组身份运行。这个位在协作目录场景里非常有用,但在可执行文件上要谨慎使用。
Sticky Bit(1):作用于目录。目录设置了粘滞位后,只有文件属主、目录属主或root用户才能删除/重命名目录中的文件。Linux的/tmp目录就是最典型例子:
ls -ld /tmp drwxrwxrwt 20 root root 4096 2月 25 10:30 /tmp其他用户的x被替换成了t。所有用户都可以在/tmp里创建文件,但没有权限删除别人的文件。共享目录里如果不想让成员互删文件,这个位也可以派上用场。不过要注意,如果你在协作目录里同时用了SGID和Sticky Bit,权限位显示会变成drwxrwsrwt,两个特殊位都占了。
设置特殊位的命令也不复杂:
# SUID chmod u+s /path/to/program # SGID(目录) chmod g+s /srv/share # Sticky Bit chmod +t /srv/share也可以直接用数字方式:
# SUID + 755 chmod 4755 program # SGID + 770(目录) chmod 2770 /srv/share # Sticky + 1777 chmod 1777 /srv/share数字模式下第一个数字就是特殊位的权重组合:4是SUID,2是SGID,1是Sticky。组合起来也可以,比如chmod 6755表示SUID+SGID+755。
重要警告:非必要不要给普通可执行文件设置SUID。尤其不要为了让某个脚本能绕过权限检查就加上
u+s。一旦给shell脚本设置SUID,很多系统会直接忽略SUID位(出于安全考虑),而二进制程序设置SUID则意味着任何执行者都拥有属主权限,这等价于在系统里开了一扇后门。
4.2 用户与密码权限管理:安全底线怎么守住
权限控制不止是文件和目录。用户能不能登录、能不能用sudo、密码存在哪里,这些都和权限强相关。
/etc/passwd是所有人可读的,但里面只有用户名、UID、GID这些基本信息;真正的密码哈希在/etc/shadow,默认权限是-rw-------(600),只有root能读写。这也是为什么普通用户不能用cat /etc/shadow查看密码。
sudo的授权文件/etc/sudoers默认权限是440,语法错误会导致sudo无法使用。改这个文件有专门命令:
visudo为什么要用visudo而不用vim直接改?因为visudo会做语法校验,如果配置写错,保存时会被拦截,避免把系统锁死。我见过有人直接vim /etc/sudoers保存退出后,所有sudo命令全部报错,机器得像救援模式一样进,非常头疼。
给普通用户加sudo权限,最好不要直接改/etc/sudoers,而是在/etc/sudoers.d/下新建文件:
echo "alice ALL=(ALL) ALL" > /etc/sudoers.d/alice chmod 440 /etc/sudoers.d/alice这样管理起来更清晰,单点配置出错也不影响主文件。再次强调,这个文件同样需要语法校验,所以正规流程仍是visudo -f /etc/sudoers.d/alice。
日常安全底线我习惯默认做三件事:
- 所有需要登录的普通用户,密码策略要启用有效期限,强制定期更换。
- root远程登录直接关闭,改为普通用户+sudo方式,用密钥认证。
- 定期用
find / -perm -4000 -type f检查全盘SUID程序,发现可疑项立即排查。
5. 常见问题与排查技巧实录
5.1 高频报错速查表与对应解法
这一节整理我实际工作中遇到最多的权限报错,直接做成速查表。
| 报错信息 | 原因 | 解决办法 |
|---|---|---|
Permission denied | 访问文件/目录时权限不足 | 检查当前用户是否属于对应属主或组,用ls -l查看权限 |
cannot open ... (Permission denied) | 写入文件但该文件不可写,或所在目录不可写 | 检查文件权限和目录权限,stat查看具体路径 |
/bin/bash: Permission denied | 执行脚本文件但没有执行权限,或者文件系统挂载了noexec | 给脚本加chmod +x,或调整挂载选项 |
sudo: unable to resolve host | /etc/hosts与主机名不匹配 | 检查/etc/hostname和/etc/hosts |
user is not in the sudoers file | 用户没有sudo授权 | 用root执行visudo或创建/etc/sudoers.d/用户文件 |
No such file or directory(执行脚本时) | 脚本内第一行解释器路径错误,或者脚本使用CRLF换行 | 检查#!/bin/bash路径,确保换行符是LF,别用Windows换行 |
Operation not permitted | 权限足够但操作被限制(如无能力、文件不可变属性) | 检查文件属性lsattr,root也不一定越过AppArmor/SELinux限制 |
这张表基本覆盖了90%的日常权限问题。遇到没见过的报错,第一步永远是查权限,第二步查属主属组,第三步查特殊属性,按这个顺序排查不会错。
5.2 排查方法论:从报错信息到定位问题
排查权限问题的核心思路是:顺着“身份 -> 路径 -> 权限 -> 上下文”这条链路走。
身份排查很简单,先确认当前用户是谁、属于哪些组:
id whoami groups路径排查要看目标文件的完整路径,确认没有经过符号链接。符号链接的权限判定比较特殊:ls -l看到的是链接自身的权限,但实际操作时系统用的是链接指向的目标的权限。如果你对链接本身给了权限却还是无法访问,多半是目标文件权限不对。
ls -ld /srv/share readlink -f /path/you_check权限排查就是看目标和它所在所有父目录的ls -ld。很多“文件权限没问题但访问不了”的情况,问题出在父目录缺少执行权限。
上下文排查则要看有没有额外的限制层,最常见的是SELinux和AppArmor,其次就是文件系统挂载选项。检查命令很简单:
getenforce mount | grep /data如果getenforce输出Enforcing,那么SELinux可能拦截了本来“应该允许”的操作。遇到这情况不要急于关闭SELinux,先用ausearch -m avc看拒绝日志,根据日志调整上下文或布尔值。关闭SELinux等于把安全层卸了,换个环境就忘,迟早出问题。
5.3 两个真实场景复盘
场景一:某次部署Web应用,页面一直报500错误。查后端日志看到无法读取配置文件,文件权限是640,属主root:root,而运行进程的用户是www-data。进程用户既不是root也不在root组,所以读取直接被拒。解决方式是把配置文件的属组改成与进程相同的组,或给其他用户加读权限。但考虑到安全,最终我选择用目录组授权的方式:chown root:www-data config.ini && chmod 640 config.ini。这套组合现在被我记为标准配置。
场景二:某同学把一个共享目录直接chmod -R 777了,说“这样谁都能用”,结果没过多久发现里面出现一个陌生文件,占着磁盘空间还删不掉。原因就是777给了所有人写权限,任何人都能创建文件,且文件属主不是他,普通删除也受目录sticky位限制。正确的做法是设置目录属组devteam、组权限770,或者用ACL精确控制,绝不无脑777。做运维和开发,权限这种事,越“宽容”越容易失控。
6. 结尾:权限管理的一点心得体会
踩过这么多次坑之后,我最大的体会是:Linux权限的设计并不复杂,它就像一套严谨的门禁系统——身份、门锁、规则各就各位,出了问题几乎都能按图索骥。真正让人栽跟头的,通常不是命令记不住,而是“想当然”。总觉得文件权限对了就行,没管父目录;总觉得加了sudo就能为所欲为,没注意sudoers语法;总觉得设置个777最省事,没想过为这个便利付出的代价是毫无安全性可言。
最后再分享一个我已经固化成习惯的操作:每次拿到一台新环境,第一件事就是用umask 027把所有新建文件默认权限收敛到最安全档位,再用find /srv -xdev -perm -2000搜索SGID文件做个统计。权限这事,宁可一开始紧一点,也别等出问题再补,因为权限相关的故障,通常都是“你意识到问题的时候,别人已经进来了”。这是Linux服务器管理里最值钱的一条经验,没有之一。