☰
Linux用户与文件权限全解析:从Permission denied到chmod实战
2026/9/29 12:18:27 网站建设 项目流程

1. 从一次深夜报错说起:用户与权限为什么是绕不过去的坎

刚接手 Linux 服务器的人,十个里有八个第一次被卡住的地方不是命令记不住,而是屏幕上那句冷冰冰的Permission denied。我当时的第一反应是"这文件明明在我面前,怎么就动不了",然后习惯性地开个 root 一把梭,问题当场消失,隐患也就此埋下。后来项目做大,多人共用一台机器,有人误删了别人的数据,有人部署的服务因为属主错乱起不来,才回过头认真补上Linux 用户、用户组和文件权限这一课。

这篇内容我想把这条线一次讲透:Linux 里用户是怎么被定义的、权限位那九个字符到底在说什么、chmod和chown的数字是怎么算出来的,以及最关键的——遇到Permission denied时应该按什么顺序去查。它适合刚接触 Linux 的同学,也适合已经能敲命令但总在权限上翻车的开发者。我不会只给你一堆命令清单,而是把每个参数背后的理由讲清楚,让你下次见到报错能自己定位,而不是靠搜索引擎撞运气。

2. 用户、用户组与权限的底层模型

2.1 Linux 为什么一定要做成多用户系统

很多人把 Linux 当成"单机系统"来用,装了桌面环境,开机就进自己的账号,感觉和 Windows 差不多。但 Linux 从设计的第一天起就是为多人共用一台机器准备的:上世纪的大型机时代,几十个终端接到同一台主机上,凭什么保证 A 的操作不会把 B 的数据搞坏?答案就是用户身份 + 权限校验这套机制。

具体来说,内核在做任何文件操作前,都会先问自己三个问题:当前发起操作的进程是谁(uid 是多少)、它属于哪些组(gid 和附加组)、目标文件允许谁做什么(权限位)。三个问题答完,才决定放行还是返回EACCES,也就是我们看到的Permission denied。这就是为什么同一个文件,你用 A 账号能改,用 B 账号就改不动——不是文件有问题,是身份不匹配。

理解了这一层,你就不会再把"权限"当成一个配置项,而会把它当成系统安全的第一道闸门。这也是为什么生产环境严禁所有人共用 root,以及为什么容器、CI 流水线都要专门建一个非特权用户跑服务。

2.2 三个关键文件:passwd、shadow、group

用户信息不是存在内存里的,而是落在三个纯文本文件里,它们的分工很明确:

文件存什么关键字段权限
/etc/passwd用户基本档案用户名:x:UID:GID:描述:家目录:登录Shell644,所有人可读
/etc/shadow密码哈希与过期策略用户名:哈希:最后一次改密天数:最小间隔:最大有效:警告天数640 或 000,仅 root 可读
/etc/group组与组成员组名:x:GID:成员列表644

/etc/passwd里第二字段那个x是个历史遗留的占位符,真正的密码哈希早就搬到/etc/shadow里去了。原因很直白:早期密码哈希就放在 passwd 里,而这个文件必须全局可读(很多程序要靠它做 uid 到用户名的翻译),等于把哈希暴露给所有人做离线爆破。分家之后,普通程序读 passwd 拿身份映射,只有 root 能读 shadow 校验密码,安全性立刻提升一个档次。

这里有个新手常踩的点:UID 为 0 的账号就是 root,不管它叫什么名字。有些加固规范会建议把真正的 root 改名,再建一个假的 root 账号(UID 非 0)当诱饵,原理就在这——内核只认数字,不认字符串。

2.3 用户组的两副面孔:主组和附加组

每个用户有且仅有一个主组(primary group),记录在/etc/passwd的第四字段;同时可以有零到多个附加组(supplementary groups),记录在/etc/group的成员列表里。判断权限时,内核会把主组和所有附加组一起拿来比对。

这个设计解决的是"一个人要同时参与多个项目"的场景。比如你既在dev组,又在ops组,那两个组共享的目录你都能进,不需要来回切换账号。反过来,如果你只给某人加错了组,他就会莫名其妙地被某个目录拒之门外——这是排查Permission denied时第一个要确认的地方。

id命令可以一次性把这几层关系摊开给你看:

$ id alice uid=1000(alice) gid=1000(alice) groups=1000(alice),27(sudo),1001(dev)

