1. 先搞懂权限位的真正含义:rwx 背后是谁在读、谁在写、谁在执行
很多新手朋友第一次在服务器上敲ls -l,看到一长串drwxr-xr-x这种输出,第一反应是“这串乱码到底在说什么”。其实这东西不复杂,它就是把文件的权限状态用字符摆给你看,但很多人学了大半年Linux,仍然只记住“755 是常用权限、777 千万别用”,至于为什么是 755、为什么不能用 777,却说不出个所以然。这一节先把地基打牢。
1.1 一串 drwxr-xr-x 是怎么拆出来的
拿一个典型输出举例:
$ ls -l /etc/passwd -rw-r--r-- 1 root root 1875 Feb 21 09:32 /etc/passwd从左往右拆,第一个字符-表示这是一个普通文件;如果是d就是目录,l是符号链接,c是字符设备,b是块设备。后面跟着三组、每组三个字符,分别是所有者(owner)权限、所属组(group)权限、其他人(others)权限:
- 第一组
rw-:文件所有者 root 可读、可写,不能执行。 - 第二组
r--:属于 root 组的用户可读,不能写也不能执行。 - 第三组
r--:其他所有用户可读,不能写也不能执行。
重点来了:对普通文件来说,r表示能读取内容,w表示能修改内容,x表示能把它当作程序或脚本执行。而对目录来说,这三个位的含义完全不同,这是初学者最容易栽跟头的地方:
r(读):能列出目录里有哪些文件名。w(写):能在目录里创建、删除、重命名文件或子目录。x(执行):能进入这个目录,也就是能cd进去,并且能访问其中文件的元数据。
单独有r没有x,你虽然能ls看到目录里的文件名,但无法读取文件的 inode 信息,系统会提示权限不足。单独有x没有r,你能cd进去,但列不出目录内容。想要正常使用一个目录,通常是rx搭配出现。
1.2 数字权限 755 为什么是“7、5、5”
数字权限的本质是二进制位的换算。rwx三个位分别对应二进制中的 4、2、1,三位的组合值就是它们的和:
| 权限组合 | rwx | r-x | rw- | -wx | --- |
|---|---|---|---|---|---|
| 二进制位 | 111 | 101 | 110 | 011 | 000 |
| 十进制值 | 7 | 5 | 6 | 3 | 0 |
所以 755 就表示所有者权限是 7(rwx),所属组是 5(r-x),其他人是 5(r-x)。为什么大多数可执行程序推荐 755?因为所有者需要完整控制权,而组内成员和其他用户只需要读取和执行的权限,不需要写入。一旦放开写权限给所有人,文件内容随时可能被篡改,这在生产环境中是非常危险的事情。
我自己见过不少同事在调试时偷懒,遇到权限不对就chmod -R 777 xxx一把梭。这种操作在一个单机测试环境里可能几天都碰不到问题,但只要放到多人共用的服务器上,基本就是一个灾难:任何人都能改你的代码、删你的配置、覆盖你的日志。权限不是一个“挡住所有人”的障碍物,它更像一套门禁系统,你要做的是给正确的人发正确级别的门卡。
2. chmod 的两种写法:符号模式与数字模式,谁更不容易出错
chmod是权限管理的核心命令,全称 change mode。它支持两种写法:字母和符号组成的符号模式,以及纯数字的数字模式。两种我都经常用,但使用场景不一样。
2.1 数字模式:适合整体设置、脚本批量处理
数字模式的语法最简单:chmod 644 文件名就完事了。三个数字分别对应所有者、所属组、其他人,顺序不能乱,我见过有人写成chmod 464,还以为自己在设置读写权限,实际效果和想要的差了十万八千里。
在用数字模式时,有个非常重要的细节:它会把指定权限整体覆盖。比如一个文件原本是rwx------(700),你执行chmod 644 file,它会直接覆盖成rw-r--r--,而不是在原有权限基础上追加。这在写自动化脚本时特别容易误伤文件,所以我在批量修改前,一定会先ls -l看一眼目标文件的原始权限,确认覆盖后是否符合预期。
下面列几个高频使用的数字权限值,以及它们对应的场景:
| 数值 | 权限 | 典型用途 |
|---|---|---|
| 600 | rw------- | 私钥文件、密码文件、个人隐私文件 |
| 644 | rw-r--r-- | 普通配置文件、网页静态文件 |
| 700 | rwx------ | 私有目录、家目录 |
| 750 | rwxr-x--- | 需要组协作的项目目录 |
| 755 | rwxr-xr-x | 可执行程序、共享目录 |
| 666 | rw-rw-rw- | 极少使用,仅当所有用户都需要读写时 |
| 777 | rwxrwxrwx | 尽量避免,除非是临时共享交换目录 |
2.2 符号模式:适合局部调整、不覆盖原有权限
符号模式的语法是chmod [谁][操作][权限] 文件。谁的部分有u(所有者)、g(所属组)、o(其他人)、a(所有人);操作部分是+(增加权限)、-(减去权限)、=(设置成指定权限);权限部分还是r、w、x。
举个例子,chmod o+w file表示给“其他人”增加写权限,chmod g-x file表示去掉所属组的执行权限。如果你只想在原有基础上增加或移除某一项,符号模式比数字模式安全得多,因为它不会动其他位的权限。
符号模式里那个=的操作也很实用。比如chmod u=rwx,go=rx script.sh,它和chmod 755 script.sh等价,但可读性更强。写进脚本或交接文档时,我个人更喜欢用符号模式,因为看到u=rwx,go=rx,别人一看就明白意图,而755还需要在脑子里换算一下。
2.3 实战:批量修改配置文件时我在意什么
批量修改权限时,最忌一个-R递归下去,把所有文件统一成一个权限。比如/opt/app目录下有一堆代码,你想把所有 shell 脚本提权为 755。如果直接chmod -R 755 /opt/app,那些本该是 644 的配置文件也会变成 755——权限放宽,而且统统多了执行位,这在出安全审计的时候特别难看。
合理的做法是先找出所有需要改的脚本文件,再挨个处理:
find /opt/app -type f -name "*.sh" -exec chmod 755 {} \;或者用find的-exec做更精细的控制。同理,想批量锁紧权限,也尽量用find限定目标类型,而不是整个目录递归。
3. chown 与 chgrp:把文件“户口”迁对才是多用户协作的关键
权限位怎么设是基于“文件属于谁”的,所以文件所有者和所属组是谁,直接决定了权限规则往哪套。chown用于修改所有者和所属组,chgrp专门修改所属组。这块看起来简单,但坑特别多。
3.1 普通用户到底能不能修改文件所属组
先说结论:只有 root 能随意修改文件所有者和所属组;普通用户想改文件的所属组,有个前提——他必须是文件的所有者,并且目标组是他自己所属的组。
这条规则曾经把我绕晕过。我做了一个测试:用普通用户 A 创建一个文件,然后尝试把它 chown 给用户 B,系统直接提示Operation not permitted。这背后其实是很合理的安全设计:如果普通用户能把文件“送”给别人,那 A 就能创建一些“烫手”内容后转移所有权,从而完美甩锅,或者把别人的文件改成自己的来窃取内容。
所以日常使用中,chown基本是 root 或者拥有 sudo 权限的管理员在操作。语法非常简单:chown 用户名:组名 文件,中间用冒号连接。只改所有者可以chown 用户名 文件;只改组名可以chown :组名 文件,这等价于chgrp 组名 文件。
有一点要注意,chown后面的用户和组可以同时修改,但如果你只想改组,冒号前面留空即可,写成chown :devops fstab。在老的 Unix 语法里也有人用点号.分隔,但 Linux 下统一推荐冒号,避免混淆。
3.2 递归、符号链接与参照文件:三个容易被忽略的参数
-R递归很好理解,把目录下所有文件和子目录的所有权都改掉。但递归操作必须配合find或直接在目录级别操作,才不至于遗漏隐藏文件。比如:
chown -R appuser:appgroup /opt/app这条命令会递归修改目录内的所有权,包括隐藏文件。但如果你只想改某类文件的所有权,同样建议先find再改。
-h参数是针对符号链接的。默认情况下chown修改的是符号链接指向的目标文件,而不是链接本身。如果你想让符号链接本身的所有权也改掉(虽然很少见),需要加-h。在有些版本里,chown -R递归时遇到符号链接也会自动处理指向目标,可能会造成所有权在不知不觉间被改动,这一点做容器镜像或打包发布时要特别注意。
还有一个非常实用的参数:--reference=参考文件。它可以“抄”另一个文件的所有者和所属组:
chown --reference=/opt/app/config.yml /opt/app/config.yml.bak这样新生成的备份文件就和原文件归属完全一致,省去手工查询 uid 和 gid 的麻烦。我在做发布回滚时经常用这一招,能让新旧文件的所有权保持一致,避免一些奇怪的授权异常。
4. umask:新文件权限的“出厂设置”,不搞清楚迟早吃闷亏
很多人学 Linux 权限时会忽略umask,但实际工作中碰到最多的“为什么新建文件权限不对”类问题,十有八九跟它有关。
4.1 为什么新建一个文件默认是 644,新建目录默认是 755
操作系统创建新文件时,默认权限并不是你理解的“没设置”,而是由一个叫权限掩码(umask)的配置文件决定的。通常 Linux 系统的默认 umask 是 022,它的含义是:从基础权限里去掉“组和其他人的写权限”。
这里有一个关键背景知识:普通文件的默认基础权限是 666(rw-rw-rw-),目录的基础权限是 777(rwxrwxrwx)。然后系统用基础权限“减去”umask 的权限位,得到最终权限。事实上“减去”的算法是取反后按位与,但直观理解成“去掉某些位”也没问题。
umask 是 022 时:
- 文件:666 - 022 = 644,也就是 rw-r--r--。
- 目录:777 - 022 = 755,也就是 rwxr-xr-x。
umask 是 077 时:
- 文件:666 - 077 = 600,也就是 rw-------。
- 目录:777 - 077 = 700,也就是 rwx------。
所以你在一些安全要求较高的服务器上,会看到新创建的文件直接是 600,因为管理员把 umask 调成了 077,目的就是防止新文件和目录被组内其他人或其他人看到。
我在帮某高校的课程服务器配置环境时,发现一个很典型的问题:学生用户创建的文件全是 644,组内其他同学可以直接查看源码,考核成绩的脚本内容悄然泄露。后来把系统的默认 umask 从 022 改成 027(文件为 640,目录为 750),这类问题就基本消失了。umask 看起来不起眼,但在多人共用一台主机的场景下,它往往比 chmod 更值得你花心思去设计。
4.2 修改 umask 的三种方式
第一种是临时修改,只对当前 shell 会话生效:
umask 027执行后新文件权限立即变化。这种修改适合临时操作,比如在当前目录生成一批敏感性配置文件时,临时收紧权限。
第二种是修改当前用户的启动文件,让新设置对某个用户永久生效。如果用户用的是 bash,可以在~/.bashrc或~/.profile里加一行:
umask 077第三种是系统级默认配置,修改/etc/profile或/etc/bashrc,让所有用户都受影响。这种修改影响面大,改之前必须在测试机上验证,确认程序读取配置、日志轮转这些环节不会因为权限收紧而出问题。
顺便提醒一句:容器镜像里如果基镜像把 umask 改成了奇怪的数值,业务启动时动态生成的文件权限也会跟着变,这种问题排查起来就好比大海捞针。遇到“容器内生成的文件宿主机上没权限看”的经典问题,先检查容器进程的 umask,再检查宿主机目录的属主和组,不要一上来就 chmod 777。
5. 特殊权限位:setuid、setgid、sticky bit 的用途与风险
基础权限的 rwx 之外,Linux 还有三个特殊权限位:setuid(4)、setgid(2)、sticky bit(1)。它们往往藏在ls -l输出的执行位上,比如rws、r-xr-s、rwt等形式。很多管理员看到这类输出会紧张,可它们本身都是非常成熟的机制,理解得当就是效率工具,理解不当则是安全隐患。
5.1 setuid:为什么普通用户能执行 passwd
先看一个你每天都在用的例子:普通用户执行passwd修改自己的密码,按理说 /etc/shadow 是 root 专属,普通用户根本没有写权限。可为什么能改成功呢?
秘密就在/usr/bin/passwd这个程序上。它的权限位是-rwsr-xr-x,三组权限中,所有者的执行位不是x而是s。这个s就是 setuid 位,它表示:当任何用户执行这个程序时,程序会临时以文件所有者(root)的身份运行,因此能够打开 /etc/shadow 并修改密码。
设置 setuid 的命令是:
chmod u+s /path/to/program等价的数字写在最前面:chmod 4755 /path/to/program,其中 4 就是 setuid 的值。
给可执行程序设置 setuid 是非常强大的能力,也意味着极其危险。一个带有 root setuid 的程序如果存在漏洞,攻击者可以通过它获得 root 权限。我排查过某真实线上事故,起因就是一个开发者为了让普通用户能改一个日志文件,直接给某脚本加了chmod u+s,结果脚本本身是用 Python 写的,可以任意读写文件,整个系统差点被渗透。结论很简单:尽量不要给脚本语言加 setuid,如果确实需要提权,用 sudo 规则去精细化授权,远比粗暴的 setuid 安全。
5.2 setgid:让共享目录里的新文件自动归属同一组
setgid 位作用于目录时,特性非常有用:目录里新建的文件或子目录,所属组会自动继承父目录的组,而不是创建者的默认组。
设置方法:
chmod g+s /srv/shared或者写成chmod 2775 /srv/shared,其中 2 是 setgid 的值。
我在某公司搭建团队共享开发目录时,就把/srv/shared设置成drwxrws---,所有者 root,组 devops。这样无论谁在共享目录里创建项目,生成的中间文件都属于 devops 组,组内同事都能按组权限协作,不会再出现“A 创建的文件 B 碰不得”的问题。
如果你用完之后想撤掉 setgid,可以chmod g-s /srv/shared。这个操作不会影响已有文件的 setgid 位,只影响目录本身,需要留意。
5.3 sticky bit:/tmp 目录的安全盾牌
先做一个测试:在 /tmp 目录里创建一个文件,然后切换成另一个普通用户尝试删除它,你会发现删除不了。这就是 sticky bit 的作用——它只允许文件的所有者(或 root)删除自己的文件,即便其他人对 /tmp 有写权限。
/tmp 目录的权限通常是drwxrwxrwt,最后的t就是 sticky bit。由于 /tmp 是公共写入目录,所有人都能在里面创建临时文件,如果没有 sticky bit,任何用户都能把别人的临时文件删掉、改掉,系统早就乱成一锅粥了。
给目录设置 sticky bit:
chmod o+t /data/tmp等价数字写法是chmod 1777 /data/tmp,不过1很少单独出现,最常见的就是 1777 用于公共交换目录。这种目录的权限必须配合粘滞位,否则后果不可控。
6. 明明权限没问题却被拒绝?从“表层权限”看底层真相
有一种情况特别让人抓狂:ls -l显示的权限位明明没有问题,文件所有者、组都对,rwx 也都齐,可用户执行操作时还是提示 Permission denied。这种问题每次都容易被误判,下面几个“幕后裁判”值得逐一排查。
6.1 访问控制列表(ACL)可能比基础权限更严格
ACL 可以针对单个用户或单个组做更细粒度的授权,很多系统安装时都会默认打开。如果某个文件上有 ACL 条目,基础权限位只能反映其中一部分,真正生效的规则以 ACL 为准。
查看 ACL 用getfacl:
getfacl /data/project/script.sh输出里如果出现了user:zhangsan:rw-或带mask的行,说明 ACL 在起作用。给你遇上的用户加合适权限:
setfacl -m u:zhangsan:rx /data/project/script.sh如果排查完发现不需要 ACL,可以全部移除:
setfacl -b /data/project/script.sh其中的坑在于:ACL 里的mask像一个上限,它会收缩“组和其他用户”的有效权限。比如 mask 是r--,即便组权限是rwx,组用户实际也只有读权限。这时候用ls -l看,组权限位会多一个+符号,表示 ACL 接管了,需要仔细看。
6.2 挂载选项、文件系统属性和安全模块也是裁判
挂载参数对权限有直接影响。查看某个目录所在文件系统的挂载方式:
mount | grep /data如果看到noexec、nosuid、nodev等挂载选项,就算文件有执行权限,也会被内核拦截。最常见的例子是有人把/tmp挂载成noexec,然后抱怨“脚本明明加了 x 权限,运行却说 Permission denied”;实际上是在保护系统免受恶意脚本执行。
文件系统本身还有一层属性叫 LSATTR,可以用lsattr查看:
lsattr /path/to/file如果看到i(immutable,不可变)属性,文件连 root 都无法修改和删除,必须先解除:
chattr -i /path/to/file还有一种裁判是安全增强模块(比如 SELinux 或 AppArmor)。SELinux 在强制模式下会对进程访问文件资源的粒度做检查,常见表现为:web 服务无法读取上传目录的内容,ls -l权限却一切正常。遇到这种情形,先看系统的 SELinux 状态,并检查文件的安全上下文标注,而不是盲目chmod 777,那样既解决不了问题,还会把安全体系搞乱。
7. 权限管理两个实战场景与一套好用的排错命令
前面讲了原理,最后落地到两个我实际处理过的场景上。你可以把它们当作标准模板,遇到类似问题直接按这个思路操作。
7.1 场景一:新建用户登录后打不开家目录
现象:执行useradd zhangsan和passwd zhangsan创建完用户,zhangsan 登录后提示无法访问家目录,或者在当前目录下什么都写不了。
排查步骤和原因:用户的家目录 /home/zhangsan 通常由 useradd 自动创建,但有些发行版出于安全考虑,家目录权限默认是 700。如果父目录 /home 权限被设置得太高,比如 777,任何用户都能进入 /home 下所有子目录,chdir 倒是没问题;如果 /home/zhangsan 的属主或组不对,登录时更会直接报错。
处理命令:
chown zhangsan:zhangsan /home/zhangsan chmod 700 /home/zhangsan顺带检查一下 /home 目录本身的权限,建议chmod 755 /home,不要让其他用户跑到别人家目录里翻东西。
7.2 场景二:web 上传目录只该有“写入”权,却把整站给了 777
现象:网站要支持用户上传头像,但运维图省事,把上传目录直接 777,然后整个站点目录也递归 777。某天攻击者发现上传目录能执行脚本,直接上传一个 shell,服务器就沦陷了。
正确做法:上传目录只需要 web 服务进程可写,普通用户没必要读写:
mkdir -p /var/www/uploads chown www-data:www-data /var/www/uploads chmod 750 /var/www/uploads如果 web 服务进程是 www-data,给它 750(所有者读写执行,组读执行)就足够;如果必须允许其他人通过 web 接口写入,也尽量只针对 750 或 770,而不是 777。再加一道保险,把这个目录挂载为noexec,即使上传了可执行文件也无法运行:
mount -o bind /var/www/uploads /var/www/uploads mount -o remount,bind,noexec /var/www/uploads这种组合拳下来,上传功能保留,执行能力却被封死,攻防角度上,攻击成本会高出很多。
7.3 一套好用的排错命令清单
当感觉权限“不对”时,我喜欢按以下顺序查:
ls -l # 基础权限 stat 文件 # 更详细的权限、属主、时间信息 getfacl 文件 # 查看是否有 ACL 在拦截 namei -l 路径 # 查看路径上每一级的权限,定位是哪一层目录卡住namei -l特别适合查“路径中某层目录无执行权限”导致无法进入的情况。比如/data/project/config看起来一切正常,但 /data 是 700 而 root 的 umask 没有处理好,普通用户根本进不来。namei -l /data/project/config会输出每一级目录的权限,一眼就能定位问题出在哪里。
8. 我踩过几次权限坑之后留下的几个习惯
写到这里,所谓“权限相关指令”最核心的内容基本覆盖完了,最后聊几个我自己长期沉淀下来的习惯。
习惯一:写脚本里如果要设置权限,永远用数字模式,但注释里写清楚符号模式的等价形式。比如chmod 750 script.sh # 等价于 u=rwx,g=rx,o=,方便后来的人看明白也不容易误操作。
习惯二:每次做权限变更前,先ls -l和getfacl记录原始状态,变更后马上验证。这个习惯帮我省掉很多往返排查的时间,尤其在凌晨处理事故时,有一份变更前后对比比回忆靠谱得多。
习惯三:目录给协作权限时,优先 setgid 加组权限,而不是递归 chmod 777。表面看 setgid 多敲了几个字符,但它能保证后续新建文件自动继承组属性,长期来看维护成本低得多。
习惯四:遇到 Permission denied 先别急着加权限,先看stat和namei -l。很多问题的根源只是路径上某一层目录权限不合理,而不是目标文件本身;直接对目标文件 chmod 777 只是掩盖了问题,安全隐患还在原地。
权限指令本身并不难,难的是把它放进真实场景里去衡量:这个命令到底会打开哪扇门,又会锁上哪扇门。配好一个权限往往只需要几秒钟,但想清楚这几秒钟带来的安全边界变化,才是真正拉开差距的地方。