做运维这行久了,会发现绝大多数“莫名其妙”的问题,追到根上都是权限的锅。服务起不来、文件读不了、脚本执行报错、网站目录被人改得乱七八糟,十有八九都是权限设置出了问题。“Linux权限”这四个字看着基础,但真要掰扯清楚,里面的门道比大多数人想象得深。这篇我把自己这些年实操中积累的权限管理经验、踩过的坑、还有排查思路一次性倒出来,从最底层的原理讲到高权限位的坑,希望帮你把这块彻底吃透。
1. 权限体系的设计思路,先搞懂它为什么长这样
1.1 三种身份、三种权限,权限模型最初的出发点
Linux的权限模型设计得其实相当朴素,它从一开头就假设了“一台机器上不止一个人用”的场景(那个年代的Unix就是多用户系统)。为了让大家各用各的文件、互不干扰,就有了“三类身份”和“三类操作”的划分。
三类身份分别是:属主(user)、属组(group)、其他人(other)。为什么要分属组这一层?因为单靠属主和其他人,粒度太粗了。比如一个开发团队有5个人,你们要共享一份代码目录,既不希望老板之外的人看到,也不希望老板之外的人随便改。这时候把5个人放进同一个用户组,给这个组设置合适的权限,就比一个个文件设属主高效得多。这也是Linux权限体系里一个非常核心的思想:用“组”来抽象“一批人”的集合,让权限管理从单对单变成一对多。
三类操作分别是:读(r)、写(w)、执行(x)。读和写好理解,就是能不能查看内容和能不能修改内容。执行稍微特殊一点,它代表能不能“运行”一个文件或者“进入”一个目录。这里有一个经常被忽略的细节:对一个目录来说,r权限只能让你看到目录里有什么(比如能用 ls 列出来),但如果没有 x 权限,你是进不去的,也就是cd进不了这个目录。反过来,只有 x 没有 r,你能进去但看不到里面有什么。这个组合关系是初学者最容易搞混的地方。
这套模型的聪明之处在于它简单,简单到可以用 9 个字符完整表达:rwxr-xr--。从左到右每三位分别对应属主、属组、其他人的权限。这个字符串看着平平无奇,但它就是整个Linux权限体系的骨架。搞懂这 9 个字符,后面所有命令都是围绕它展开的。
1.2 数字表示法,为什么偏偏是 4、2、1
用rwx字符看权限很直观,但用命令改权限的时候,显得麻烦。于是就有了数字表示法。每个权限位对应一个固定权重值:r=4,w=2,x=1。任何一个角色的权限就是这三个数字加总。比如rwx= 7,r-x= 5,r--= 4。
为什么偏偏是 4、2、1 而不是 1、2、3?因为这仨数字是二进制的三个位,r是第3位(对应4),w是第2位(对应2),x是第1位(对应1)。它们之间是独立的位运算关系,可以任意组合且不产生歧义。不存在“2+2=4导致权限重叠”这种问题。数字法的本质其实是二进制掩码,理解这一点,你就能明白为什么有的chmod参数看起来像“魔法数字”了。
常见的组合我直接列一下:
- 755:属主可读可写可执行,属组和其他人只读可执行。这是最常见的程序、脚本目录标准配置。
- 644:属主可读可写,属组和其他人只读。这是普通文件的典型配置。
- 700:只有属主有全部权限,其他人啥都干不了。私密文件、密钥文件的标准配置。
- 600:属主可读可写,其他人没有任何权限。比如
id_rsa私钥、一些敏感的配置文件,就必须是这种。
举一个我实际遇到的例子:某次部署一个内部系统,对方给了一个环境变量配置文件,里面写着数据库明文密码,结果配置文件权限是644。任何能登录这台机器的用户都能读。我拿到手的第一件事就是chmod 600把权限收紧。这属于安全基线里的基础操作,但很多人根本注意不到。
2. 权限管理实操,命令背后的逻辑比命令本身更重要
2.1 chmod 和 chown 到底怎么组合用
改权限有两个维度:改文件的能力集(用什么权限),改文件的归属(这个文件是谁的)。对应两个命令:chmod和chown。
chmod最常用的两种写法我都在用:
数字写法:
chmod 755 /opt/app chmod -R 644 /var/www/html要注意-R参数是递归的,会把这个目录下所有子文件和子目录都改掉。这个参数很有用但也危险。有一次我在不知道某个目录下挂着什么的情况直接chmod -R,结果把一个脚本目录里原本应该带执行权限的.sh文件全部变成了644,随后这个系统在调用脚本时一直报“Permission denied”,折腾了半小时才缓过神来。递归改权限之前,先看一眼目录结构和原权限再动手。
符号写法:
chmod u+x run.sh # 给属主增加执行权限 chmod g-w,o-r data.txt # 去掉属组的写权限和其他的读权限 chmod a+r public.txt # 给所有人增加读权限符号写法的好处是只动你关心的那一位,不会像数字法那样把整个权限覆盖掉。比如你只想让所有用户对某个目录加一个执行权限,数字法必须写出完整的三个数字,而符号法直接chmod a+x就能搞定。
chown用来改属主和属组。注意,改属主通常需要 root 权限,或者至少是能 sudo 的用户:
chown user:group /opt/data chown -R user:group /opt/app chown user /opt/file # 只改属主,属组不变 chown :group /opt/file # 只改属组很多人在创建共享目录的时候只记得改chmod 777,这个习惯特别差。换成chown root:developers+chmod 770或者chmod 2770(这个后面讲),效果完全一样,但安全边界清晰得多。
2.2 umask,新文件权限的“出厂默认值”
你新建一个文件,默认权限是644;新建一个目录,默认权限是755。为什么会不一样?原因有两个:一是系统给文件和目录设了不同的默认权限基数,文件是666,目录是777(因为目录必须有执行权限才能进入);二是umask这个“遮罩”会从中减掉一部分权限。
umask的定义是:新文件/目录的最终权限 = 默认权限基数 - umask值。减法的执行方式是按位相减,具体来说就是把 umask 里有的权限位从默认权限里去掉。
比如常见的umask 022:
- 文件:
666 - 022 = 644(属主可写,其他人只读) - 目录:
777 - 022 = 755
比如umask 027:
- 文件:
666 - 027 = 640 - 目录:
777 - 027 = 750
理解 umask 的好处在哪?它决定了你新建出来的文件会不会意外带着过宽的权限。比如你把 umask 改成000,意味着所有新文件都是666,对于敏感环境来说这等于裸奔。所以配置服务进程的启动环境时,特别是像 Tomcat、Nginx 这类会创建日志文件的程序,我在启动脚本里会主动umask 027,保证生成的日志文件不会带着644权限让其他用户随便读。
我猜有人会问:我设置了 umask 是不是就能覆盖掉用户目录的全局配置?对,每个用户可以在自己的~/.bashrc或~/.profile里设置自己的umask,覆盖系统的/etc/profile里的默认值。公司内部如果对日志、配置文件有安全基线要求,我一般会在/etc/profile.d/放一个umask.sh统一设置,强制全员生效。
3. 特殊权限位,普通权限之外的危险与高效
3.1 setuid、setgid 和 Sticky Bit,这三个“变身技能”要分清
普通的rwx只能解决“谁能不能操作”的问题,但实际业务里经常会遇到一种尴尬:操作者没有权限,可必须得让他完成这个操作。比如普通用户要改自己的密码,而/etc/shadow文件只有 root 才能写。怎么办?Linux 的解法就是特殊权限位。
第一个是 setuid 权限位(Set User ID)。它在属主权限位的执行位上体现为字母s(比如-rwsr-xr-x)或者数字4开头(比如4755)。它生效的逻辑是:当一个可执行文件带有 setuid 权限时,任何用户运行这个文件,进程的有效用户 ID 自动变成文件的属主,通常是 root。
最典型的例子就是/usr/bin/passwd:
ls -l /usr/bin/passwd # -rwsr-xr-x 1 root root 59976 xx月 xx日 /usr/bin/passwd那个s就是 setuid。普通用户运行 passwd 命令时,进程以 root 身份修改 shadow 文件,改完就退出,整个过程用户并不知道自己“临时变成了root”。这种机制极大地减少了需要 root 授权的操作点,但风险也同步拉满——如果你在某个自己能控制的脚本上设置了 setuid,等于把一把 root 权限的钥匙交给了所有能执行它的人。
很多年前我参与过一台被入侵机器的排查,最终发现入侵者就是利用了一个带有 setuid 权限的漏洞程序,通过它提升到了 root 权限。因此我在自己的服务器上有一个习惯:没事就搜一下全盘有没有非 root 属主的 setuid 文件:
find / -perm -4000 -type f 2>/dev/null排查结果里只要是意料之外的文件,直接改掉权限。
第二个是 setgid 权限位(Set Group ID)。它在属组权限位的执行位上体现为s(比如-rwxr-sr-x)或者数字2开头(比如2755)。setgid 作用于文件和目录有两种不同逻辑:
- 作用于可执行文件:运行者会自动获得文件所属组的身份,适用于团队协作工具。
- 作用于目录:新创建的文件/子目录会继承该目录的属组,而不是创建者自己的默认组。这一条在共享目录里非常实用。
举一个我搭过的团队共享仓库的例子:目录属于root:devteam,权限设为2770。团队里不管是谁往里面放新文件,新文件的属组都会自动变成devteam,大家就都能读写协作。如果不设 setgid,A 建的文件属组是 A 自己的私有组,B 根本无权访问,协作就无从谈起。
第三个是粘滞位(Sticky Bit)。它体现在“其他人”权限位的执行位置上,记为t(比如drwxrwxrwt)或者数字1开头(比如1777)。它最常见的应用是/tmp目录:所有人都能在 /tmp 里创建文件,但只有文件属主和 root 能删除。如果没有这个位,任何用户都能把别人的临时文件删掉,那系统早就乱了套。
自己搭共享目录、上传目录的时候,如果需要“大家都往里写,但不允许互相删文件”,给目录加上粘滞位就行:
chmod +t /opt/shared # 或者 chmod 1777 /opt/shared关于这三个特殊位的判定,我直接给一个速查表:
| 权限位 | 表示位置 | 字母标识 | 数字标识 | 主要用途 | 风险提示 |
|---|---|---|---|---|---|
| setuid | 属主执行位 | s / S | 4xxx | 普通用户执行时临时获得属主身份 | 高危,可能成为提权漏洞 |
| setgid | 属组执行位 | s / S | 2xxx | 继承目录属组;运行文件时临时切换属组身份 | 中危,注意目录协作场景 |
| Sticky Bit | 其他人执行位 | t / T | 1xxx | 目录中仅允许属主/root删除自己的文件 | 低危,用于共享目录 |
小写字母表示该位和执行权限同时存在(比如rws),大写字母(rwS、rwT)表示有这个特殊位但没有执行权限。大写形式通常是不正常的状态,我排查问题时会多留一个心眼。
3.2 ACL,给权限模型装上“精细到个人”的扩展
标准权限体系有个绕不开的缺点:只能设置一个属主、一个属组,然后就是“其他人”。很多时候我们需要“这些人能读,那两个人能写”,但标准权限位做不到。比如一个公司内部的项目目录,开发组的成员要读写(rwx),测试组成员只读(r-x),项目外包人员完全不能碰。如果只用传统权限,你得建两个组,或者干脆粗暴777。而 ACL(Access Control List,访问控制列表)就是为了解决这类“超出标准模型的精细控制”而生的。
ACL 给文件/目录增加独立的访问控制条目,可以单独指定某个用户或某个组的权限,而不影响原有的属主与属组设置。最常用的命令是setfacl和getfacl:
# 给用户 zhangsan 附加读权限 setfacl -m u:zhangsan:r /opt/project # 给测试组附加读和执行权限 setfacl -m g:testers:r-x /opt/project # 删除某个特定用户的ACL setfacl -x u:zhangsan /opt/project # 查看ACL getfacl /opt/project需要注意,设置过 ACL 的文件,用ls -l查看时,权限位的末尾通常会多出一个+号(例如-rw-r-----+)。看到这个+要明白,这个文件不再只用传统权限解释了,得用getfacl看完整清单。
ACL 还有一个隐藏优势:它不会影响传统权限位对属主属组的判断。你可以把 ACL 理解成“在传统模型上贴了一层便签”,便签指定额外的用户/组权限,原模型还继续生效。这种叠加设计让 ACL 可以平滑地融入日常工作,不需要推翻现有环境。
实操中有一件事千万要注意:对目录递归设置默认 ACL,让新创建的文件自动带上指定的 ACL 条目:
setfacl -R -m d:u:zhangsan:rwx /opt/project这一条d:前缀代表 default,意思是从现在起这个目录下新建的文件,都会自动给 zhangsan 加上读写执行权限。如果忘了加d:,你只对已存在的文件做了 ACL,下次新建文件又“一朝回到解放前”,这种问题排查起来相当隐蔽。
4. 权限排查思路与实操心得,眼毒心细才能少踩坑
4.1 判断权限问题的标准流程:从 id、ls -l 到实际身份
遇到“没权限”的报错,第一步不是改权限,而是确认“当前进程到底以什么身份在操作”。很多时候你以为当前用户是 A,实际进程以 B 身份运行,那一切权限判断都是错的。
我的排查套路如下:
先确认身份:
id # uid=1000(zhangsan) gid=1000(zhangsan) groups=1000(zhangsan), 1001(devteam)输出里uid是你的真实用户 ID,gid是主组 ID,后面的groups是当前用户所属的全部附加组。
再确认目标文件/目录的权限:
ls -lnd /opt/project-n参数可以显示数字形式的 uid/gid,而不显示用户名的解析结果,这样能避免一台机器上用户名显示不全的问题。
然后做身份匹配:
- 当前 uid 等于文件属主 uid?走属主权限。
- 当前 gid 或附加组列表里有文件属组?走属组权限。
- 两者都不匹配?走“其他人”权限。
这里有个特别容易犯的错:只单独查看用户是不是属于某个组,却忽略了附加组和主组的区别。在 Linux 里,groups会列出所有组,但进程实际生效的组 ID 集合,取决于进程当时加载的凭证。如果你编辑/etc/group把一个用户加进了某个组,这个用户当前已登录的会话未必会自动刷新,必须重新登录才能生效。我遇到过同事把用户加了组之后,发现权限还没通,最后检查了半天才发现用户新开了一个 shell,但旧 shell 还在运行旧凭证。
另外,不要忽略“文件系统挂载选项”对权限的影响。比如用mount挂载时带了noexec选项,那么即使文件有x权限位,也无法在挂载目录下执行任何程序;带了nosuid,则 setuid 位会被系统忽略。排查权限问题时,mount | grep <路径>看一眼挂载参数,能少走很多弯路。
4.2 常见问题的排查结论实录
我挑几个最典型的“权限类事故”写一下当时的排查思路和最终结论,给各位做个参考。
事故一:目录明明给了 777,其他用户还是访问不了这种情况十有八九不是目标目录本身的问题,而是它的上级目录权限卡壳。比如你想让用户 A 能访问/opt/app/data,但/opt的权限是700,只有 root 能进。那用户 A 从根目录开始往上走,走到/opt就被拦住了,后面再怎么放开data也没用。 这个问题的排查方法是用namei -l /opt/app/data,它能一口气把路径上一层层目录的权限全列出来:
namei -l /opt/app/data # f: /opt/app/data # drwxr-xr-x root root / # drwxr-xr-x root root opt # drwxr-xr-x root root app # drwxrwxrwx root root data看到哪一层缺x权限,哪一层就是拦路虎。
事故二:服务能启动,但写不出日志服务能启动,说明主程序文件没问题。写不进日志,通常是日志所在目录的属主和进程用户不一致。我之前排查过一个 Java 服务,启动脚本用 root 启动进程,但日志目录的属主是普通用户,结果服务的 stdout 重定向文件就一直报Permission denied。 后来把服务改成专用用户运行,并用chown -R把日志目录的属主改成那个专用用户,问题才算彻底解决。这也点出一个规范:运行用户、日志目录、临时目录的属主必须一致,或者至少要专门设置成可写。
事故三:setuid 位设置了,不起作用这个案例来自一个深夜救火场景:一个自定义脚本设置了 setuid,普通用户执行后需要写某个 root 文件,结果还是被拒绝。排查后发现问题出在chmod 4755是设在脚本上,但很多发行版的脚本执行器并不信任脚本的 setuid 位,出于安全考虑会直接忽略它。setuid 只对编译好的二进制可执行文件是可靠的,对脚本(shell 脚本通常不受保护)往往无效。 因此在线上环境,如果确实需要让普通用户执行某个需要 root 才能完成的操作,正确做法不是依赖脚本的 setuid,而是用sudo精确授权,或者用专门的策略配置来限定命令白名单。这是更安全、可控的方案。
4.3 权限基线建议,把默认值设置成“安全优先”
做权限管理,最高级的思路不是等问题出现了再修,而是从一开始就建立一套自己的基线。我给自己管理的主机定了几条默认规则,分享出来:
- 所有非必要文件不用
777。我在审查服务器时看到777,基本会认定它是事故隐患。要么环境变量配置有问题,要么是某个安装教程让设的,但风险极高。 - 所有敏感配置文件的权限一律收紧到
600或640。比如.env、数据库连接配置、密钥文件、证书私钥,能不给别人读就不给。 - 运行服务的专用用户,不赋予任何 shell 登录能力。用
usermod -s /sbin/nologin锁定。这样即使进程被入侵,也无法轻易拿到一个交互式终端。 - 目录设
755,执行类文件设755,普通数据文件设644。这是一个很稳的默认值组合,覆盖了绝大多数场景。 - 定期扫描 setuid 文件和高权限文件:
find / -xdev -type f -perm -4000 -o -type f -perm -2000 2>/dev/null这个命令能帮你在入侵事件发生前先发现异常。
授权模型的设计上,我特别想说一句:权限管理要考虑“最小够用原则”,即只给某个角色或进程恰好能完成工作的权限,不多给一分。举个例子,Nginx 只需要读静态文件,那你就不该让它对业务目录有写权限;备份脚本只需要写备份目录,那就不该让它读数据库主目录。连 root 权限都不是越高越好,而是越精确越好。
这个思路延续到 sudo 授权也一样:任何人在/etc/sudoers里的条目越具体越好,尽量用“命令白名单”而非ALL。比如:
zhangsan ALL=(root) /usr/bin/systemctl restart nginx这比直接给他sudo su或者ALL=(ALL) ALL安全一个量级。
5. 从“能用”到“好用”:权限管理的设计心法
5.1 权限设计要根据场景来,仓储式目录和共享式目录完全不同
权限设计没有银弹,不同场景的设计逻辑差异很大。比如个人代码目录,我通常700,只允许自己和 root 访问,因为里面的代码和配置属于私密资产。
团队协作目录则相反,要的就是“组内顺畅协作,组外彻底隔绝”。这种目录我会这样做:
- 建一个专用组名,比如
devcore; - 目录属主设为
root:devcore,权限2770; - 给目录设置默认 ACL,确保新文件自动继承属组;
- 禁止
chmod -R 777的粗暴行为,做成团队规范。
这套方案最简单的表达就是:把钥匙交给组,而不是交给所有人。组里的成员随时可以进出,组外的人即便看到了目录路径,也进不去。这种布局比“所有人可读”的755严谨得多,也比“把人逐个加 ACL”维护起来轻松。
5.2 端口映射、系统服务这些特殊场景,权限要盯得更细
系统的一些敏感资源,比如/etc/shadow、/etc/sudoers、/root/.ssh,它们的权限默认值已经是安全最优了。我写过一个简单的自检脚本,定期检查这些关键文件是否被意外放宽了权限:
check_file_perm() { file="$1" expected="$2" actual=$(stat -c "%a %U:%G" "$file") if [ "$actual" != "$expected" ]; then echo "WARNING: $file permission changed: expected $expected, actual $actual" fi } check_file_perm /etc/shadow "000 root:shadow" check_file_perm /etc/sudoers "440 root:root" check_file_perm /root/.ssh "700 root:root"这个脚本虽然简单,但它在关键资产上相当于放了一个“哨兵”。运维平台可以把这个检查集成到日常巡检里,一旦权限被脚本、误操作改掉,第一时间就能发现。
还有一类容易被忽略的是“临时目录”。很多程序喜欢把临时文件放到/tmp,但/tmp是全球共享的,权限太宽松会带来符号链接攻击的风险:攻击者可以在 /tmp 里创建一个指向敏感文件的符号链接,诱导程序去写。应对方案是给程序设置私有的临时目录,比如systemd服务的PrivateTmp=true,或者设置 TMPDIR 环境变量。自己写脚本处理敏感文件时,临时目录用mktemp -d生成一个700的目录,别直接用/tmp。
5.3 从权限到安全:这条线最好一次想清楚
权限是安全的基础设施。没有清晰合理的权限体系,防火墙、加密、入侵检测都是空话。我见过很多团队,安全投入都花在流量层、应用层,结果服务器上一个用户密码泄漏了,内网横向移动简直畅通无阻,因为服务器本身的权限设计一塌糊涂,任何普通用户都能读配置、翻日志、执行运维脚本。
反过来,如果基础权限做得足够严格,即使某个服务被攻破,攻击者拿到的也只是一个很不完整的权限集,没法轻易横向扩散。比如核心应用单独用专用账户跑,连/root目录的读权限都没有,那就算进程被拿下,它能够读取的资产也极其有限。
所以我一直觉得,权限管理在运维中的位置,有点像房子的门锁:不是装完就完了,而是要在入住之前就规划好哪扇门是主门、哪扇门是后门、哪些窗户要封死。规划清楚之后,日常的查漏补缺才是有意义的工作。
我个人还有个习惯,每次在全新环境里接手服务器,第一件事就是梳理机器上所有“账号+权限”的对应关系。把每一个服务的运行用户、日志目录、配置目录权限全部列成一张表,后续排查问题的时候心里非常有底。这个过程看着琐碎,但回报在下一次故障处理时会十倍显现。
最后再分享一个小技巧:改动任何权限前,先做一次备份记录。用ls -lR > orig_perms.txt把权限清单存下来,这比盲改然后靠记忆恢复要靠谱得多。权限这种东西,一旦改错,轻则服务不可用,重则安全漏洞直接暴露,审慎永远比熟练更重要。