再配合groups、whoami,基本就能确认"当前这个进程到底以什么身份在跑"。注意,附加组的变更是要重新登录(或者新开会话)才生效的,因为进程启动时组列表就已经固定下来了。我见过太多人usermod -aG加完组,在当前终端里怎么试都是Permission denied,新开一个 ssh 会话就好了——不是命令没生效,是当前 shell 还没更新组信息。

3. 把那串-rwxr-xr-x彻底读懂

3.1 九个字符的三段结构与八进制换算

ls -l输出里最长的那一串,其实是十位:第一位是文件类型,后面九位才是权限。

$ ls -l deploy.sh -rwxr-xr-x 1 alice dev 2183 Aug 12 09:14 deploy.sh

第一位:-普通文件,d目录,l符号链接,c字符设备,b块设备,ssocket,p命名管道。后面九位分成三组,分别对应属主(user)、属组(group)、其他人(other),每组三位依次是r(读,4)、w(写,2)、x(执行,1)。没有权限的位置用-占位。

于是rwxr-xr-x换算成八进制就是:属主4+2+1=7,属组4+0+1=5,其他人4+0+1=5,合起来755。常用组合其实就那么几个:

八进制符号形式典型用途
644rw-r--r--普通文本、配置、网页文件
600rw-------私钥、~/.ssh/id_rsa、含密码的配置
755rwxr-xr-x目录、可执行脚本
700rwx------个人私有目录、.ssh目录本身
750rwxr-x---组内共享、对外封闭的目录
775rwxrwxr-x团队协作目录,同组可写
777rwxrwxrwx除非做实验,否则不要出现

换算的逻辑值得记牢而不是背表:把读、写、执行分别当成 4、2、1 三个砝码,需要哪个就加哪个。这样你看到任意一个三位数都能反推出权限,反之亦然。

3.2 chmod 的两种写法,什么时候用哪种

chmod支持符号模式和数字模式两种写法。

符号模式的结构是[对象][操作符][权限],对象取u/g/o/a,操作符取+/-/=:

chmod u+x deploy.sh # 给属主加执行权 chmod go-w notes.txt # 去掉属组和其他人的写权 chmod g=rx shared.log # 属组精确设为读+执行 chmod a+r public.html # 所有人可读

数字模式则是直接覆盖全部九位:

chmod 644 app.conf chmod 755 /opt/scripts chmod 600 ~/.ssh/id_rsa

我的使用习惯是:知道目标状态就用数字,只做增量微调就用符号。比如给一个脚本加执行权,chmod +x最直观;但给一堆配置文件统一收紧,chmod 644更不容易漏。要注意chmod 644 dir这种写法作用在目录上是灾难性的——目录没了x位就进不去,等于把自己锁在门外,这个坑后面还会细说。

3.3 chown 与 chgrp:改属主才是治本的手段

很多Permission denied的根因不是权限位不够,而是文件的属主压根就不是你。这时候chmod加权限只是治标,正确做法是改属主。

chown alice app.log # 只改属主 chown alice:dev app.log # 属主和属组一起改 chown :dev app.log # 只改属组 chown -R www-data:www-data /var/www/html # 递归改整个目录树

-R这个参数要格外小心。它会把子目录、子文件全部改成同一个属主和属组,包括那些本来不该动的(比如用户上传目录里的文件、.git目录)。另外递归改属组时,如果希望新文件能继承目录的组,更稳妥的方案是给目录加SGID 位(见下一节),而不是反复手动chown -R。

顺带说一个容易混淆的点:chown -R user:group dir之后再往里新建文件,属组默认是创建者的主组,不是目录的属组。想让新文件自动归到目录的组,就必须给目录设 SGID。

3.4 特殊权限位:suid、sgid、sticky

除了九位常规权限,还有三个特殊位藏在同一个字段里,分别占八进制的高位 4、2、1。

SUID(4):作用在可执行文件上,任何用户执行它时,进程的有效 UID 会临时变成文件属主。最典型的就是passwd命令——它是 root 所有,但因为设了 SUID,普通用户也能通过它去改自己的密码、间接写/etc/shadow。

$ ls -l /usr/bin/passwd -rwsr-xr-x 1 root root 68208 ... /usr/bin/passwd

