干运维这行,整天和用户、组打交道,一开始觉得不就是几条命令嘛,直到某天线上服务报权限错误、某位同事说文件明明在却进不去目录、安全审计问有哪些僵尸账号,你才会意识到“用户与组管理”这几个字背后藏了多少细节。它不是简单的useradd、groupadd,而是整个系统权限体系的基石,是安全基线和审计入口。这篇文章把我日常在Linux服务器上关于用户与组管理的实操经验完整记录下来,新手可以直接照着敲命令,老手也可以对照检查自己的管理习惯有没有埋坑。
1. 先把底层逻辑盘清楚:用户、组与权限的铁三角
1.1 用户到底是什么:账号、UID 与家目录的关系
很多人以为用户就是一个用户名,实际上Linux内核根本不认识用户名,它只认UID(用户ID)。用户名是给人看的标签,UID才是系统真正用来识别身份的编号。这个理解不到位,后面很多坑都避不开。
举个例子:某天你发现服务器上有一堆文件属主显示的是数字,比如1002,但你去找/etc/passwd,里面根本没有uid=1002的账号。这是因为创建这些文件的用户已经被删掉了,但文件还在,系统没法把UID解析成一个名字,只能显示数字。这时候你要么用find / -uid 1002去逐个查找这些文件,要么就把这个UID重新分配给一个新用户,让文件归属恢复正常。我见过不少同事遇到这种情况一脸茫然,其实就是没想明白UID才是本质。
再往下看,一个用户通常还对应家目录、登录Shell、密码过期策略等一堆属性。这些属性分散在几个核心文件里,分别是/etc/passwd(账号基本信息)、/etc/shadow(密码密文与策略)、/etc/group(组信息)、/etc/gshadow(组密码)。root的UID固定是0,普通用户一般从1000开始分配,1到999是系统服务账号用的,给那些不登录、只跑服务的程序使用。
这里有个操作原则:尽量避免直接vim编辑这些文件。系统提供了vipw和vigr命令,它们会帮你加锁、做基本校验,避免多个管理员同时修改导致文件损坏。血的教训:以前有人直接编辑/etc/group,手滑删了一个冒号,整个系统用户组信息解析失败,所有依赖组校验的服务全部异常。
1.2 组的本质:为什么一个用户可以同时属于多个组
组这个概念,简单理解就是用户的集合。文件权限有三层:属主(owner)、属组(group)、其他用户(other),中间这个“属组”就是靠组来实现的。
为什么要把用户放进组里而不是单独授权?因为组是权限复用的最小单元。你自己想:一台服务器上有20个做Java开发的同事,他们要共同读写/data/project,你总不能给20个账号挨个设ACL吧。正确做法是建一个dev组,把20个人加进去,然后把这个目录的属组改成dev,给组授予读写权限,一次搞定,后续新同事入职只需要usermod -aG dev一次。
这里还要分清主组和附加组。每个用户必须有一个主组,通常是创建用户时以同名组作为主组;同时用户可以加入多个附加组,从而获得其他资源的访问权。日常操作中要注意:usermod -g是改主组,usermod -aG是追加附加组。很多人写脚本时漏了-a,直接把用户的所有附加组覆盖掉了,用户瞬间失去一堆权限,排查半天。
主组、附加组和文件权限的关系,打个比方:主组像你的“人事档案归属”,附加组像你手里的“门禁卡”。你进哪个办公室,看的是门禁卡(附加组),而不是档案归属。所以你给文件授权时,要想清楚是把用户加到文件的属组里,还是把文件的属组改成用户的某个组。
1.3 密码与影子文件:排查问题的第一步
用户管理最绕不开的就是密码相关文件。/etc/passwd里每个字段依次是:用户名、密码占位符(x)、UID、GID、注释信息、家目录、登录Shell。密码真正的密文存在/etc/shadow里,普通用户读不了,只有root能看。
/etc/shadow里的字段很重要,包含密码加密串、最近修改时间、密码最少使用天数、最长使用天数、过期警告天数、宽限期、账号失效时间等。我排查问题的时候,经常先去看shadow文件,能发现很多“疑似问题”其实是密码策略导致的。比如某个账号用户一直说登不上去,日志里提示密码过期,一查shadow里第三列最后修改时间加最大天数已经超了。
组信息在/etc/group里,格式是组名:组密码占位符:GID:组成员列表。/etc/gshadow则是组密码的管理文件,日常用得不多,但如果你要做一个共享组,并允许组外用户通过newgrp临时进入,就会涉及组密码。这里提醒一句:不要手动改这些文件,即使你觉得自己很小心,也容易改错权限。改之前先备份,改完用getent passwd、getent group验证一下再退出。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 用户管理实操要点:从创建到回收
2.1 useradd 的正确姿势:参数背后都是规范
创建用户是最高频的操作,但很多人只执行useradd username,后面一堆问题。
不同发行版默认行为不一样。有的默认创建家目录,有的默认不创建。所以最稳妥的写法是显式把所有关键参数写清楚:
bash复制useradd -m -d /home/zhang -s /bin/bash -G devops,www -e 2025-12-31 zhang
逐个解释一下参数的含义:
- -m:强制创建家目录。这个参数在有些系统上是默认的,有些不是,显式加上最保险。
- -d:指定家目录路径。如果省略,默认是/home/用户名。
- -s:指定登录Shell。/bin/bash是交互式Shell,/sbin/nologin则表示不允许登录,适合服务账号。
- -G:指定附加组,多个组用逗号分隔,不要有空格。
- -e:设置账号过期时间,格式YYYY-MM-DD。项目合作方账户、临时外包账户尤其建议加上,让账号到期自动失效,省得过后忘了清理。
再补充一个容易被忽略的点:新建用户家目录里的初始文件,是从/etc/skel这个模板目录复制的。如果你想给所有新用户自定义一份默认配置,比如设置好.bashrc的别名、放一个欢迎文件,只要往/etc/skel里放就行。新用户的家目录会自动继承这些文件。
创建完用户后,别忘了给他设置密码或者锁定期限。空密码账户默认是不能远程登录的,不过本地登录也容易出问题,谨慎处理。
2.2 密码策略:别让弱口令毁掉整个权限体系
用户创建出来,第一件事就是设置密码。手动设置用passwd命令,交互式输两次;脚本里批量设置用chpasswd,直接管道传值:
bash复制echo "zhang:Passwd@2024" | chpasswd
不过我更推荐一种做法:初始密码设置后马上标记为过期,让用户第一次登录时强制改密。命令是:
bash复制passwd -e zhang
这样管理员不需要知道用户后续设置的密码,用户密码只有他自己知道,安全性和合规性都好很多。很多企业级服务器的初始密码流程就是这么干的。
密码策略的全局设置在/etc/login.defs文件里,几个关键参数:
| 参数 | 作用 | 建议值 |
|---|---|---|
| PASS_MAX_DAYS | 密码最大使用天数 | 90 |
| PASS_MIN_DAYS | 密码最少使用天数,防止频繁改密绕过历史 | 7 |
| PASS_WARN_AGE | 密码过期前多少天提醒 | 14 |
| UID_MIN / UID_MAX | 普通用户UID分配范围 | 1000 / 60000 |
| CREATE_HOME | 是否默认创建家目录 | yes(按企业规范定) |
这个文件改完后对已有账号不生效,已有账号需要单独用chage调整:
bash复制chage -m 7 -M 90 -W 14 zhang
2.3 账号生命周期:锁定、禁用、删除的正确顺序
账号管理最容易出问题的不是创建,而是回收。某同事离职了,你怎么办?直接userdel -r?我建议先锁后删。
先用usermod -L锁定账号,让任何人都无法登录;再顺手把他加入的附加组记录下来,方便后续审查;然后观察一段时间确认没有影响线上的任务或定时作业后,再执行userdel -r删除。
bash复制usermod -L zhang
usermod -e 2023-01-01 zhang
usermod -L是锁定密码,让登录失效;usermod -e是把账号设置成过期,两者叠加效果更好。这种“冷处理”的好处是:如果离职人员账号还在跑某个线上定时备份,你还能及时发现;如果直接删除,相关文件和任务就会变成失控状态。
真正要执行删除时,注意userdel -r会连家目录和邮件目录一起删掉。如果你不确定里面的数据是否还需要,可以先不删家目录,只删除账号本身,后续把家目录打包备份。
批量清理长期不登录账号也有思路可以抄:结合/var/log/secure或atime排序找出长期未活动的账号,先锁定后通知,确认不用了再删除。
3. 组管理:权限分配的精细化操作
3.1 groupadd 到 groupdel:组操作的基本边界
组操作看起来简单,但有几个边界要清楚。
bash复制groupadd -g 10001 devops
-g指定GID。为什么要手动指定?因为默认自动分配的GID在不同服务器上可能不一致。如果你有多台服务器,希望同一个组的GID保持一致(这对NFS共享、日志分析、权限比对都有好处),就需要规划GID范围并手动指定。
groupmod可以修改组名或GID,但要慎重。改GID会导致该组在文件系统里已有的属组变成数字GID(如果新GID与旧GID不一致),影响范围可能很大。我建议GID创建后就不要改动,除非能确认所有相关文件的属组都同步调整。
向组里添加成员有两种方式:gpasswd -a user group,或者usermod -aG group user。gpasswd更适合指定某个人来管理某个组的成员,比如把组管理员角色授权出去:
bash复制gpasswd -A zhang devops
这是把devops组的管理权限交给zhang,让他可以通过gpasswd来添加移除成员,不需要root权限。对大规模协作团队来说,这能减轻管理员的工作量。
查看组成员用getent命令最方便:
bash复制getent group devops
它会从所有配置的数据库中获取组信息,显示结果比单纯cat /etc/group更可靠,因为可能还有LDAP或其它数据源。
3.2 setgid 目录继承:多用户共享目录的灵魂
组管理里最容易出问题的是“新创建的文件属于哪个组”。默认情况下,用户创建的文件属组是用户的主组,而不是当前目录的属组。这在共享目录里特别坑:一个目录属于devops组,所有成员都有权限写入,但某个成员在目录里新建的文件却属于他的个人组,其他成员看不了。
解决办法就是给共享目录设置setgid位:
bash复制chown root:devops /srv/project
chmod 2770 /srv/project
数字权限的2770里,2就是setgid位。这表示在这个目录下新建的文件和子目录,属组会自动继承目录的属组(devops),而不是用户主组。这是多用户开发服务器最核心的一招。
与之类似的还有sticky bit,通常用在/tmp这种全局可写的共享目录。sticky bit(数字权限里的1xxx)限制只有文件属主、目录属主或root才能删除文件。比如/tmp目录权限是1777,任何用户都可以在/tmp里创建文件,但不能删除别人的文件。一个目录上可以同时设置setgid和sticky bit,比如3770,既能让共享目录里的文件组继承,又能防止成员误删别人的文件。
3.3 newgrp 与有效组:临时切换组的正确姿势
前面说过,用户通过附加组可以获得额外权限,但一个文件被创建出来时,它的属组是根据“当前有效组”来定的。默认情况下,有效组是主组,不会自动变成某个附加组。
当用户需要让新建文件属于某个附加组时,可以用newgrp临时切换有效组:
bash复制newgrp devops
执行后会开启一个子Shell,这个子Shell里的有效组就变成了devops,新创建的文件自动属于devops组。退出这个Shell就回到原来的有效组。
还有一个非交互式版本sg命令,适合在脚本里用:
bash复制sg devops -c "touch /srv/project/test.txt"
这段命令以devops组身份执行touch,创建出来的文件属组就是devops。日常用到这个命令的场景不多,但在某些需要严格按照组身份运行的脚本或定时任务里,它就是关键先生。
4. 常见场景实战:直接抄作业的三套方案
4.1 场景一:给开发团队建一个共享项目目录
这是我自己在多个项目里反复使用的一套流程,按顺序执行即可:
bash复制# 1. 创建共享组
groupadd -g 10086 devops
# 2. 把团队成员加进组(注意一定带 -a)
usermod -aG devops zhang
usermod -aG devops li
usermod -aG devops wang
# 3. 创建目录,设置属主和属组
mkdir -p /srv/project
chown root:devops /srv/project
# 4. 设置权限:属主读写执行,组读写执行,其他人无权
chmod 2770 /srv/project
这里权限用了2770,也就是setgid + rwxrwx---。为什么不用2775?因为2775意味着其他用户(不在devops组里的人)可以读取和执行目录里的文件,这对项目源代码或内部数据来说权限过宽。权限最小化是安全的基本原则,宁可先收紧,真的有人需要再单独授权。
此外还要注意umask。即使目录是2770,如果用户的umask是022,新建文件的默认权限是644,组没有写权限。共享目录场景建议把umask设为007,让新建文件默认就是rwxrwx---。可以要求团队成员在.bashrc里加入umask 007,或者针对共享目录写一个环境配置脚本。
4.2 场景二:批量创建账号并强制首次改密
新入职一批员工,需要批量创建账号。人工一条条敲命令不是不行,但容易漏参数。用一个简单的脚本搞定:
bash复制#!/bin/bash
USER_LIST=(zhang li wang zhao sun)
GROUP=staff
# 确保组存在
getent group "$GROUP" >/dev/null || groupadd "$GROUP"
for u in "${USER_LIST[@]}"; do
if id "$u" >/dev/null 2>&1; then
echo "用户 $u 已存在,跳过创建"
continue
fi
useradd -m -s /bin/bash -G "$GROUP" "$u"
echo "$u:Init@$(date +%Y%m)" | chpasswd
passwd -e "$u"
done
这段脚本有几个设计细节:
- 先用id判断用户是否已存在,避免重复执行时报错。
- 初始密码写成Init@加上当前年月,同时马上passwd -e,强制用户首次登录改密。这样初始密码即使泄漏,有效期也极短。
- 创建完成后不要在任何日志里保留明文密码,脚本执行完最好直接删除或放入仅root可读的文件。
批量场景也可以使用newusers工具,它从文件按格式批量导入用户信息,适用于几百上千个账号的规模。格式为username:password:UID:GID:comment:home:shell,先准备好文本文件再执行newusers < users.txt。注意密码字段可以是明文,但要通过chpasswd或passwd -e做二次处理,否则安全隐患很大。
4.3 场景三:服务账号与无登录账号
很多服务需要独立账号运行,比如Web服务、消息队列、对象存储服务。这类账号不需要登录Shell、不需要家目录,甚至不需要密码,原则是最小权限。
bash复制useradd -r -s /sbin/nologin -M appservice
-r表示创建系统账号,UID自动落在系统范围;-s /sbin/nologin表示禁止登录;-M表示不创建家目录。这样这个账号只能用来作为服务运行身份,别人无法通过它登录系统,也无法在系统里留下个人文件。
服务账号千万不要给交互式Shell,不要让它是root组成员。如果服务需要读取某些密钥或配置文件,应该用文件权限控制,甚至使用专门的密钥管理方案,而不是把服务账号放进sudo组或root组,否则一旦服务被攻破,危害范围会呈指数级放大。
5. 排查记录:你大概率也会遇到这些坑
5.1 明明加入了组,却还是 Permission denied
这是最常见的坑。某开发者把账号加入了docker组,告诉他可以免sudo执行docker命令了,结果他ssh登录后发现还是报权限错误。原因在于:用户当前已登录的会话,其附属组列表是在登录时确定的。当你通过usermod -aG把人加进新组后,他已经在线的shell不会自动刷新组成员关系,必须完全退出重新登录,或者执行newgrp docker开启一个子Shell,新组才能生效。
这个问题的本质是组成员关系在会话创建时就缓存了,后续修改不会热更新。排查步骤很简单:
bash复制id username
groups username
如果groups输出里有目标组,但用户实际执行命令仍然失败,那几乎可以断定是会话没有刷新。让用户重新登录,而不是反复去查权限。
5.2 家目录权限不对导致无法登录
有一种现象是SSH认证已经通过,但连接立刻断开,或者登录后出现“Could not chdir to home directory”。问题往往在家目录权限和属主。
家目录如果权限是777,SSH服务出于安全考虑会拒绝信任,因为它担心任何用户都能篡改里面的文件(比如.ssh/authorized_keys)。而家目录属主如果不是用户本人,则可能导致Shell无法正常读写历史记录和配置文件。
排查命令:
bash复制ls -ld /home/zhang
ls -ld /home/zhang/.ssh
修复方法:
bash复制chown -R zhang:zhang /home/zhang
chmod 700 /home/zhang
chmod 700 /home/zhang/.ssh
chmod 600 /home/zhang/.ssh/authorized_keys
很多人配置SSH密钥登录时,只关心authorized_keys里的公钥对不对,忽略了.ssh目录权限。权限过宽是SSH服务拒绝使用密钥认证的一个重要原因。
5.3 删了用户文件成了“孤儿”,怎么找回归属
前面提过,直接userdel删用户会导致该用户拥有的文件变成数字UID显示。这类文件不会自动消失,会一直占用空间,还可能在服务巡检里显得非常扎眼。
判断哪些文件是这个用户的:
bash复制find / -uid 1002 2>/dev/null
批量修复归属:
bash复制find / -uid 1002 -exec chown newuser:newgroup {} \;
但千万别在没排查完就去批量chown,等于是把未知归属的文件一次性全归到新用户名下,可能有安全隐患。正确思路是先删掉用户账号,但保留家目录,通过家目录文件列表和定时任务列表确认哪些文件需要保留,再转移归属。
5.4 常用排查速查表
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 加入组后权限没生效 | 会话未刷新 | 重新登录或执行newgrp 组名 |
| 文件属主显示数字 | UID对应的用户已删除 | find -uid 定位,chown改归属 |
| 登录后回到home失败 | 家目录属主/权限错误 | chown / chmod 700 |
| passwd修改报“token manipulation error” | /etc/shadow被误改或权限不对 | 检查shadow权限,用vipw修复 |
| SSH密钥登录失效 | .ssh目录或authorized_keys权限过宽 | chmod 700 .ssh / 600 authorized_keys |
| 共享目录新文件别人改不了 | 新文件属组是主组,没有继承组 | 设置setgid位(chmod g+s) |
| 组里有成员但getent看不到 | 使用了LDAP或其它NSS数据源 | 检查/etc/nsswitch.conf配置 |
这几类问题基本覆盖了日常“用户与组管理”的大部分异常场景。实际运维中你还会遇到更复杂的组合问题,但只要把UID/GID、主组附加组、setgid继承、会话刷新这些底层概念理清楚,绝大多数问题都能归类定位,而不是靠撞大运。
做“用户与组管理”这件事,我的体感是:命令本身五分钟就能背完,真正值钱的是做事的顺序和预防式管理。每初始化一台新服务器,我先做一张用户规划表,把角色、用户、组、目录权限、有效期五列写清楚,再动手执行。批量环境就靠配置管理工具把用户、组、sudo规则都写成声明式配置,从源头杜绝“某个账号只在那台机器上有”这种隐性孤儿账号。权限问题往往不是单一命令能解决的,但你从用户与组这个根源开始梳理,大部分疑难杂症都能找到出口。
