最近在帮一个客户整理新上线的测试服务器,需求很简单:十几个开发测试人员的账号要建好,项目目录的读写权限要划清楚。结果账号没花多少时间,目录权限倒是来来回回改了好几轮。不是这个组的人进不去,就是那个目录所有人都能写,差点把生产测试环境搞乱。Linux的用户管理和文件目录权限就是这样,入门容易,吃透难,尤其是到了多用户共用服务器的场景,规则没定好,后面全是运维给你擦屁股。这篇把这两块从原理到实操一次讲透,新手可以照着敲命令,老手可以重点看看我踩过的权限规划相关坑。
1. 用户管理先搞明白:Linux的账号体系到底是怎么运作的
很多教程上来就给你命令,但不讲原理,最后你只会在单机上建用户,一到真实业务环境还是懵。我先带你把底层关系捋清楚,后面敲命令才有底气。
1.1 用户、组、权限的底层关系
Linux 设计哲学里有一条核心:一切都是文件。这句话放用户管理上同样成立,用户的身份不靠“用户名”来识别,靠的是 UID 和 GID 这两个数字。内核在判断文件访问权限时,只认数字,不认字符串。那个看起来像用户名的字符串,其实是给人看的标签,最终都要映射成一个整数。
root 的 UID 固定是 0,这是特权账号,内核里几乎所有限制对 UID 0 的进程都形同虚设。普通用户的 UID 在大多数发行版里从 1000 开始分配,低于 1000 的通常是系统服务用的伪用户,比如 nobody、www-data。这一点很重要,后面排查“文件属主变成了数字”的问题就是从这里来的。
组的作用是解决权限共享问题。一个文件只能有一个属主,但如果你想把一批用户放进同一个权限空间,怎么办?答案是组。每个用户创建时都会带一个同名的主组,比如你建一个用户 dev01,系统同时会建一个叫 dev01 的组,并且把 dev01 放进这个组里。这种策略叫 UPG(User Private Group),好处是用户间默认互相隔离,你不小心开了个共享目录,也不会让所有用户都自动获得访问权。除了主组,用户还可以加入多个附加组,附加组才是日常做权限隔离的核心手段。
理解权限判定有一个简洁模型:当进程访问文件时,内核先看你这个用户是不是文件的属主,是的话直接用 owner 权限;不是的话看你在不在文件所属的组里,在的话用 group 权限;都不在,用 other 权限。这个判定顺序是固定的,不会先用组之后再看属主。很多权限问题就出在这个顺序上,你以为改了组权限就生效,结果文件 owner 是你,系统根本没走 group 那条分支。
1.2 三个核心配置文件:/etc/passwd、/etc/shadow、/etc/group
用户信息不是存在数据库里,而是存在几个纯文本文件里。早期系统只用 /etc/passwd,后来发现密码哈希也放这个文件里很容易被任何人读到,于是把密码部分挪到了更敏感的 /etc/shadow。
/etc/passwd 每一行代表一个用户,格式是固定的:
登录名:x:UID:GID:描述信息:家目录:登录Shell
其中那个 x 不是密码,只是占位符。真正的密码哈希在 /etc/shadow 里,普通用户没权限读。这一点很多初学者搞反了,以为 /etc/passwd 里的 x 就是密码,或者干脆以为密码字段可以随便删,结果一删用户就免密登录了。
/etc/shadow 每一行对应一个用户,字段比 passwd 多很多,常见的有:用户名、密码哈希、密码最后修改时间、两次修改最小间隔、密码最大有效期、过期警告天数、宽限期、账号失效日期、保留字段。这里的时间都是从 1970 年 1 月 1 日算起的天数,比如看到 18500,想对应成具体日期可能需要换算一下。用 passwd 命令改密码时,实际上就是在更新这些字段。
/etc/group 的格式是:组名:x:GID:组成员列表。成员列表是附加组的集合,主组不在这里体现。也就是说,你在 /etc/passwd 里看到的 GID 是主组,你在 /etc/group 里看到的组员列表是那些把该组当作附加组的人。两条路径都要看,才能完整知道一个用户属于哪些组。
这三个文件相当于是系统的“账号数据库”。直接拿普通编辑器去改容易改坏,因为系统对格式非常敏感。正规做法是改 /etc/passwd 用 vipw,改 /etc/group 用 vigr,这两个命令会加锁并校验语法。新手图省事直接 vim 打开,一旦写错字段,可能导致某个用户无法登录,甚至系统整个起不来,这个风险没必要冒。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 常用用户管理实操命令:建用户、改权限、删账号
讲完原理,命令就好理解了。这里把项目里最常用的命令过一遍,重点说参数背后的含义,以及我实际踩过的坑。
2.1 建用户、改用户、删用户的命令实例
创建用户最常用的是 useradd,基本实例是:
bash复制useradd -m -s /bin/bash -d /home/dev01 -G web -c "dev user" dev01
拆开讲:-m 表示创建家目录,这个参数在 CentOS/RHEL 上尤其重要,不加的话 root 用户建出来的用户没有家目录,后面登录会发现 home 是个不存在的路径。-s 指定登录 Shell,如果用户是拿来跑服务的而不是给人登录的,也可以指定为 /usr/sbin/nologin,后者更安全。-d 指定家目录路径,不写默认就是 /home/用户名。-G 指定附加组,多个组用逗号分隔,比如 -G web,devops。 -c 是注释,一般写姓名或用途,方便后来人看。
有些发行版把 useradd 包装成了 adduser。Debian/Ubuntu 系的 adduser 默认会创建家目录、设置密码提示、复制模板文件,比较友好。但你想精准控制参数时,还是会绕回 useradd。
建完用户后设置密码:
bash复制passwd dev01
这个命令默认要求输入两次新密码,交互式进行。普通用户可以改自己的密码,root 可以改任何人的密码。在脚本场景里,你想批量初始化密码,可以用 chpasswd:
bash复制echo "dev01:临时密码123" | chpasswd
注意很多教程会让你用 passwd --stdin,这个参数在 CentOS/RHEL 上是可用的,但 Debian 系不一定支持,可移植性差。chpasswd 两边都能用,代码里更好统一。
改用户信息用 usermod,改动和创建时的选项基本一致。例如把用户 dev01 追加到 sudo 组:
bash复制usermod -aG sudo dev01
这里的 -a 是 append,千万别忽略。没有 -a 的话,系统会把用户从所有其他附加组里踢出去,只保留这个新组。我见过有同事改用户组的时候没加 -a,结果把用户从原有业务组踢了,表面看新组加上了,实际上项目环境里一堆权限瞬间丢了,排查半天才发现是这条命令的问题。
删除用户:
bash复制userdel -r dev01
-r 表示连家目录和邮件 spool 一起删。不加 -r,用户删了但 /home/dev01 还在,而且手动删目录又容易留下权限混乱的问题。更麻烦的是,用户删了以后,这些文件属主显示的是 UID,如果这个 UID 被后面新建的用户复用,老文件就“送”给了新用户,这种隐患属于历史遗留炸弹,后面我会在问题排查里展开。
2.2 sudo授权容易踩的几个坑
给普通用户管理员权限,大家都会想到 sudo。sudo 的配置文件是 /etc/sudoers,正规修改方式是:
bash复制visudo
不要直接 vim,因为 visudo 在保存时会做语法检查,语法不对就拒绝保存,能避免把 sudoers 写坏。写坏 sudoers 的后果很严重,所有人都会失去 sudo 权限,而且你没法在系统里直接修复,得物理机进单用户模式或者借助其他方式编辑,非常折腾。
比较大的项目里,我更推荐在 /etc/sudoers.d/ 目录下放独立文件,比如:
bash复制echo "dev01 ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx" > /etc/sudoers.d/dev01
chmod 440 /etc/sudoers.d/dev01
这里的意思是说,dev01 可以在任意主机节点上,以任意用户身份执行 systemctl restart nginx,而且不需要输密码。NOPASSWD 在自动化脚本场景下很实用,但也要慎用,权限给得太宽会变成安全隐患。
另一个常见问题是 sudo 找不到命令。有时候普通用户明明用 sudo 执行命令,系统却提示 command not found,但 root 下能执行。原因是 sudo 有 secure_path 配置,只搜索有限的白名单路径,你自己编译安装到 /usr/local/bin 下的命令不在里面。解决办法:用完整路径调用,或者去 /etc/sudoers 里扩展 secure_path。这个坑在源码装软件时经常遇到,不是权限问题,是环境变量问题,别搞混了。
还有一点,sudo 授权最好遵循最小化原则。别图省事直接给 ALL=(ALL) ALL,那是给 root 备份钥匙。真实项目里我习惯按命令授权:重启服务给 systemctl,改配置给 vim 的具体路径,查日志给 tail。虽然配置麻烦一点,但出事之后责任边界清楚,审计也好说。
3. 文件目录权限与属主管理:chmod、chown、umask实战
用户管理是为了解决“谁是谁”,文件权限解决的是“谁能碰什么”。这一章是文件目录权限的核心,也是实际工作中最容易因为“差不多懂了”而翻车的地方。
3.1 权限位、特殊权限和数字计算
Linux 文件权限最基本的九个位分成三组:属主(u)、属组(g)、其他(o),每组三个字符 rwx。r 是读,w 是写,x 是执行。用数字表示时,r=4,w=2,x=1,相加得到该组权限,比如 rwx 是 7,r-x 是 5。一条 chmod 755 命令,就是给属主 7、属组 5、其他 5。
对普通文件来说,rwx 的含义很好理解:r 能读内容,w 能修改内容,x 能作为程序执行。但目录不一样,目录的“执行”权限其实是“进入”权限。目录没有 x,你就算有 r 也进不去,ls 能列出文件名,但访问具体文件会失败;目录没有 r 但有 x,你能进入和访问里面的已知文件,却列不出目录内容。这个区别很微妙,很多人遇到“明明能看到文件名但打不开文件”的问题,根因就在这。
当目录上 w 权限单独存在而没有 x 时,你可以在目录里创建文件,但是没法进入目录查看,这组合比较反直觉。所以最稳的目录权限组合是 rwx(7)或者 rx(5),不要轻易给 6(rw)这样的组合。
除了基础权限,还有三个特殊权限位:
SUID(setuid,数字 4):主要用在可执行文件上。典型例子是 /usr/bin/passwd,普通用户能用它修改自己的密码,但它读取 /etc/shadow 时其实是以 root 身份在操作,因为 passwd 命令的属主是 root 且有 SUID 权限。设置方式:chmod u+s 文件,或者数字模式 chmod 4755 文件。
SGID(setgid,数字 2):文件上执行时以文件属组身份运行;目录上的 SGID 更有用,能让这个目录下新建的文件自动继承目录的属组,而不是继承创建者的主组。这在共享目录方案里是核心,后面场景一节会用到。设置:chmod g+s 目录,或者数字模式 chmod 2770 目录。
Sticky bit(粘滞位,数字 1):常见于 /tmp。这个位要求目录里只有文件属主、目录属主或 root 才能删除和重命名文件,而不是“谁有写权限谁就能删”。所以 /tmp 权限是 1777,大家都能写,但你删不了别人的文件。设置:chmod +t 目录,或 chmod 1777 目录。
我建议在服务器上养成习惯:凡是想做共享目录,先想明白你要不要让文件自动继承组权限,然后再决定要不要 SGID。很多权限事故,就是因为共享目录直接套了 777,所有人都能写,垃圾文件越来越多,一不小心还把别人的代码覆盖了。
3.2 umask决定默认权限,目录权限实操
每次新建一个文件,系统会给你一个默认权限,这个默认权限不是 666,而是 666 去掉 umask 中对应的位。目录默认则用 777 去掉 umask 对应位。
搞清楚一点:文件默认没有执行权限,是因为文件创建时要用 666 作为基值,再减去 umask。比如 umask 022,文件默认是 666-022=644,即属主读写、属组和其他只读。目录默认是 777-022=755,即属主能进能写,其他人只读能进。umask 027 的结果是文件 640、目录 750,这种更严格,适合多用户项目组环境。
查看当前 umask:
bash复制umask
临时修改:
bash复制umask 027
永久修改要把配置写进 /etc/profile 或 /etc/bashrc,普通用户也可以在自己的 ~/.bashrc 里设置。但要小心:服务进程的 umask 不一定来自 bash,比如 nginx、php-fpm 的 umask 由 systemd 单元文件或启动脚本决定。你改完 shell 的 umask,以为生效了,结果服务创建的缓存文件还是 644,这种不一致很气人。
实操里,调整文件属主和属组用的是 chown:
bash复制chown dev01:web /srv/data/report.txt
chown 支持递归 -R,但递归要大范围慎用,一旦参数写错,可能把系统目录的属主全改成当前用户,那种事故恢复起来非常麻烦。通常建议先锁定修改范围,再执行。
修改权限:
bash复制chmod 640 /srv/data/report.txt
或者用符号模式:
bash复制chmod u=rw,g=r,o= /srv/data/report.txt
符号模式更适合代码评审时看懂,数字模式适合快速设置。我个人的习惯是:脚本里尽量用数字模式,因为可复制;给别人讲解时用符号模式,因为可读性好。
4. 结合场景:多用户服务器上的目录规划与权限策略
来了,这是最接近真实工作的一节。一台公共服务器往往要承载多个项目组,每个组有多个账号,大家还要共享一些目录。规划好了,权限清晰,出问题好追溯;规划不好,权限爆炸,天天有人来求助说 “我写不了文件” 或者 “别人的文件我怎么也删不掉”。
4.1 用组加SGID搭建共享目录
假设场景:一台服务器上有一个 web 项目组,组名叫 web,里面有 dev01、dev02、dev03 三个用户。服务器上有个共享目录 /srv/web,希望三个用户都能在里面创建和修改文件,但文件之间不能互相乱删,而且不管谁创建的文件,组都自动归 web 组。
第一步,先把用户建好并加入 web 组:
bash复制groupadd web
useradd -m -G web dev01
useradd -m -G web dev02
useradd -m -G web dev03
第二步,创建共享目录并设置属组:
bash复制mkdir -p /srv/web
chown root:web /srv/web
chmod 2770 /srv/web
这里关键在 2770。2 是 SGID,7 是属主 rwx,7 是属组 rwx,0 是其他无权限。SGID 保证 dev01 在这个目录下新建的文件,组自动是 web,而不是 dev01 自己的主组 dev01。这样一来,dev02 也能在组权限范围内访问,不用再单独改属组。
如果想把“能写但不允许互相删”加上,可以再加粘滞位,权限改成 3770:
bash复制chmod 3770 /srv/web
这样 dev01 建的文件,dev02 可以看可以改(组可写),但不能删 dev01 的文件,只有 dev01、web 组管理员(root)能删。很多项目组内部公共目录采用 2770 还是 3770,取决于团队文化。我倾向于共享目录都开粘滞位,避免误删。
第三步,把目录权限测试一遍。用 dev01 登录创建文件:
bash复制sudo -u dev01 touch /srv/web/test.txt
ls -l /srv/web/test.txt
正常情况下属主 dev01、属组 web,权限默认按 umask 计算。如果没特殊设置,可能是 664,意思是不影响组内其他人读写。如果你希望组内新建文件默认不可被其他组读写,可以把用户默认 umask 设置成 027。
这套方案的核心思路是:共享目录按“组”划分,不让用户直接操作其他用户的个人目录,也不给 other 任何权限。运维的人一看权限位是 2770 或 3770,就知道这是一个受控共享目录,比看到 777 心安得多。
4.2 用ACL做细粒度权限,突破三组权限限制
前面说的组权限方案有个前提:文件只能有 owner、group、other 三组权限。但真实场景里,常常有这种需求:/srv/web 属于 web 组,但开发 leader 账号 ops 既不在 web 组,又需要对这个目录有完全访问权限。你不想把 ops 加入 web 组,也不想给 other 开权限,怎么办?
答案是用 ACL(Access Control List,访问控制表)。它允许你在保留传统权限的同时,针对具体用户或组单独授权。
给目录加一个用户 ACL:
bash复制setfacl -m u:ops:rwx /srv/web
给目录设置默认 ACL,让以后在目录里新建的文件都自动给 ops 相应权限:
bash复制setfacl -d -m u:ops:rwx /srv/web
查看详细权限:
bash复制getfacl /srv/web
输出里除了传统的 user、group、other,还会多出带 user:ops 的条目,以及一条 mask。mask 很关键,它是所有命名用户、具名组以及 ACL 默认权限的上限。比如 mask 是 r-x,哪怕你给用户设置了 rwx,实际生效也只剩下 r-x。修改一定注意顺序,通常先看 getfacl 再动手。
ACL 还有一个隐藏的坑:以后每次你用 chmod 修改这个文件的传统组权限时,系统可能会悄悄改动 mask,进而影响 ACL 条目的实际权限。我遇到过这个情况,之前给一个合作方账号设置好了目录权限,后来别人在目录里改了一下权限位,合作方账号突然就进不去了,查半天发现 mask 被 chmod 改掉了。所以说,用 ACL 的目录,改动权限前最好先 getfacl 拍个照。
此外,tar 备份默认不会保存 ACL,恢复后 ACL 就丢了。做备份时记得用:
bash复制tar --acls -czf backup.tar.gz /srv/web
5. 常见问题与排查技巧实录
前面都是常规操作,这一节更值钱,全是我在实际运维和项目交付中真碰过、真排查过的问题。做成速查表就是一份排障手册。
5.1 用户删除后文件变数字(UID残留问题)
有一次,同事清理离职人员账号,直接 userdel 把某用户删了,没加 -r。后来新员工入职,建了个新用户,系统分配了一个之前用过的 UID,结果发现离职员工家目录下的所有文件,属主全变成了新员工。从文件权限角度,这些文件就是新员工的“合法财产”,新员工可以随意读,甚至删除。
这个问题的根源就是:文件记录的是 UID,不是用户名。用户名删了,文件还在,UID 还在,只要后面有人“继承”了 UID,文件归属就转移了。
正确的清理姿势是:删除用户前,先找出该用户的所有文件:
bash复制find / -uid 原来的UID -ls 2>/dev/null
或者用用户名先查 UID:
bash复制id -u 用户名
然后决定这些文件是归档、迁移到其他用户,还是直接删除。删除用户时能加 -r 就加 -r,能把大部分个人文件带走。但 /tmp、/var 下的残留文件可能仍然存在,所以删除后还要再全局扫一遍:
bash复制find / -nouser -ls
这条命令会列出所有属主不存在的文件,运维跑一次能发现很多历史遗留问题。
5.2 密码策略、锁定账号与passwd的怪脾气
密码相关的问题容易让新手抓狂,主要因为不同发行版行为有差异。常见的批量设置密码方式是:
bash复制echo "dev01:NewPass123" | chpasswd
缺点是密码会在 shell 历史里出现,安全性不好。更正规的方式是写入临时文件后重定向:
bash复制echo "dev01:NewPass123" > /tmp/pw.txt
chpasswd < /tmp/pw.txt
rm -f /tmp/pw.txt
遇到需要强制用户下次登录改密码的情况:
bash复制chage -d 0 dev01
这会把密码最后修改时间设为 0,系统会要求用户下次登录时立刻改密码。如果你的需求是对大批账号设置密码有效期,可以在 /etc/login.defs 里配置 PASS_MAX_DAYS 等全局参数,也可以在 /etc/shadow 的字段里单独设置。
锁定账号和临时禁用账号也是日常高频操作:
bash复制usermod -L dev01
usermod -U dev01
或者用 passwd 的加锁方式:
bash复制passwd -l dev01
passwd -u dev01
被锁定的账号,/etc/shadow 里的密码哈希前面会多个 “!”。登录时会提示密码错误,看不出来任何特殊之处,别慌,用 getent shadow dev01 查看确认。
这里有个坑:有些发行版的 shadow 密码字段前面本来就带 “!”,表示该账号还没有设置有效密码,新创建但未 passwd 的用户就是这样。你千万别一看有感叹号就去解锁,得先确认账号是“未设密码”还是“被锁定”。未设密码的账号,直接 sudo 进系统用 passwd 设置密码即可,不是 usermod -U 的业务范围。
5.3 常见权限问题速查表
我整理了下面这份速查表,基本覆盖日常运维中绝大多数文件目录权限问题:
| 症状 | 可能原因 | 排查命令 | 解决办法 |
|---|---|---|---|
| 提示 Permission denied | 当前用户对文件或目录没有足够权限 | ls -l 文件名;ls -ld 目录名 | chmod/chown 调整,或用 ACL |
| 能列出文件名但打开文件失败 | 目录权限缺 x | ls -ld 目录名 | chmod a+x 目录 |
| 有写权限但不能创建文件 | 目录可能缺 x,或被粘滞位限制 | ls -ld 目录名 | 检查目录属主和粘滞位 |
| 新建文件属组不是想要的组 | 缺少 SGID,或用户主组错误 | getfacl / ls -ld 目录名 | 目录设 SGID 或用 chown 改属组 |
| sudo 提示命令找不到 | secure_path 未包含该路径 | sudo env 查看 PATH | 使用全路径,或修改 /etc/sudoers 的 secure_path |
| 用户无法登录Shell | 用户的 shell 不存在或不在 /etc/shells | grep 用户名 /etc/passwd | chsh 修改 shell |
| 用户登录后提示资源限制 | /etc/security/limits.conf 配置或 ulimit 限制 | 查看 /etc/security/limits.conf | 调整 nproc/nofile 等限制 |
| 备份恢复后ACL消失了 | tar 未加 --acls | getfacl 原目录 vs 新目录 | 备份和恢复都加 --acls |
| 删除文件提示不允许操作,虽然目录可写 | 目录上有粘滞位,且你不是文件属主 | ls -ld 目录名 | 由 root 或文件属主删除 |
这张表看着简单,每一行背后都是真实事故。很多运维老手依赖经验直接猜,其实先看权限位再对照表,定位速度反而更快。
个人经验,用户管理和文件目录权限是 Linux 系统里最基础也最容易被轻视的模块。做了多年运维和项目交付,我发现大部分权限事故不是因为命令不会,而是因为没有提前规划用户组、目录结构以及权限模型。像共享目录该用 2770 还是 3770、ACL 会不会被后续 chmod 改写、旧用户的 UID 文件怎么清理,这些小细节一旦爆发,往往都是在深夜的线上环境里让你措手不及。因此我强烈建议:每一台服务器上线前,先把用户规划、目录规划、默认 umask 和备份策略写进初始化的检查清单里,别等火烧眉毛再回来补。