注意属主那组的x变成了s。SUID 是提权漏洞的高发区,自己写脚本时不要随便加,尤其是 shell 脚本——绝大多数内核会直接忽略脚本上的 SUID 位,这本身就是一道安全防护。

SGID(2):作用在文件上时,进程有效 GID 变为文件属组,用法和 SUID 类似但更少见;作用在目录上时,它会让该目录下新建的文件和子目录自动继承目录的属组。这个特性在团队协作目录里非常实用:

chmod 2775 /srv/project # 目录设 SGID

设完之后,无论谁往里建文件,属组都固定是project组,同组的人都能读写,省掉了无数次chown :project。

Sticky(1):只对目录有意义,效果是目录里的文件只有属主本人(或 root)才能删除或重命名。/tmp就是标配:

$ ls -ld /tmp drwxrwxrwt 12 root root 4096 ... /tmp

其他人那组的x变成了t。没有它,任何人只要对/tmp有写权限,就能删掉别人正在用的临时文件——这就是一个共享目录从"能用"到"敢用"的分界线。

4. Permission denied 的分类排查手册

4.1 先分清是文件权限还是目录权限

报错信息通常长这样:

$ cat /var/log/app/error.log cat: /var/log/app/error.log: Permission denied

新手会直接chmod 644 /var/log/app/error.log,改完发现还是不行。原因是访问一个文件要经过它的每一级父目录,任何一级不让你通过,后面的权限再宽松也没用。这时候namei -l是最省事的工具,它会把整条路径上每一层的权限列出来:

$ namei -l /var/log/app/error.log f: /var/log/app/error.log drwxr-xr-x root root / drwxr-xr-x root root var drwxr-xr-x root root log drwx------ root root app <-- 问题在这里 -rw-r--r-- root root error.log

一眼就能看出是/var/log/app这个目录把其他人挡在外面了。没有namei的环境,用ls -ld一层层往上敲也能达到同样效果,只是慢一点。

4.2 目录的 r、w、x 到底各自管什么

目录权限和文件权限的语义完全不同,这个差异是绝大多数误判的来源:

  • r(读):允许列出目录内容,也就是ls能看到文件名。
  • w(写):允许在目录内创建、删除、重命名文件。
  • x(执行):允许"进入"目录,也就是能访问目录内的文件、能cd进去。

关键在于:删除一个文件靠的是目录的 w 权限,而不是文件本身的 w 权限。这就是为什么你在共享目录里可以删掉别人的文件(如果目录给了你写权限),却改不动它的内容。理解了这点,/tmp要加 sticky 位、共享上传目录要小心设计,就都顺理成章了。

还有一个反直觉的组合:只有 x 没有 r 的目录。你可以直接cd进去、可以访问已知文件名的文件,但ls会报Permission denied。这在某些只允许按固定路径取文件的场景里是有意为之的设计。

4.3 权限位之外的那些坑

权限位对上了还是报Permission denied?那就要往更外层找。以下是几个我实际踩过的方向:

SELinux / AppArmor。CentOS、RHEL 系默认开 SELinux,文件权限完全正确,安全模块照样拦你。判断方式是看/var/log/audit/audit.log有没有 AVC 记录,或者临时用getenforce看状态。修复手段通常是restorecon -Rv /path恢复正确的上下文,而不是直接把 SELinux 关掉。

挂载选项。findmnt -T /srv/data能看到挂载参数,如果带ro就是只读挂载,怎么chmod都没用;noexec会让脚本即使有x位也执行不了;nosuid会让 SUID 位失效。

文件系统属性。lsattr能看到i(不可变)和a(只追加)这类标志,被chattr +i锁过的文件,root 都改不动,必须先chattr -i。

POSIX ACL。getfacl能看到比ls -l更细的授权规则,有些目录的权限是通过 ACL 而不是传统三位组给的,只看ls -l会漏判。对应的命令是setfacl -m u:alice:rwx dir。

磁盘满或配额超限。写入时磁盘空间耗尽,也可能以权限类错误的形式表现出来,配合df -h和df -i(inode 用尽)一起看。

远程登录的Permission denied, please try again。这个和文件权限完全是两码事,它是 SSH 认证失败——密码错了、PermitRootLogin被禁、AllowUsers白名单没包含你,或者密钥没被authorized_keys接受。排查方向应该是/etc/ssh/sshd_config和认证日志,而不是去改文件权限。

4.4 一张报错速查表

