我们团队之前有台内部开发服务器,账号体系一直比较随意。新来的实习同事要部署一个服务,怕他没有权限,某老员工直接把 root 密码给了他。结果有一次做清理,实习同事一个rm -rf把备份目录整个干掉了,关键数据没留下一份。那天晚上大家都在想办法恢复数据,心里都在骂为什么当初不做用户与组管理。
后来痛定思痛,把整个账号体系重新梳理了一遍,才真正把用户、组、权限这三件事的关系摸透。现在回头看,用户与组管理看似是 Linux 里最基础的日常操作,但里面隐藏的细节和坑,足够让一个运维从入门到崩溃。这篇文章就围绕用户与组管理这个主题,把我实际踩过的坑、验证过的思路、以及一套可以直接抄作业的操作流程完整写出来。适合刚入门 Linux 的开发者,也适合被权限问题折磨过几次的运维朋友。
1. 从一次权限混乱事故看用户与组管理的底层逻辑
1.1 事故现场复现:共享 root 账号有多可怕
先说回那次事故。问题表面上出在rm -rf,但根子在于所有人的操作都顶着 root 身份,系统根本分辨不清是谁干了什么。服务器上跑着好几个项目,之前为了省事全部用 root 启动,日志、临时文件、备份脚本全都揉在一起。文件属主全是 root,普通账号上去看什么都动不了,那大家自然更倾向继续用 root。实际上这是典型的授权粒度失控:所有人拥有全部权限,等于所有人对一切操作都不负责。
后来我重新建账号时,给每位同事单独建了用户,又按项目组划分了组。项目组的成员只能读写自己组目录下的文件,部署脚本统一用部署账号执行,root 密码只放在特定的人手里。整理完再回头看那次事故,彻底明白了用户与组管理的核心价值:它不是让你多敲几条命令,而是给系统建立一套清晰的“身份边界”和“权限边界”,让每个操作可追溯、可限制、可回收。
1.2 用户、组、权限三者之间的关系拆解
在 Linux 里,用户就是账号,组就是用户的集合,权限就是文件对三类主体(属主、属组、其他人)的访问规则。这三者平时大家都能说出个大概,但具体到系统层面要分清楚:
- 用户的标识是 UID,组的标识是 GID,系统内核认的是数字而不是用户名。你
ls -l看到的是用户名,是因为系统把 UID 映射成了名字,Uid mapping 丢了就会显示一串数字。 - 每个用户至少有一个主组(primary group),创建文件时文件的属组默认继承主组。用户可以同时加入多个附加组(supplementary groups),用来获得额外目录的访问权。
- 组是权限分配的“中间层”。假设目录
/data/project属于组project,那么所有加入project组的用户都能访问,不用逐个用户去设置权限。这就是组存在的意义:批量授权、统一管理。
这套逻辑就像公司门禁:用户是工卡,组是部门,权限是门禁规则。给一个人发卡就是建用户,把人拉进某个部门就是加入组,门禁系统决定你能不能进某间办公室。如果所有人共用一张万能卡(共享 root),门禁就成了摆设。
2. 用户账号生命周期管理实操:从创建到注销的完整链路
2.1 用 useradd 正确创建账号,参数逐个讲清
创建用户最常用的命令是useradd,但直接执行useradd zhangsan会暴露一堆暗坑。默认情况下它不一定创建家目录,不同发行版表现还不一样。我强烈建议创建账号时把参数写明确,别依赖默认值。
useradd -m -d /home/zhangsan -s /bin/bash -c "张三" -u 1050 -G dev,ops zhangsan这条命令干了四件事:-m创建家目录,-d指定家目录路径,-s指定登录 shell,-c写入注释(一般是姓名或用途),-u指定 UID,-G把用户加入 dev、ops 两个附加组。为什么 UID 要手动指定?因为不同服务器上同一个人的 UID 如果不一样,以后做文件同步或备份,属主会错乱。实际管理里,我给每类用户规划好 UID 段,比如普通员工 1000-1999,运维 2000-2999,服务账号 3000-3999,这样一眼就能从 UID 判断账号类型。
如果是批量创建,我一般把参数模板写进循环脚本。一个真实的例子:某次新项目上线要一次性给 20 个外包同事开账号,我就用一个for循环读用户列表文件,逐行执行useradd,然后把初始密码写入并提示首次登录修改。
while read user; do useradd -m -s /bin/bash -G project "$user" echo "$user:Temp@2025" | chpasswd chage -d 0 "$user" done < user_list.txtchage -d 0这行很重要,它强制用户下次登录时必须改密码。不然初始密码能一直用,就成安全隐患了。
2.2 密码策略与账号过期:passwd 和 chage 的配合
账号建好只是第一步,密码策略跟不上等于白建。passwd命令大家都会用,但chage常常被忽略。chage控制的是密码生命周期,几个关键参数:
-M密码最大有效天数,超过就强制改;-m两次修改密码的最小间隔天数;-W过期前多少天开始警告;-I密码过期后多少天锁定账号;-E账号失效日期,格式如2025-12-31。
我建议对普通员工执行chage -M 90 -m 7 -W 14 zhangsan,意思是密码每 90 天必须换,两次换密码间隔至少 7 天,到期前 14 天开始提醒。这样避免用户改完密码立刻又改回原样。
密码信息存在/etc/shadow里,平时不要手动改这个文件,它每行对应一个用户,字段用冒号分隔:用户名、加密密码、最近修改日期、最小天数、最大天数、警告天数、宽限天数、失效日期。日期字段是从 1970-01-01 开始算的天数。偶尔排查账号问题时可以awk -F: '{print $1, $3, $4, $5}' /etc/shadow看一眼,但记住只能看,最好别直接编辑。
2.3 修改与删除:usermod 和 userdel 的注意事项
账号要调整时用usermod。常用场景:
usermod -L zhangsan # 锁定账号,禁止登录 usermod -U zhangsan # 解锁账号 usermod -l lisi zhangsan # 改登录名 usermod -g staff zhangsan # 修改主组 usermod -G dev,ops zhangsan # 用逗号分隔的附加组列表覆盖原来的附加组 usermod -aG docker zhangsan # -a 追加附加组,不覆盖原有组-a参数特别容易漏。不加-a直接-G会把用户原有的附加组整个清空,然后只保留你新写的组。某次我改一位同事的附加组时忘了加-a,结果他从项目组里被悄悄移了出去,第二天访问项目目录报权限错误,排查半天才找到原因。
删除账号同样不能无脑操作:
userdel -r zhangsan-r会同时删除家目录和邮件池。但删除前我建议先查一下这个用户还有哪些文件:
find / -user zhangsan 2>/dev/null如果查出来有重要文件属于他,先chown -R转到其他用户或组,再执行删除。直接userdel -r虽然把人删了,但服务器上还可能残留一堆属主为已删除用户 ID 的文件,将来看到一串数字属主,想查是谁的都没法查。
3. 组管理的核心玩法:主组、附加组与权限协同
3.1 组的创建与常见误区
组管理没有用户管理那么复杂,但坑也不少。创建组命令:
groupadd project groupadd -g 4100 deploy-g指定 GID,和用户一样,建议做好规划避免混乱。改组名用groupmod -n new_name old_name,删组用groupdel group_name。但要注意:如果一个组是某个用户的主组,groupdel会直接报错,提示无法删除。必须先把用户移出该组或改掉主组,才能删掉。这个限制其实是保护机制,避免删掉组之后用户的主组 GID 变成无效值。
曾经我遇到过一个情况,某服务账号的主组和附加组里同时存在一个组,后来在组里加入了很多权限规则。有同事执行groupdel失败后,又尝试直接改/etc/group文件把那一行删了,结果该服务账号的所有定时任务瞬间全部失效,因为脚本里chgrp和chmod操作都依赖这个组的 GID。最后我恢复备份才解决。从此定下规矩:组信息变更必须走命令,禁止直接编辑/etc/group。
3.2 主组与附加组的区别,以及切换技巧
主组和附加组的区别,不只是数量上的差异。主组决定用户新建文件时文件的默认属组,附加组决定用户能访问哪些额外组权限。可以这样理解:主组是“出身”,附加组是“人脉”。
id zhangsan这条命令输出里gid=1050(project)是主组,groups=1050(project),2000(dev)是附加组。用户在终端里实际能访问的权限是所有组叠加的结果,但新建文件的属组只会是主组。
临时需要切换主组时可以用newgrp:
newgrp dev这个命令会启动一个登录 shell,把当前会话的有效组切换成指定组。前提是用户本身属于该组。退出切换后的 shell 可以用exit回到原来的身份。初学阶段很多朋友看不懂newgrp的输出,其实它就是新建了子 shell,环境变量$GRP变了,文件默认属组也跟着变。实际运维场景里我很少用newgrp,因为它容易让后续操作产生误判,我更喜欢在脚本里显式用chgrp去设置目标文件属组,这样每一步都清晰可控。
3.3 查看组成员:别再用 grep 硬翻文件
查一个组里有哪些成员,很多新手第一反应是grep project /etc/group。这样做虽然能出结果,但/etc/group的第四列只显示把该组作为附加组的用户,主组成员不会出现在这一列里。正确的做法是用getent group project,它会从系统数据库里读取完整记录。
getent group project # 输出格式: project:x:4100:zhangsan,lisi带主组用户的完整成员名单,要结合getent passwd一起看:
awk -F: '$4==4100{print $1}' /etc/passwd$4是 GID 字段,等于 4100 的用户都是主组成员。我发现很多人只盯着/etc/group看,结果漏掉了主组用户,后来对权限始终想不明白:明明用户在这个组里,为什么getent group看不到他。
4. 特殊权限与 sudo 提权:避免所有账号变成 root
4.1 setuid、setgid 与粘滞位:三个容易被忽略的权限位
普通权限位rwx之外,Linux 还有三个特殊权限位。我挑了实际工作中最容易被忽略的两个场景来讲。
第一个是setuid(SUID)。当一个可执行文件设置了 SUID 权限,用户执行它时,进程以文件属主的身份运行。最典型的是/usr/bin/passwd:
ls -l /usr/bin/passwd # 输出: -rwsr-xr-x 1 root root ...普通用户修改密码时需要写/etc/shadow,但 shadow 文件只有 root 能写。解决方式就是让passwd程序以 root 身份运行一小段时间,完成密码写入后再退回普通用户。这是设计好的安全机制,但如果第三方程序被配置了 SUID 且属主是 root,就等同于给所有用户发了一张 root 临时通行证,风险极大。我检查系统时会定期找 SUID 文件:
find / -perm -4000 -type f 2>/dev/null第二个是setgid(SGID)对目录的作用。给目录设置 SGID 后,任何用户在目录里新建文件,文件的属组自动变成目录的属组,而不是用户自己的主组。这在共享目录场景下非常好用。比如/data/share属组是project,执行:
chmod g+s /data/share之后 project 组的成员在目录里创建的文件,属组一律是project,同伴之间可以相互读写,不用每次手动chgrp。某公司内部文档库用的就是这个方案,实测下来文件属组从不混乱。
粘滞位(Sticky Bit)最典型的例子是/tmp。目录权限列表里最后一位t表示只有文件属主(或 root)能删自己的文件,其他人即使有目录写权限也不能乱删。设置命令是chmod +t /tmp/share。
这三个特殊权限位建议谨慎使用,能用普通权限解决的问题就不要引入它们,因为排查问题的时候它们往往是最隐蔽的一环。
4.2 sudo 与 sudoers:让组决定谁能提权
sudo 是把用户和组接入提权规则的核心桥梁。/etc/sudoers的配置语法初看很绕,但核心就是一行一种授权规则。常用写法:
# 允许用户 zhangsan 执行任何命令 zhangsan ALL=(ALL:ALL) ALL # 允许 dev 组成员执行任何命令 %dev ALL=(ALL:ALL) ALL # 允许 ops 组成员以 root 身份免密执行特定命令 %ops ALL=(ALL:ALL) NOPASSWD: /usr/bin/systemctl, /usr/bin/journalctl # 允许开发组成员以 www-data 用户身份执行命令 %dev ALL=(www-data:www-data) /usr/bin/git第二列的ALL表示主机名范围,第四列NOPASSWD:表示免密执行。实际业务里我很少给整个组 ALL 权限,更常见的做法是只开放必要命令。比如运维组只需要管理服务,那就只给 systemctl 相关权限;开发组只需要看日志,只给 journalctl 权限。最小授权原则在 sudoers 里同样成立。
需要提醒的是,sudoers 文件的编辑必须用visudo,不要直接vim /etc/sudoers。visudo会在保存前做语法检查,如果语法错误它会阻止保存并提示,有效防止配置文件写错导致所有用户无法 sudo。我自己因为自信直接编辑过,后果就是写错一行,所有 sudo 命令全部失效,只能到终端上重新修文件。这个坑在下一章详细说。
4.3 实战场景:用专用账号跑服务,权限最小化
给服务单独建账号是用户与组管理里最值得养成的好习惯。某次部署一个 Web 应用,我没有用 root 去跑进程,而是建了专用账号:
useradd -r -s /usr/sbin/nologin webapp-r创建系统账号,UID 在系统范围内;-s /usr/sbin/nologin指定不可登录 shell,这样账号只能被服务引用,不能被人登录。然后在 systemd 服务文件里指定运行身份:
[Service] User=webapp Group=webapp ExecStart=/opt/webapp/run.sh再把/opt/webapp目录属主改成 webapp,日志目录单独授权。这样即使 Web 应用被人拿到 shell 权限,它的系统权限也只是普通账号级别,没法关系统服务,更没法动别的项目目录。为服务建专用账号带来的安全收益非常大,而且 cost 几乎为零,任何一个项目上线都该这样操作。
5. 我踩过的用户管理坑:完整排查链路与修复方案
5.1 坑一:误改 UID 导致文件属主全部变数字
有段时间我负责整理服务器账号,觉得某个用户的 UID 编号不规整,直接用usermod -u 3000 zhangsan改了他的 UID。结果第二天同事反映,很多文件属主显示成了一串数字“1050”。原因很简单:UID 是内核识别身份的唯一标记,改 UID 只改了账号文件里的映射关系,已经存在磁盘上的文件记录中存的还是旧 UID 数字。系统找不到对应的用户名,就不做映射显示,直接露出现实数字。
排查思路是这样的:先确认改 UID 的时间点和范围,然后用find / -uid 1050找出所有影响文件,再统一chown -R 3000转移。但如果文件分散在很多位置,一个一个查太累。我当时写了一条命令:
find / -uid 1050 -exec chown 3000 {} \;/全盘扫描加上逐文件 chown,耗时比较久,但能一次性修复。这里要强调一个教训:改 UID 前先find / -user zhangsan看看涉及哪些文件,评估影响面再动手。如果只是想清理账号,不一定非要改 UID,保持现状更稳妥。
5.2 坑二:直接编辑 /etc/passwd 导致语法错误
某次我图方便直接用文本编辑器改/etc/passwd,想把某用户的默认 shell 换成/bin/bash,不小心把一个冒号删掉了。保存退出后,再开终端登录,系统提示认证失败,甚至有服务直接起不来。这是因为系统的账号解析依赖/etc/passwd的格式,每行严格由 7 个冒号分隔的字段组成,少一个冒号整行解析就失败,登录和提权都受影响。
正确做法是用vipw编辑,它会加文件锁并且做基本格式校验。如果已经手贱改坏了,也不要慌,在单用户模式下用原始备份恢复,或者从能读文件的途径把错误的行修复回来。我当时的临时办法是用pwck检查并定位问题行,再借助另一台同系统的正常/etc/passwd对照修复。硬件期总共花了半小时,完全是可避免的。现在所有账号相关文件我都只通过命令改,除非明确知道自己在做什么。
5.3 坑三:sudoers 写错导致 sudo 完全失效
sudoers 编辑事故是运维圈里的经典翻车。之前为了给某组加一条 git 命令白名单,我直接打开/etc/sudoers追加了一行:
%dev ALL=(ALL) /usr/bin/git看起来没问题,但因为我用的是普通文本编辑器,对格式检查不严,某一行别处可能多了一个 tab 键或者少了空格,结果所有用户的 sudo 都提示语法错误。包括 root 在内的 sudo 操作全部卡住,而且你不一定记得 root 密码,因为平时都在用 sudo 而不是su -。此时系统上的 sudo 相当于被锁死了。
修复链路:如果有另一台同网段的机器能 ssh 过来,用 root 身份重新执行visudo -c找到错误行,或者直接把错误行注释掉。如果完全进不了,需要重启进入单用户模式,重新挂载根文件系统为可写,再手动修改文件。那次之后我学乖了,凡是要改 sudoers,必先备份一份到/etc/sudoers.d/下的独立文件,再用visudo -f编辑。分文件管理的好处是出错只出在子文件,主配置仍然可读,恢复成本低很多。
5.4 UID 0 与系统账号保护:为什么 root 只能有一个
系统里 UID 0 代表超级用户权限。有些人图省事,会把某个应用账号的 UID 改成 0,让应用天然拥有 root 权限。这种做法非常危险,一旦应用被远程攻击拿下,攻击者直接就拿到了整个系统的控制权。即使应用不暴露外网,内网环境中 UID 0 账号也意味着它能在服务器上做任何事。
我检查服务器账号安全时,必查两条命令:
awk -F: '$3==0{print $1}' /etc/passwd awk -F: '$3<1000 && $3!=0{print $1, $3}' /etc/passwd第一条找 UID 0 账号,正常情况下应该只有 root 一个;第二条找系统账号,逐个确认它们的登录 shell 是否为/usr/sbin/nologin。系统账号本来是给服务用的,不能允许密码登录,否则等于留了后门。
6. 多主机环境下的用户管理进阶:从单机命令到批量协同
6.1 为什么服务器一多,单机管理就撑不住了
当你管理的服务器达到几十台的时候,每台机器都单独去useradd是不现实的。新员工入职要建账号,如果一台一台敲,既慢又容易漏。更麻烦的是权限策略难保持一致:A 服务器开发组有某条 sudo 权限,B 服务器忘了加,同事换台机器就发现命令执行不了。
某公司环境里就发生过一次闹剧:开发组在代码发布机器上有免密 sudo 执行某部署脚本的权限,但测试服务器上配置漏了,上线当天测试环境的部署失败,大家现场排查了半天,最后发现是 sudoers 配置不一致——两台机器上同名用户的权限居然不一样。这种问题靠手工管理必然出错,因为人脑没办法在几十个节点上平铺维护同一套配置。
6.2 集中化管理的常见方案与选择逻辑
解决多主机用户管理问题,通用的思路是把用户身份集中到统一的认证源里。常见方案包括轻量目录访问协议(LDAP)、基于 LDAP 的各类实现、以及配合系统安全服务的 SSSD 做认证缓存。大致原理是这样的:各服务器不再本地创建用户,而是配置为连接统一的目录服务;用户在一处创建就能登录所有接入的服务器;服务器本地只保留访问目录服务的配置和缓存。
实际落地时需要考虑网络稳定性与可用性。目录服务如果挂了,依赖它的服务器还能不能登录?通常通过 SSSD 缓存机制来缓解,用户在成功认证一次之后,即使目录服务暂时不可达,也能根据缓存继续认证一段时间。这个设计很像打工人门禁卡:门禁系统偶尔断电,但已经登记过的人可以靠离线记录进出。
不过集中化方案对中小团队来说可能有点重。我的建议是:服务器在十台以内,把用户创建和 sudoers 配置写成脚本,纳入版本管理,配合批量执行工具下发,足够用;超过这个规模再考虑目录服务。脚本方案虽然原始,但好处是透明、可控、不需要额外维护一套基础设施,适合没有专职身份管理团队的场景。
6.3 可复用的批量用户管理脚本模板
分享一下我一直在用的批量管理脚本思路。核心文件有四个:用户列表、组权限定义、sudo 规则、Shell 模板。用户列表用 CSV 格式管理:
name,uid,primary_group,extra_groups,shell,comment zhangsan,1101,project,dev,ops,/bin/bash,张三 lisi,1102,project,dev,/bin/bash,李四脚本读取 CSV 后逐行执行useradd、usermod和密码策略设置,并把 sudoers 规则写入/etc/sudoers.d/下的独立文件。这样做的好处是新增账号时只需要编辑 CSV,不用每台机器敲命令;出问题时查看 CSV 和子文件就能回溯原来的配置意图。
awk -F, 'NR>1{ system("useradd -m -u " $2 " -g " $3 " -G " $4 " -s " $5 " -c \"" $7 "\" " $1) system("chage -M 90 -m 7 -W 14 " $1) }END{}' user_list.csv批量下发的时候配合批量执行工具,把脚本推到多台目标机器再执行就行。但要记住,脚本执行前先在测试环境跑一遍,避免因为变量里的特殊字符产生意外行为。我试过在 CSV 注释里写中文逗号,结果某些环境下处理错了,把所有用户都建到了错误的组里。从那以后 CSV 一律用英文逗号分隔,注释字段单独放 ASCII 文本。
7. 写给新手的三个建议
最后分享三条基于实际项目经验的小建议,不是官方文档上会写的那种。
第一,给用户和组的 UID、GID 做编号规划。很多新手觉得系统自动分配就行,但等到跨服务器同步文件、备份归档时,发现同一批人的 UID 在各台机器上不一样,属主就乱套了。宁可一开始多花十分钟规划 UID 段,也别等出问题了再返工。
第二,禁止直接修改/etc/passwd、/etc/shadow、/etc/group。所有账号与组信息的变更都尽量通过useradd、usermod、groupadd等命令完成。这些命令背后有完整的一致性检查和配套文件更新逻辑,手工编辑很容易漏掉某个关联文件,造成账号状态不一致。
第三,每季度做一次账号与权限审计。检查有没有长期未登录的账号、UID 0 账号是否唯一、各服务器 sudoers 配置是否一致、system 账号是否还开放了登录 shell。审计不要凭记忆,写个脚本输出报表对照看,这样用户与组管理的整体状态都在掌握之中。