1. 为什么Linux要搞用户和组这套东西
很多刚接触Linux的人都会问一个问题:为什么Windows下我开机就能用,Linux却非要分什么用户、组、权限?直接用root不行吗?
先说结论:root确实能做所有事,但你不会想用root干所有事。Linux沿用了Unix几十年的多用户设计哲学,这套体系的核心目标是权限隔离——让每个进程、每个文件都明确归属到某个用户和组,谁能读、谁能写、谁能执行,系统里每个资源都有清晰边界。
举个生活化的例子:一个公司里,普通员工只能进自己的工位和公共区域,财务室、机房的钥匙只有特定部门有。如果所有人都拿万能钥匙,任何一台电脑都能访问公司所有资料,那内部出问题只是时间问题。服务器也一样——如果所有应用都用root跑,一个Web服务的漏洞被利用,攻击者直接拿到整台服务器的最高控制权,连数据库备份文件都能顺手带走。而如果Web服务以普通用户的身份运行,漏洞被利用后攻击者能碰到的只有那个用户权限范围内的东西,伤害面小很多。
这也就是我会把用户与组管理称为“Linux系统安全的地基”——权限规划做不好,后面所有安全加固都白搭。不管你是刚装好一台CentOS想搭开发环境,还是在公司接手一台多部门共用的生产服务器,或者只是给自己折腾NAS、树莓派这类小玩具,搞明白用户和组的管理逻辑都是绕不开的第一步。
这篇内容我会从底层原理讲到具体命令,把用户、组、权限、以及sudo授权这几块串起来。不是单纯背命令,而是讲清楚每个操作背后的逻辑——你理解了为什么这么设计,后面遇到奇怪的问题自己就能推断出来。
2. 核心概念串讲:用户、组与权限到底怎么协作
2.1 用户是身份的起点,组是权限的集合
Linux里的“用户”并不只是登录时那个用户名。每个用户对应一个UID(用户ID),内核实际只认数字,不认名字。用户名只是给人看的映射而已。每个用户的文件、进程都打着这个UID的标记,内核判断“这个文件能不能删”“这个进程能不能杀”,本质是比对UID和文件权限位。
“组”(Group)则是把多个用户圈在一起的权限集合。为什么要多一层组?想象一台服务器上有开发A、开发B、开发C三个同事,他们都需要读写某个项目目录。如果你按用户逐个授权,每加一个用户就要改一次文件的ACL或权限位,非常啰嗦。更优雅的做法是建一个dev组,三个人都加入dev组,只要给目录设置组读写权限,后续来了开发D,只需要把他加进dev组就完事——这就是组存在的意义:让权限管理从“一对一”变成“一对多”。
Linux还规定每个用户必须有一个主组(primary group)。用户新建文件时,文件的属组默认就是自己的主组。一个用户还可以同时加入多个附加组(supplementary groups)。区分主组和附加组的意义在于:主组决定你新建文件的默认属组,附加组让你能同时拥有多个领域的访问权限。比如一个用户主组是自己的私有组,附加组有docker组(因此能用docker命令)、wheel组(因此能sudo),三者互不冲突。
2.2 权限位的阅读方法:rwx不止是三个字母
权限位的本质是三组“rwx”的组合。第一组是属主(owner)权限,第二组是属组(group)权限,第三组是其他所有人(others)权限。r代表可读(4),w代表可写(2),x代表可执行(1),数字权限就是这三者累加。
这里有个很多新手踩过的坑:目录的x权限和文件的x权限含义并不完全一样。对文件来说,x是“能执行”;对目录来说,x是“能进入”——没有目录的x权限,哪怕你有r权限能列出目录内容,也进不去这个目录,人家叫“扫描权限”。同样,目录的w权限也不是“能改文件内容”,而是“能在目录里新建、删除、重命名条目”。这也是为什么对一个共享目录,通常要同时给rx,否则别人能看目录列表却进不去。
顺带说一个常见困惑:普通用户A能不能删除root创建的、权限是644的文件?关键不在文件本身,而在文件所在的目录。如果目录对用户A有w+x权限,那A可以在目录里删除任何条目,即使那个文件属于root、权限只读。很多“共享目录被别人误删”的事故就是这么发生的。这个细节在项目协作场景特别重要——你需要区分“文件本身可写”和“目录允许删除”是两回事,保护文件往往要保护它所在的目录,而不是文件本身。
2.3 新建用户会导致哪些默认文件出现
当你执行useradd还附带创建家目录时,系统会从/etc/skel目录拷贝一份模板文件到新用户的家目录里。这个/etc/skel里通常有.bashrc、.profile这类shell配置文件。很多人不知道这个目录是可以自定义的——你可以往/etc/skel里放一个欢迎脚本、一个项目规范文档、甚至预设好的.vimrc,以后每次新建用户都会自动带上,省去逐台机器配置的重复劳动。我自己管理服务器时会把一些统一的别名、环境变量template也放进去,新来的同事账号一建好,基础环境直接就是齐的。
3. 配置文件逐行解读:用户信息到底存哪了
3.1 /etc/passwd与/etc/shadow的分工
大多数Linux发行版的用户信息分散在两个核心文件里。先看/etc/passwd,每一行对应一个用户,用冒号分隔成7个字段:
username:x:1000:1000:姓名注释:/home/username:/bin/bash第一个字段是用户名;第二个字段在早期Unix里存密码哈希,现在统一是x,表示真正的密码放在/etc/shadow里(这是出于安全考虑,/etc/passwd需要被世界可读以便各种工具查询用户信息,密码不能跟着暴露);第三个字段是UID;第四个字段为主组GID;第五个字段为注释信息,通常存姓名或描述;第六个字段是家目录路径;第七个字段是登录shell。如果第七个字段是/sbin/nologin,代表这个用户被禁止登录系统,只能用来运行服务或占位——很多系统服务账户都是这种类型。
/etc/shadow只有root可读,每一行同样用冒号分隔,核心是第二字段的密码哈希。密码哈希前常有不同前缀:$6$开头是SHA-512,$y$是近年流行的yescrypt(部分新发行版),$2y$是bcrypt。后面的字段还包括密码最后修改时间、过期策略、宽限期等等。实际上,当你执行passwd修改密码,改的其实是shadow文件。
为什么一定要用shadow文件?因为早期密码直接存在passwd里,那个文件可能被普通用户读取来离线爆破。把密码挪到只有root能读的shadow里,攻击面立刻小很多。这是Linux安全设计演进的一个典型例子。
3.2 /etc/group和/etc/gshadow
组信息对应/etc/group,一行四个字段:组名、组密码(通常也是x)、GID、组成员列表。组成员列表用逗号分隔附加成员,主组成员不写在这里——因为/etc/passwd里第四字段那个GID已经标明了每个用户的主组。这也解释了为什么你明明把用户加进某个组,用id命令能看到,但/ect/group里不一定直接有那个名字,因为它在passwd里通过主组GID关联。
/etc/gshadow则类似shadow之于passwd,存放组的密码和组管理员信息。平时给普通组设密码的场景较少,但涉及建共享目录、临时授权某些用户访问一个组的资源时,组密码能充当一道门槛。多用户协作的组里,这个功能还是偶尔会用到的。
修改这些文件时的铁律:必须用vipw和vigr。直接vim编辑容易出格式错误,而且vipw会加锁防止多人同时改文件,改完还会提示你同步shadow。这类文件一旦字段错位,可能导致某个用户无法登录、甚至系统某些服务起不来。我在生产环境见过有人手滑改了passwd里UID,结果用户所有文件归属全部错乱——真是灾难现场。
4. 用户与组管理核心命令实操
4.1 创建用户的完整决策链
创建用户的命令在不同发行版有差异。基于Debian的系统(如Ubuntu)通常有adduser这个交互式包装脚本,它会自动创建家目录、设置密码、生成随机UID;而CentOS/RHEL/Fedora则常用useradd这个底层命令,参数更灵活但默认行为需要自己把握。这篇文章以useradd为主线,因为它的参数逻辑能帮你理解系统底层行为。
我推荐创建用户时养成带完整参数的习惯,不要裸敲useradd就完事:
useradd -m -d /home/zhangsan -s /bin/bash -c "Zhang San" -u 1005 -g zhangsan -G wheel,develop zhangsan逐个解释:
-m:创建家目录。如果不加,大部分发行版不会自动建家目录。-d:指定家目录路径,默认是/home/用户名,一般不用改,但特殊场景可指定到数据盘。-s:登录shell,默认可能是/bin/sh,建议显式指定为/bin/bash,否则交互体验很差。-c:注释信息,写个姓名或工号,方便后面查id时辨认。-u:手动指定UID。这个看场景,有些公司有统一UID规划,有些则让系统自动生成。如果你在同步多台服务器用户信息,UID手工统一会省很多跨机同步的麻烦。-g:主组,必须预先存在。-G:附加组,可以多个用逗号分隔。把用户加入wheel组(CentOS)或sudo组(Ubuntu),就赋予了sudo权限,这一步对常规运维人员来说几乎是刚需。
用户创建之后,紧接着要执行passwd zhangsan设置初始密码。这里有个实践技巧:临时给一个人开账号时,可以用echo '初始密码' | passwd --stdin zhangsan(CentOS支持),首次登录后强制改密。强制改密的做法是:
chage -d 0 zhangsan这样用户下次登录时必须先改密码,否则什么都干不了。这对临时账号、外包人员账号特别有用——既给了访问能力,又杜绝了初始密码被长期使用。
4.2 批量创建用户的省力方案
如果一个一个敲useradd太烦,还有批量方案。这是我在迁移一批账号时用的方法:先准备一个users.csv文件,每行一个用户信息,用for循环批量处理。
while IFS=',' read -r user uid comment; do useradd -m -s /bin/bash -c "$comment" -u "$uid" "$user" echo "初始密码123" | passwd --stdin "$user" chage -d 0 "$user" done < users.csv注意这不是“完全没有坑”的脚本,实际用的时候还要加上判断,比如用户是否已存在、张comment里是否有中文逗号等。但思路很重要:运维工作中凡是超过5个用户的需求,都值得用脚本而不是手工去敲。人手工操作一定会漏、会错,脚本反而可重复、可审计。
批量建组同理,groupadd一个循环就解决了。
4.3 用户修改:usermod是瑞士军刀
用户信息改了之后不需要删掉重建,usermod就是干这个的。常见用法:
usermod -s /bin/nologin zhangsan # 禁止登录 usermod -G docker,lfs zhangsan # 覆盖附加组(注意!这会替换掉原有附加组) usermod -aG develop zhangsan # 追加附加组最关键的教训就在-G上。很多人想给用户加一个新组,直接写-G docker,结果用户本来在wheel组里的sudo权限瞬间没了。因为-G不是追加,是替换。正确做法是每次追加都用-aG,或者先usermod -G列出当前所有组然后再加上新组重新赋值。我吃过这个亏:当时给一个同事加docker组,没加-a,把他的sudo权限带走了,他当场一脸懵。所以强制自己形成一个肌肉记忆:附加组修改永远用-aG,不要用-G。
锁定用户账号也有几个层级:
usermod -L或passwd -l:锁定密码,ssh密钥登录还能用(如果开了)。usermod -s /sbin/nologin:禁掉shell登录,但sftp这类依赖shell的也会被禁,纯ftp不受影响。chage -E 2026-12-31:设置账号过期时间,到期自动失效。
如果一个人离职了,我建议的处置顺序是:先chage -E 0或者usermod -L锁账号,把进程踢掉,等他确认没有需要交接的东西了再考虑userdel -r删用户和家目录。直接删用户容易把遗留在系统里的文件变成孤儿文件(属主显示成UID数字),后面清理很麻烦。
4.4 删除用户前必须自查的三件事
userdel -r username会删除用户并删除家目录和邮件池。但执行前一定确认三件事:
- 该用户有没有正在跑的进程?
pgrep -u username查一下,别把人家的定时任务或者web服务给杀了。 - 该用户有没有系统里其他重要文件?
find / -user username排查一下,有时候用户在自己的临时目录里放了数据库备份,人走了东西还在。 - 该用户是否属于某个共享资源的管理员?删掉会影响共享目录的属主显示和权限判断。
生产环境里删用户要像删表一样谨慎。宁可先锁账号观察一周,确认没有告警和反馈,再执行真正的删除。
5. sudo授权:给用户“合理范围”的root能力
5.1 为什么不能直接给所有人root密码
这是一个反复出现的讨论:既然有人需要装软件、改配置,为什么不直接告诉他root密码?
我见过一些创业团队确实这么干——root密码全组共用,一个人改了密码其他人全进不去。这种做法的根本问题是无法审计和无法收权。出了操作事故,你不知道是哪个人执行的;有人离职了,你得改一次root密码并通知所有人;更麻烦的是,如果有人把root密码写到某个脚本里且没有保护好,那相当于把服务器钥匙丢在了马路上。sudo机制就是用来解决这些问题的:按需给特定用户特定命令的执行权,所有sudo操作都会记入日志,权限还能随时收回,比共享root密码安全一个量级。
5.2 sudoers文件的最小实用配置
sudo的权限配置在/etc/sudoers里,强烈建议用visudo命令编辑——它会检查语法,语法错了会提示你而不会让你存盘退出。一旦sudoers格式损坏且没有被发现,sudo就完全不可用,届时只能用root单用户模式修复,很麻烦。visudo的语法保护能规避绝大多数这种事故。
先看最简单的配置逻辑。格式可以简化为三块:谁、在哪台机器、能执行什么命令。
zhangsan ALL=(ALL) /usr/bin/systemctl, /usr/bin/dnf这个配置允许zhangsan在任何主机上以任何用户身份执行systemctl和dnf命令。注意这里最好不要随便加ALL,越精确越安全。如果只是让用户重启某个nginx服务:
zhangsan ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginxNOPASSWD表示执行该命令时不需要再输密码,这对脚本化操作很有用;但一般不建议给太多NOPASSWD权限——每次sudo输密码,本质上是一次“你确定要执行吗”的确认。
如果你想让某个用户组整体具有sudo权限,配置就是:
%develop ALL=(ALL) ALL前面的%号表示后面是个组名。明文给ALL ALL是最高的sudo授权,等同于给root,只对完全信得过的人使用。生产环境里最安全的最小化做法是:先给一组精确的命令,观察用户的实际需求,再逐步放权。没人会嫌权限少,但权限给多了早晚会出事。
5.3 sudo日志审计的隐藏价值
sudo日志默认在/var/log/secure(CentOS)或/var/log/auth.log(Ubuntu)里。每次执行sudo都会记录:谁、在哪、执行了什么命令、几点几分。我排查过几次服务器异常,都是靠翻sudo历史找出问题命令的。这个能力是共享root密码完全没有的。
如果你在多台服务器统一管理sudoers,建议用配置管理工具下发,不要让运维人员每台机器手工登录visudo改。手工改的sudoers时间长了会产生漂移——两台机器配置不一样,出了问题都排查不出来。
6. 常见问题排查与避坑速查表
6.1 为什么用户突然sudo不了
这个排查看似复杂,实际上就几条线:
- 用户不在sudo组里。Ubuntu用户默认必须在sudo组内才有sudo权限,CentOS对应wheel组。
groups 用户名看组成员。 - sudoers文件语法错。这时候任何用户sudo都会报错,看/var/log/messages或直接root检查/etc/sudoers。
- 用户被设置成nologin或用
usermod -s改了shell,sudo过程也可能异常。 - 时间同步出问题,Kerberos(如果用了)认证失败。
我遇到过最隐蔽的一种:运维同事把用户同时放进了多个互斥的组策略里,sudoers规则冲突导致某些命令被拒绝。排查方式就是一条条grep sudoers里的规则,理清优先级。
6.2 为什么新用户登录后没有命令行提示符
刚创建的用户登录后显示$而不是常见的[user@host ~]$,或者光标前什么都没有。多半是家目录里的.bashrc没有被加载。原因通常是创建用户时没有指定shell——useradd默认可能给了/bin/sh,或者根本没有创建.bash_profile。
修复方案:
usermod -s /bin/bash 用户名然后把/etc/skel里的.bashrc复制到用户家目录(如果缺失的话)。实在不行就用userdel -r删掉重建,这也是为什么我强烈建议创建用户时把-s参数写明白。
6.3 用户无法删除自己目录下的文件
这个坑特别多。很多人以为“我是这个目录的属主,目录里的文件我想删就删”。真实规则是:能不能删除某个文件,看的是文件所在目录的写权限,而不是文件本身。如果你在一个归属别人、权限为r-x的目录里,哪怕你是里面某个文件的属主,也删不了它。
遇到这种问题,先ls -ld看目录权限。如果目录被设置了粘滞位(sticky bit,目录权限最后一位是t),情况更特殊:只有文件属主、目录属主或root能删除目录内的文件。/tmp目录就是典型例子——任何人都有写权限,但你不能删别人的临时文件。粘滞位的设计初衷就是这个。
在共享项目目录上设粘滞位是个很实用的经验:
chmod +t /data/project这样所有协作者都能自由上传、修改自己的文件,但谁也不能误删别人的文件。
6.4 误改UID/GID后文件权限错乱怎么救
如果你不小心用usermod -u改了用户UID,但用户原来的文件还显示成旧UID数字,就说明文件没有跟着“迁移”。解决办法是用find把旧UID的文件批量改属主:
find /home/zhangsan -user 1005 -exec chown zhangsan: {} \;注意统一使用文件名/用户名而不是裸UID,避免又搞混。这种事故也说明id映射一旦变动,文件归属就会断裂,所以生产环境改UID之前先全盘盘一下该用户有哪些文件。
6.5 常用排查命令速查
| 想查什么 | 命令 | 说明 |
|---|---|---|
| 用户全部信息 | id 用户名 | UID、GID、附加组一目了然 |
| 用户组列表 | groups 用户名 | 主组+附加组 |
| 用户是否存在 | getent passwd 用户名 | 兼容NIS/LDAP等远端用户源 |
| 所有用户列表 | awk -F: '{print $1}' /etc/passwd | 简单粗暴 |
| 谁在登录系统 | who/w | 当前在线用户 |
| 用户最近登录 | last 用户名 | 登录历史 |
| 查文件属主 | ls -l/stat 文件 | 看UID/GID |
| sudo授权清单 | sudo -l -U 用户名 | 查看某用户能执行哪些命令 |
| 系统里所有组 | getent group | 不比grep /etc/group弱 |
7. 一个测试环境下的用户组管理小实践
为了把上面的零散知识点串起来,我设计了一个小场景,你可以自己在虚拟机里复现一遍,会比只看命令记忆深刻得多:
现状:有一台测试服务器,上面跑着nginx和MySQL,有三个同事要上去协作:A负责后端代码,B负责前端资源和部署,C只负责看日志排查。要求是:
- 每个人有自己的账号,能登录,能用sudo重启nginx。
- 前端的静态资源目录/data/www,A和B能读写,C只能读。
- 谁都不能用root直接登录系统。
操作步骤可以这样走:
第一步,建组。建一个web组:
groupadd web第二步,建用户并加入相关附加组。
useradd -m -s /bin/bash -c "Developer A" -G web deva useradd -m -s /bin/bash -c "Developer B" -G web devb useradd -m -s /bin/bash -c "Ops C" -G web devc三个人的主组各不相同(各自私有组),附加组都是web。
第三步,设置目录权限。/data/www属于web组:
chown root:web /data/www chmod 2775 /data/www这里用的2775,2是setgid位——该目录下新建的文件自动继承web组,防止有人建的目录属组变成自己的私有组导致别人进不去。775赋予属主和web组rwx,其他人只能读和执行。这样A、B、C都能进入,但A和B要写,C没有w权限,所以写不了。完美满足“C只看日志不能动”的需求。
第四步,配置sudo。用visudo加一行:
%web ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx只允许web组里的用户重启nginx,且不需要密码。对于C这种只看日志的人,这权限也够用了——他不需要systemctl stop或者改配置。
第五步,禁止root远程登录。修改/etc/ssh/sshd_config:
PermitRootLogin no然后重启sshd。以后所有人必须用普通账号登录再sudo,行为全部留痕。
这个案例不复杂,但把用户、组、权限位、setgid、sudo、SSH安全加固全串起来了。你在自己环境里跑一遍,遇到任何报错都按上面章节里的排查思路去看,基本就是一次完整的学习闭环。
8. 我踩过的一些坑和实际体会
最后聊聊几个真实的教训。
第一个是不要在用户创建时省略参数。我早期图省事,useradd zhangsan然后直接让他登录,结果他登录进来没有家目录,shell还是sh,连tab补全都没有。到后面连他自己都烦了,还是得回退重建。一次图省事,后面全是债。
第二个是GID冲突后患无穷。有一次我用groupadd建组,当时没注意GID已经被某个系统进程占用了,后面把该组添加为某目录属组时,目录权限莫名其妙地跟一个系统服务共享了。后来查出来是GID冲突,最后只能把所有相关目录重新chown一遍。所以批量建组的时候我会刻意groupadd -g指定一个自定义范围的GID段(比如10000以上),避免和系统自动分配的GID撞车。
第三个是批量脚本一定要有幂等性。后来我写批量创建用户的脚本,在循环体里先检查用户是否已存在:
if id "$user" &>/dev/null; then echo "$user already exists" continue fi这个判断帮我挡住了很多次重复执行导致的问题。凡是自动化操作,默认都要考虑“如果已经存在怎么办”,不要指望现实环境跟第一次跑的时候一模一样。
第四个也是很多初学容易忽略的:改了sudoers必须主动开一个新终端验证。不要改完就关掉当前终端,否则万一语法有问题、sudo全挂了,你还得想办法恢复。我的习惯是:备份一份sudoers,改完先visudo -c检查语法,然后另开一个终端测试sudo是否正常,确认没问题才退出当前编辑会话。
用户与组管理的知识树大概就到这。这些东西不算高深,但覆盖面广,而且每个细节都可能在生产环境里变成事故点。真正提升水平的路径就是多在实际的服务器上折腾,越早把自己的账号体系、权限模型梳理清楚,后面不管是部署应用还是排查安全问题,都会顺手很多。