把常见现象和对应动作整理成表,下次直接对号入座:

现象最可能的原因首选动作
新建文件同级目录下报无法创建目录缺 w 或 xls -ld看目录权限
cd进不去目录目录缺 xchmod +x或chmod 755
ls列不出目录内容目录缺 rchmod +r
能读不能写文件文件缺 w,或属主不是你先确认id,再决定chown还是chmod
删不掉别人的文件目录有 w 但被 sticky 保护用属主账号删,或由管理员处理
脚本有 x 位仍执行失败挂载了noexec,或 shebang 路径错findmnt -T检查挂载参数
改完权限依然拒绝SELinux 拦截看 audit 日志,restorecon
服务启动报无法写日志服务账号对日志目录无权限建专用用户并chown日志目录
SSH 登录报Permission denied认证失败,非文件权限查 sshd 配置和认证日志

5. 从零搭一套可用的用户与权限方案

5.1 建用户:几行命令背后的取舍

sudo useradd -m -s /bin/bash -c "Deploy account" deploy sudo passwd deploy sudo usermod -aG www-data deploy

-m会创建家目录并拷贝/etc/skel的骨架文件(.bashrc、.profile等),不写它的话家目录不会自动建,登录后一堆工具会报找不到配置文件。-s指定登录 shell,如果是纯服务账号,可以设成/usr/sbin/nologin,从源头堵死登录可能。-c是备注信息,写清楚用途,半年后回来看还能认出来。

usermod -aG里的-a是 append,漏掉它会覆盖掉用户原有的附加组,把一个还在sudo组里的人踢出去,这种事故我亲眼见过。改完组记得让用户重新登录,或者用newgrp临时切一下。

想确认结果,用id deploy和groups deploy各看一眼,比翻三个文件快得多。删除用户时,userdel默认保留家目录,要连家目录一起清掉得加-r,但生产环境我建议先保留几天再手工清理,免得误删数据。

5.2 权威原则:最小权限与专用账号

有一条原则值得刻在脑子里:每个长期运行的服务,都用独立的、权限刚好够用的非特权账号跑。数据库一个账号,Web 服务一个账号,部署脚本一个账号,彼此不共享。这样做的好处是万一某个服务被攻破,攻击者拿到的是一个几乎什么都干不了的 shell,而不是整台机器。

落到具体操作上,一个 Web 服务的典型权限布局是这样的:

sudo useradd -r -s /usr/sbin/nologin -d /var/www/app www-app sudo chown -R www-app:www-app /var/www/app sudo find /var/www/app -type d -exec chmod 750 {} \; sudo find /var/www/app -type f -exec chmod 640 {} \;

注意这里对目录和文件用了不同的权限:目录 750,文件 640。原因是目录需要 x 才能进入,而文件不需要(脚本类的可执行文件单独处理)。一刀切chmod 755 -R会让所有配置文件都变成其他人可读,把数据库密码暴露给任何能登录机器的账号。

5.3 umask:新文件的默认权限从哪来

每次新建文件,权限都不是凭空来的,而是基准权限减去 umask。文件基准是 666,目录基准是 777。默认 umask 022 时,新文件是 644,新目录是 755。

日常最需要改 umask 的场景是团队协作目录:希望新文件同组可写,就得把 umask 调成 002。

umask 002 # 当前会话生效 echo 'umask 002' >> ~/.bashrc # 持久化

在/etc/profile或/etc/login.defs里统一配置可以让所有用户生效。另外,目录上加 SGID 位配合 umask 002,是团队目录最稳的组合拳:组继承有 SGID 保证,组内可写有 umask 保证,两个都做对了,基本不用再事后补救。

5.4 sudo:把 root 权限关在笼子里

生产环境直接用 root 登录是禁忌,正确姿势是给普通账号配 sudo,并且只授权必要的命令。

sudo visudo -f /etc/sudoers.d/deploy

在里面写:

deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart app %ops ALL=(ALL) ALL

第一行只允许 deploy 用户免密重启指定服务,其他什么都干不了;第二行让 ops 组拥有完整 sudo,行首的%表示这是组名。用visudo而不是直接编辑文件,是因为它会做语法校验,写错了会拦下来——直接编辑/etc/sudoers写崩了,你就失去了唯一的提权入口。

排查 sudo 相关问题时,sudo -l能列出当前用户被授权执行哪些命令,比翻配置文件快得多,也是"我明明配了为什么还是被拒"这类问题的第一站。

