在Linux服务器上折腾多了,你会发现用户和用户组这个东西,绕不开。不管你是给新同事开账号,还是部署服务时创建一个运行用户,甚至排查一个诡异的Permission denied,最后都得回到useradd、usermod、groupadd这组命令上来。话题看起来基础,但这些年我见过不少人把“主组”和“附加组”搞混,或者用usermod -G时手一抖把用户的附加组清空。2026年整理运维笔记,我决定把这个主题彻底拆开讲一讲。这个系列已经写到002篇,这次换成“概念+场景+命令+知识点”的方式,把用户、用户组、附加组的关系一次理清。文章适合刚接触Linux的新人,也适合想查漏补缺的运维和开发,10个实战例子全部来自真实运维场景,每个都附带知识点提取,直接照着敲就行。
1. 用户与用户组的基础概念
1.1 为什么Linux要区分用户
Linux是一个多用户多任务系统,这五个字不是白叫的。多用户意味着同一台机器上可以同时存在多个身份,每个身份都有自己的家目录、环境变量和文件所有权;多任务意味着这些用户可以同时跑自己的进程,互不干扰。内核要保证互不干扰,就必须给每个人分配一个唯一标识,然后靠这个标识决定你能动哪些文件、不能动哪些文件。这就是UID的由来。
用户组则是把一堆用户归拢成一个集合。比如一个项目组的同事都要读某个目录,与其一个个给文件授权,不如创建一个组,把人都塞进去,然后把目录的组权限打开。从权限模型上看,Linux判断你能不能访问文件,先看你的UID是不是文件属主,再看你的任何一个组身份是不是匹配文件的属组,最后才落到other。这个顺序决定了很多问题排查要沿着“用户-主组-附加组”这条线走。
1.2 UID、GID与附加组的关系
这里必须先说清楚一个最容易被忽视的点:每个用户都有一个主组,也就是initial group,在/etc/passwd的第四个字段里记录;与此同时,用户还可以同时属于若干个附加组,也就是supplementary groups。当你执行id命令时,第一行会显示uid、gid、groups……那个gid就是主组的ID,groups后面跟着的则是主组加上所有附加组。
举个例子,执行id zhang后输出:
code复制uid=1001(zhang) gid=1001(zhang) groups=1001(zhang),27(sudo)
这时候zhang的主组是zhang,附加组是sudo。为什么要有附加组?因为一个用户只属于一个组往往不够。你是开发部成员,也在某个项目组里,还需要访问运维开的目录,这时候用附加组把多个组身份叠加起来,一套账号就能拥有多个层面的权限。很多新手以为创建用户时的-g参数是唯一归属,其实那只是主组,权限判断时还要看附加组。这是整个用户管理体系里最容易踩的坑。
1.3 用户与组相关的核心文件
命令改来改去,最终写的文件就那么几个。先认识它们,后续排查会快得多。
/etc/passwd保存用户基本信息,每行7个字段,用冒号分割:用户名、密码占位符、UID、GID、注释、家目录、登录shell。比如zhang:x:1001:1001::/home/zhang:/bin/bash。里面那个x代表密码哈希不在这里,实际放到shadow文件里。
/etc/shadow保存密码哈希和密码策略,普通用户不可读,里面字段包括用户名、哈希值、上次改密时间、最短天数、最长天数、提前警告天数、宽限天数、过期时间等。
/etc/group保存组信息,每行4个字段:组名、组密码占位符、GID、组成员列表。第四个字段很重要,它列出的不是所有主要组为该组的用户,而是把这个组作为附加组的成员。比如devteam:x:1010:zhang,li,说明zhang和li把devteam当作附加组。
/etc/gshadow则保存组密码和组管理员信息,一般用不到。还有两个配置文件值得注意:/etc/default/useradd和/etc/login.defs,它们控制用户默认参数,比如UID范围、home目录模板、默认shell、是否启用用户私有组等。很多人发现不同发行版useradd行为不一样,根源就在login.defs里的USERGROUPS_ENAB设置。
我强调一点:平时尽量不要用vim直接改这几个文件,改错了系统可能直接挂掉。真要手工编辑,请用vipw和vigr,这俩命令会在修改后帮你重新同步数据库,并且加锁防止并发写坏。尤其是shadow这种文件,一旦格式错,所有用户都可能登不进来。这些文件就是用户体系的底层数据结构,后面的命令无非是对它们的安全封装。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心命令与权限模型
2.1 用户管理命令全家桶
useradd是底层命令,adduser是它的友好封装。Debian系里adduser会有交互式提示,还会顺带帮你初始化更多东西,但对脚本和自动化来说,useradd更可控。重点参数先过一遍:-m创建家目录,-d指定家目录路径,-s指定登录shell,-g指定主组,用组名或GID都行,-G指定附加组,但用逗号分隔多个组,-u指定UID,-c加注释,-r创建系统用户,-M强制不创建家目录。
usermod负责修改已有用户的属性,常用参数包括:-l改用户名,-d配合-m迁移家目录,-g改主组,-G重设附加组列表,-a追加到附加组。注意,-a必须和-G一起用,单独写-aG是比较稳妥的组合。userdel删除用户,-r会连家目录和mail spool一起删掉,但其他目录下属于该用户的文件需要另外处理。
密码管理主要通过passwd和chage。passwd设置或修改密码,-l可以锁定账户,-u解锁,-d删除密码强制下次登录设置新密码。chage则负责密码策略,比如密码最长使用天数、最短修改间隔、过期前警告天数、账号宽限期等。chsh用来修改登录shell,但很多时候直接用usermod -s /bin/bash更顺手。
2.2 组管理命令与成员操作
groupadd创建一个新组,-g指定GID,-r创建系统组,-f表示组已存在时不报错,这对脚本很友好。groupmod -n改组名,-g改GID。groupdel删除组,但有个限制:如果某个用户的主组就是当前组,groupdel会报错,必须先把用户的主组改到别处,才能删除。
gpasswd是管理组成员最常用的命令:-a把用户加入组,-d把用户移出组,-M直接设置整组用户列表,-A设置组管理员,-R禁掉组密码,-r删掉组密码。需要注意,-M设置的是完整列表,会覆盖组里原本已经存在的成员,所以生产环境里我一般用-g和-d逐个人添加,而不是图省事用-M。
还有一个容易被忽略的命令是newgrp。它能在当前shell里临时切换主组。比如你的主组是zhang,但希望接下来创建的某个文件属组是devteam,可以先执行newgrp devteam,然后再touch文件,文件属组就变成devteam了。这个操作只影响当前shell,不会修改/etc/passwd里的主组配置,也不需要重新登录。
2.3 权限模型里用户组到底怎么起作用
有了用户和组,最终要落到文件权限上。Linux文件权限用9个位表示,对应owner、group、other三类的rwx。文件属主是UID对应的用户,文件属组是GID对应的组,其他所有用户都属于other。你执行ls -l时看到的root root、zhang devteam,第一个是属主,第二个是属组。
目录的rwx含义和文件不同:r能列出目录项,w能在目录里增删改文件,x能进入目录。所以常见的安全设置是目录750,owner可读可写可执行,group可读可执行,其他人什么也干不了;文件用640,owner读写,group只读。这样设计很合理:目录需要x进入,文件不需要执行,所以文件640比644更安全。
另外有两个和组关系很密切的特殊权限位。setgid位会让新创建的文件自动继承目录的属组,这对多人协作目录非常重要。举例来说,/srv/project属于devteam组,权限设为2770,成员zhang在里面创建文件,文件属组自动变成devteam,其他组成员就能正常协作。粘滞位用在/tmp这类目录,防止某个用户删除另一个用户创建的文件。后面例子里会专门用到这两个东西。
3. 10个实战例子:核心操作逐步拆解
3.1 例子1:创建普通用户并设置密码
新同事入职,需要一个普通账号,这是最常见的需求。
bash复制useradd -m -d /home/zhang -s /bin/bash zhang
passwd zhang
-m会创建家目录。如果不加-m,很多发行版上用户登录后没有家目录,cd会回到根目录,很多软件也会因为找不到HOME变量出问题。-d指定家目录位置,这里用默认的/home/zhang其实也完全没问题,显式写出来是为了避免不同发行版默认路径不一致。-s指定bash作为登录shell,如果写成/usr/sbin/nologin,用户就无法登录。
执行后可以查看结果:
bash复制id zhang
ls -ld /home/zhang
id输出会显示uid=1001(zhang) gid=1001(zhang) groups=1001(zhang)。这里有个细节:useradd默认启用了用户私有组机制,也就是创建zhang用户时,如果名为zhang的组不存在,系统会自动创建一个同名的组,并把它作为用户的主组。这样一来,每个用户都有一个自己的私密主组,文件权限默认就不会对其他同组人开放。
知识点提取:
- useradd -m创建家目录,-d指定家目录路径,-s指定shell。
- /etc/passwd中记录用户基本信息,/etc/shadow中记录密码哈希。
- 用户私有组机制依赖/etc/login.defs里的USERGROUPS_ENAB配置。
- 创建用户后一定要设密码,否则账号处于锁定状态,无法登录。
3.2 例子2:把用户加入sudo附加组
管理员不想直接把root密码给同事,最规范的做法是给用户加sudo权限。在Debian/Ubuntu系里,sudo权限是通过sudo组授权的;在RHEL/CentOS系里,管理员组叫wheel。命名不同,机制一样。
bash复制usermod -aG sudo zhang
这里必须强调-a参数。usermod -G会重设整个附加组列表,不写-a就会清空用户之前的所有附加组。比如zhang原来还在devteam组里,你执行usermod -G sudo zhang,devteam立刻没了。写成usermod -aG sudo zhang,才是“追加到sudo组”的意思。
然后让用户重新登录,或者执行newgrp sudo刷新当前会话的组身份。为什么必须重新登录?因为登录时内核会把当前用户属于哪些组固化到会话里。usermod只是改了配置文件,不会热更新到已经在跑的shell进程。这个问题我在例子里遇到过很多次。
检查是否生效:
bash复制groups zhang
id zhang
第一句查用户在数据库里的组列表,第二句查当前会话的组身份。如果当前会话是旧session,id可能看不到sudo,但groups能看到,这就是典型的会话缓存问题。
知识点提取:
- usermod -aG是追加附加组的安全姿势,-G会覆盖原有列表。
- sudo组和wheel组在不同发行版命名不同,加组时要先确认。
- 组身份在登录会话中固化,修改后需要重新登录或使用newgrp。
- 群里讨论时经常有人问“加了docker组为什么还报权限不对”,九成就是这个原因。
3.3 例子3:创建业务组并把多个用户加入
假设有个项目叫devteam,需要让zhang和li都能访问共享目录。
bash复制groupadd devteam
usermod -aG devteam zhang
usermod -aG devteam li
然后创建共享目录,设置好属组和权限:
bash复制mkdir -p /srv/project
chown root:devteam /srv/project
chmod 2770 /srv/project
chown root:devteam把目录属主设为root,属组设为devteam。chmod 2770是什么含义?2代表设置setgid位,770代表owner和group都有rwx权限,other没有任何权限。setgid位加上之后,目录里新建的文件会自动继承devteam组,团队成员之间就能顺畅地共享文件。
如果不用setgid位,会出现什么情况?zhang在目录里创建一个文件,文件属组默认是他的主组zhang,不是devteam。li虽然也是devteam的成员,但文件属组是zhang,devteam组的权限对文件不生效,li可能连读都读不了。加一个2,就能让文件自动变成devteam属组,协作就正常了。
知识点提取:
- groupadd创建组,usermod -aG逐个添加成员。
- chown root:devteam设置目录属主和属组。
- 2770中2是setgid,770是属主和属组rwx,other无权限。
- 多人协作目录推荐2770 + setgid,避免文件属组混乱。
3.4 例子4:创建系统用户运行服务
部署Web服务时,不建议直接用root启动,也不建议用普通业务账号启动。规范做法是创建一个系统用户,让服务以最小权限运行。
bash复制useradd -r -s /usr/sbin/nologin -d /var/lib/myapp -M myapp
-r表示创建系统用户,UID范围由/etc/login.defs的SYS_UID_MIN和SYS_UID_MAX控制,一般是100到999,不会占用普通用户1000以上的UID段。-s /usr/sbin/nologin设置登录shell为nologin,这个用户无法交互式登录,只能被服务进程使用。-d指定家目录占位路径,这里指向一个已经存在的软件目录。-M表示不自动创建家目录。
这样创建出来的用户,即使Web服务被攻破,攻击者也很难用这个账号登录shell。配合目录权限,myapp用户只能访问你授权给它的目录,影响面被大大压缩。
知识点提取:
- 系统用户UID范围与普通用户不同,用-r创建。
- nologin shell可以阻止用户登录,但进程仍可以用该身份运行。
- -M不创建家目录,适合服务账号。
- 服务账号尽量遵循最小权限原则,不要用root跑业务。
3.5 例子5:修改用户主组和附加组(含追加覆盖的坑)
用户从A部门调到B部门,需要变更主组;同时可能还要加进新的附加组。
bash复制usermod -g bgroup zhang
-g这里修改的是主组。之后检查id zhang,会发现gid从zhang变成了bgroup。
如果想把zhang的附加组改成ops和devteam,可以写:
bash复制usermod -G ops,devteam zhang
这种写法的问题是,它会覆盖用户当前所有附加组。假如zhang之前在sudo组里,执行完之后sudo就没了。所以实际运维中,最安全的追加写法一定是:
bash复制usermod -aG ops zhang
usermod -aG devteam zhang
每次只追加一个,保持幂等,避免误伤。有人会说,能不能写一条命令把需要的组都加上?可以,usermod -aG ops,devteam zhang这样写也没问题,但前提是你非常清楚当前附加组有哪些。如果只是想加一个组,别想太多,-aG收着用。
知识点提取:
- -g修改主组,-G重设附加组,-aG追加附加组。
- 主组只能有一个,附加组可以有多个。
- 重设附加组会清空原有组身份,生产环境慎用。
- 脚本里批量添加用户到组时,优先用usermod -aG。
3.6 例子6:删除用户并清理文件
离职账号不能在服务器上留太久。但删除用户并不是userdel -r那么简单。
bash复制userdel -r zhang
-r会删除用户家目录和mail spool,但如果你手动在/opt或者/backup下给这个账号创建过文件,那些文件不会自动删除。更麻烦的是,它们还保留着zhang的UID信息,一旦以后创建新用户,系统复用了这个UID,新用户会莫名其妙变成这些旧文件的属主,安全隐患非常大。
所以删用户之前,先全局扫一遍:
bash复制find / -uid 1001 -path /proc -prune -o -path /sys -prune -o -print 2>/dev/null
这里的uid是指要删除用户的UID,不是用户名。因为用户名在userdel之后可能不复存在,但文件inode里记的是UID,不是名字。找到文件后,要么归档给接手的同事,要么chown到新账号,要么备份后删除。总之要确保不留孤儿文件。
知识点提取:
- userdel -r删除家目录和mail spool,但不会全局清理。
- 文件属主存的是UID,不是用户名,UID复用会带来文件归属漂移。
- find / -uid xxx可以找出某用户拥有的所有文件。
- 离职账号删除前要审计,删除后要检查UID是否残留。
3.7 例子7:批量创建用户并设置密码
给一批新员工或者班级学员开账号,手动一个个useradd太蠢了,写个for循环最舒服。
bash复制for user in alice bob carol; do
if getent passwd "$user" >/dev/null; then
echo "$user already exists"
else
useradd -m -s /bin/bash "$user"
echo "$user:Temp@123456" | chpasswd
chage -d 0 "$user"
fi
done
这里的getent passwd是查询用户是否存在,存在就跳过,避免重复创建报错。echo加chpasswd可以批量设置密码,不需要交互式输入。chage -d 0的作用是强制用户第一次登录时修改密码。这样初始密码是Temp@123456,登录后系统会要求立即换掉,安全性好很多。
如果用的不是bash而是其他shell,命令写法略有差异,但核心逻辑一样。生产环境里做批量操作,一定要加存在性判断,不然脚本跑到一半报错,你还要花时间清理半成品账号。
知识点提取:
- getent passwd可跨NSS查询用户是否已存在。
- chpasswd支持从标准输入批量设置密码。
- chage -d 0强制首次登录改密。
- 批量脚本必须考虑幂等性,避免重复执行出错。
3.8 例子8:设置密码过期策略
有些等保环境要求密码定期更换,这就要用chage配置密码策略。
bash复制chage -M 90 -W 7 -I 10 zhang
-M 90表示密码最长使用90天,到期前必须修改。-W 7表示提前7天开始警告。-I 10表示密码过期后,用户再等10天还不改密码,账号就会被禁用。三个参数一组合,就实现了“90天改密、提前7天提醒、过期宽限10天”的策略。
查看当前策略:
bash复制chage -l zhang
输出会显示上次修改密码时间、密码过期时间、警告时间等。还有两个常见参数:-m设置两次改密之间的最短间隔,-d设置上次改密时间。-d 0就是强制下次登录修改密码,这个和例子7里用的效果一样。
和passwd相比,chage的语义更清晰,设置完能直接chage -l查看。注意,密码策略最终要配合PAM才会严格生效,chage只是设置账号属性。如果系统里没有配置pam_pwquality或pam_unix的相关规则,有些策略可能达不到预期,但90%的常规场景下chage已经够用了。
知识点提取:
- -M最长密码天数,-W警告天数,-I宽限天数。
- -d 0强制下次登录改密,适合初始账号。
- chage -l可以查看账号密码策略。
- 密码策略最好和PAM一起配置,才能形成完整闭环。
3.9 例子9:限制用户只能访问指定目录
这个问题在热词里特别常见,“linux如何设置子用户只能访问指定目录”。先说结论:单靠传统权限很难做到完全隔离,因为用户从根目录到目标目录的路径上,每一步都需要x权限,你很难保证中间目录不透出信息。
一个基础方案是用组权限圈定一个共享目录:
bash复制groupadd ops
usermod -aG ops zhang
mkdir -p /srv/ops
chown root:ops /srv/ops
chmod 750 /srv/ops
这样zhang能进入/srv/ops,但如果/srv本身是755,其他用户也能进入/srv目录列表,只能看到目录名而已,具体文件进不去。如果安全等级高,希望用户SSH登录后直接被锁定在某个目录里,那需要配置OpenSSH的ChrootDirectory,或者用容器/jail方案。这是另一套配置,原理涉及chroot和PAM。
所以我给的实际建议是:项目隔离用组权限就够了,真正的用户隔离交给chroot或容器,不要把两者混在一起搞。
知识点提取:
- 组权限只能控制“能否访问特定目录”,不能限制用户对父目录的可见性。
- 严格目录隔离需要chroot或容器方案。
- 目录权限设计推荐750,文件推荐640。
- 不要试图用chmod 000来解决一切,那会让用户连家目录都进不去。
3.10 例子10:用newgrp和setgid实现组协作
这个例子演示一个容易被忽略的组切换技巧。假设zhang的主组是zhang,但今天要往/srv/project里创建一批文件,希望文件属组是devteam,方便团队其他人协作。
最直接的做法是临时切换主组:
bash复制newgrp devteam
cd /srv/project
touch readme.md
ls -l readme.md
此时readme.md的属主是zhang,属组是devteam,因为当前shell的有效主组已经切换成了devteam。newgrp不会修改/etc/passwd配置,只在当前shell生效,退出当前shell再登录,主组又变回zhang。这个命令在某些只需要临时改变属组的场景非常有用。
再看setgid协作目录的做法。如果/srv/project已经设成2770并且属组是devteam,那么无论zhang当前主组是什么,在目录里新建文件都会自动把属组设置成devteam。两种方式都能达到目的,但setgid是一劳永逸的目录级配置,newgrp是临时的会话级手段。实际协作目录建议直接用2770,省得每次都得newgrp。
知识点提取:
- newgrp临时切换主组,只影响当前shell进程。
- setgid目录让新文件自动继承目录属组。
- 组协作目录推荐chmod 2770 + chown root:devteam。
- 临时操作用newgrp,长期共享用setgid目录。
4. 常见问题与排查技巧实录
4.1 已加入组但执行命令还是Permission denied
这个是我见过最多的问题。用户执行usermod -aG docker user,然后立刻跑docker命令,提示没有权限。原因在于当前登录会话的组身份还是旧的,内核记录的是登录那一刻的组列表。解决办法是重新登录,或者执行newgrp docker刷新当前shell的组身份。
排查的时候要看两处:一是getent group docker查看数据库里组成员到底有没有更新,二是id查看当前会话里有没有docker这个组。如果组里有但id没有,说明是会话缓存;如果getent里都没有,说明命令根本没执行成功,或者加错用户了。
4.2 用-G覆盖导致附加组丢失
有一次我同事想把用户加到ops组,图省事写了usermod -G ops user,结果用户原本所在的sudo、devteam全部消失,直接把人家的管理员权限撸了。这就是没加-a的后果。恢复方法也不复杂,重新把原来的组再-aG加回去。
但这说明一个习惯问题:所有涉及附加组变更的操作,默认写成usermod -aG。除非你确实是想重设完整附加组列表,并且心里非常清楚当前列表是什么,否则不要裸写-G。这个坑踩过一次就长记性,但希望看文章的你不用踩第一次。
4.3 userdel删了用户但文件没删干净
userdel -r只处理家目录和mail spool,你在/home之外的目录给用户单独分配过的东西不会被删。比如手动改了Apache配置里的DocumentRoot到/opt/www,这个目录归zhang所有,删用户时就不会动。处理办法是先find / -uid UID全局扫,再用chown或rm处理。如果不处理,UID一旦被新用户复用,新用户就会莫名拥有这些文件,属于典型的权限越界隐患。
4.4 groupdel删不掉组
groupdel提示“cannot remove the primary group of user 'zhang'”,说明这个组是某个用户的主组。解决方法是先把用户的主组改掉,再删组:
bash复制usermod -g zhang zhang
groupdel oldgroup
改掉主组后再删除就正常了。如果你只是想清空组成员,不需要删组,用gpasswd -d逐个移除即可。
4.5 常见问题速查表
| 现象 | 大概率原因 | 处理办法 |
|---|---|---|
| 加入组后权限不生效 | 会话组缓存未刷新 | 重新登录或newgrp组名 |
| 用户莫名失去sudo权限 | usermod -G覆盖了附加组 | 重新usermod -aG sudo user |
| userdel后文件属主变数字 | 残留文件未清理 | find / -uid UID定位后处理 |
| groupdel报错 | 组仍是某用户主组 | 先usermod -g修改主组再删 |
| 用户无法SSH登录 | 登录shell是nologin | 查看/etc/passwd,usermod -s /bin/bash |
| passwd命令提示用户不存在 | /etc/passwd被误改 | 使用vipw修复,配合pwck检查 |
5. 实操心得与安全建议
5.1 我踩过的组权限坑
十年下来,我对用户管理的最大体会是:权限问题九成是组没加对、会话没刷新、目录权限和主组不匹配这三类。那些看起来莫名其妙的Permission denied,大部分都能通过id和getent group查个水落石出。而且我发现很多人喜欢把用户直接加进root组,这是一种非常危险的习惯,等于变相给了一个非root用户接近root的能力。正确做法是加sudo或wheel组,再通过sudo规则限制命令范围。
5.2 用户管理规范建议
用户名统一用小写,普通用户UID从1000开始,系统用户用-r创建,业务组名直接对应项目名称。主组尽量保持用户私有组,不要拿主组做权限分组,因为主组只有一个,不够灵活;附加组才是授权的主战场。这样一旦需要回收某个项目的权限,直接从附加组里移除用户即可,不会影响到他的其他身份。
5.3 安全审计与实用小技巧
定期检查sudo组里有谁,检查哪些用户没有密码,检查有没有意外存在的UID 0账号。这些操作可以用getent配合awk实现,比手动翻文件更规范。批量加用户之前,先用getent group判断目标组是否存在,如果不存在先groupadd,脚本会稳很多。最后再分享一个小技巧,凡是修改用户或组配置后,都执行一遍pwck和grpck,能及时发现文件格式错误,避免小问题拖成大故障。