6. 踩过的坑与长期维护经验

6.1 chmod -R 777:看起来最省事,代价最大

chmod -R 777是新手遇到权限问题的万能解药,也是我见过最贵的一行命令。它把所有文件的权限位全部打开,任何能登录机器的账号都能改你的代码、替换你的配置文件、往脚本里塞一行反向 shell。更糟的是,它破坏了"权限能反映意图"这个信息——三个月后你再看到这个目录,完全不知道哪些文件本该是可执行的,哪些本该是只读的。

如果已经这么干过,补救办法不是拍脑袋改回去,而是从另一个正常环境或备份里拷贝一份ls -lR的输出做对比,逐条恢复。系统目录的默认权限一般能通过重装对应的软件包来找回(包管理器会带上正确的权限信息)。

6.2 umask 与 SGID 的配合,能省掉大量重复劳动

前面提过,这里再强调一次,因为它是真正一劳永逸的做法。团队目录的正确开局是三步:

sudo chown -R :dev /srv/project sudo chmod 2775 /srv/project echo 'umask 002' | sudo tee -a /etc/profile.d/dev-umask.sh

这样一来,只管把文件往目录里丢,属组和权限自动就是对的,不需要每次部署后跑一条chown -R。反过来,如果先建了一堆文件再补 SGID,那些老文件是不会自动改属组的,还得手工修一遍。

6.3 权限修复的常规流程

线上遇到权限问题,我一般按这个顺序走,避免越修越乱:

第一步,id确认当前身份和组列表,同时确认会话是不是最新的(必要时重开终端)。 第二步,namei -l或逐级ls -ld看完整路径上每一层的权限和属主。 第三步,stat看文件的精确权限、属主、属组和三个时间戳。 第四步,如果权限看着没问题,getfacl看 ACL,lsattr看扩展属性,findmnt -T看挂载选项,getenforce看安全模块。 第五步,定位清楚后再动手,改完立刻复测,并把改动记录到变更单里。

这个流程的意义在于先诊断后治疗。直接动手改权限,往往会掩盖真实原因,过两天换个场景又犯。

6.4 面试里关于用户和权限的高频考法

这类题目在基础面试里出现频率很高,我整理了几个常见问法和自己习惯的回答角度:

问题回答的要点
chmod 755和chmod u=rwx,go=rx等价吗等价,但后者更明确表达意图
SUID 有什么用、有什么风险临时提升有效 UID,是提权漏洞高发点,脚本上通常无效
为什么/tmp是 1777sticky 位防止用户互删文件
用户加了组为什么不生效进程组列表在会话启动时固定,需重新登录
怎么让新文件自动继承目录属组目录设 SGID(2775)
删文件看文件权限还是目录权限看目录的 w 权限
怎么查一个路径上哪一级卡住了namei -l

回答时如果能顺带说清楚"为什么",而不是只报命令,通常能加分不少。

7. 我个人的几条长期习惯

说几条自己这些年养成的小习惯,都不是什么高深技巧,但确实省了很多事。

先看再改。任何时候动手改权限之前,先把ls -l和id的输出贴出来看一眼。养成这个反射之后,绝大部分问题在敲下chmod之前就已经定位清楚了。

给关键目录写注释。在/etc/sudoers.d/的自定义文件和/srv目录下放一个README,写清楚谁应该是什么权限、为什么这么设。权限设计一旦隔了半年,连写它的人都会忘。

密钥文件永远 600,.ssh目录永远 700。SSH 对这两个权限非常挑剔,权限太松会直接拒绝使用密钥,而这个报错往往含糊其辞,容易让人往错误方向排查。

生产环境的权限变更走记录。用getfacl -R /path > acl.bak在变更前存一份快照,出问题能一键setfacl --restore回滚。这个操作成本极低,但真的救过我一次。

尽量不用 root 做日常操作。需要提权时用 sudo,让每一条高权限命令都留下日志。这既是安全要求,也是出事之后唯一能还原现场的依据。

权限这件事,本质上是在回答"谁、能在什么范围内、做什么"。把这个问题想清楚了,Permission denied就不再是一个让人烦躁的报错,而是一条准确的提示——它在告诉你,你的身份和这个操作之间,还差一个明确的授权关系,找到它、补上它,问题就结束了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询