1. 为什么说用户和组是 Linux 的“地基”
很多人学 Linux 是从 ls、cd、grep 这些命令开始的,玩到后面突然发现,自己明明照着教程建了一个用户,结果登录进去啥也干不了;或者在 /home 下新建了一个目录,别人就是进不去。这时候才意识到,用户和组的管理不是“记几条命令”那么简单。
本地用户和组的管理,是 Linux 权限体系的起点。系统里每个进程、每个文件、每个目录,最终都要落到“某个用户”和“某个组”头上。你要理解文件为什么是 rwxr-xr--,要理解为什么 root 能改任何人的密码,要理解多用户服务器上怎么给不同人分配不同的访问范围,前提都是把用户和组这一层搞明白。
这篇文章面向两类人:一是刚接触 Linux 的初学者,想系统搞清楚 useradd、passwd、usermod、userdel 这些命令到底在干什么;二是已经会敲几个命令、但工作中经常遇到“权限问题”的运维或开发同学,想补上底层机制这块短板。今天先讲上半部分:核心概念、四个关键配置文件、用户管理的完整操作,组管理和实战案例分析放在后面的内容里展开。
文章里所有的命令我都按照最常见的发行版环境来写,Ubuntu/Debian 和 CentOS/RHEL 系都能直接跑,个别差异我会在对应位置提醒。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先把底层机制看明白:UID、GID 和四个核心配置文件
2.1 UID 和 GID:Linux 不认名字,只认数字
很多人第一次看 /etc/passwd 文件时会懵,里面明明写着 root、daemon、nobody 这些名字,但系统真正识别身份靠的不是字符串,而是一个整数——UID(用户 ID)和 GID(组 ID)。
可以这样理解:用户名是给人看的“名片”,UID 才是系统内部用的“身份证号”。你执行 ls -l 看到的 root root,其实是系统把 UID 0 和 GID 0 翻译成了可读的名字。如果某个文件的属主 UID 在 /etc/passwd 里找不到对应条目,ls 会直接显示一个数字而不是名字,这也是后面排查“孤立文件”问题时的一个关键信号。
Linux 对 UID 的分配有约定俗成的区间:
| UID 范围 | 类型 | 说明 |
|---|---|---|
| 0 | root | 超级用户,权限不受文件权限位限制 |
| 1–999(部分发行版到 499) | 系统用户 | 给服务进程用,比如 sshd、nginx、mysql,通常没有登录 Shell |
| 1000 及以上(部分发行版 500 起) | 普通用户 | 人登录用的账号,默认 UID 从 1000 开始递增 |
这个区间不是内核写死的,而是由 /etc/login.defs 里的 UID_MIN、UID_MAX、SYS_UID_MIN 这些参数控制的。发行版不同,划分可能不一样,你可以自己 grep UID_MIN /etc/login.defs 看一下本机实际值。
为什么要区分系统用户和普通用户?核心目的是安全隔离。服务以独立系统用户跑,就算被漏洞攻破,也只是那个服务账号的权限,拿不到整个系统。你去看 ps 输出的进程属主,会发现数据库、Web 服务各有各的账户,谁也不碰谁的配置目录——这就是最小权限原则在用户层面的体现。
GID 的规则和 UID 基本一致。每个用户在 /etc/passwd 里有一个“主组”(也叫初始组),同时还可以通过 /etc/group 加入多个“附加组”。一个用户最终拥有的文件访问权限,是主组和所有附加组的并集。这个机制后面操作共享目录时非常关键,先记在心里。
2.2 /etc/passwd 逐字段拆解
/etc/passwd 是用户信息最核心的数据库文件,每一行对应一个用户,用冒号分成 7 个字段。随便打开看一行:
bash复制webapp:x:1001:1001:Web Application User:/home/webapp:/usr/sbin/nologin
从左到右的含义:
webapp:用户名。x:密码占位符。以前这里存的是密码哈希,后来为了安全把哈希挪到了/etc/shadow,这里统一写成x。1001:UID。1001:主组 GID。Web Application User:注释信息(GECOS 字段),一般写用户全名或用途,可以是空。/home/webapp:家目录。用户登录后的初始目录,~就指向这里。/usr/sbin/nologin:登录 Shell。改成/bin/bash就能正常交互登录,改成/usr/sbin/nologin或/bin/false则禁止登录。
这里有个很容易忽略的细节:/etc/passwd 是全局可读的,任何用户都能 cat 它。所以里面不要放敏感信息,注释字段也别写什么密码提示之类的东西。现代系统把密码哈希挪到 /etc/shadow,正是因为这个文件必须对所有人开放(很多命令都要读取它做 UID 到用户名的解析),但又不能把哈希暴露给所有人方便离线爆破。
2.3 /etc/shadow:真正放密码的地方
/etc/shadow 的权限通常是 000 或 640,只有 root 和 shadow 组能读。每一行对应一个用户,同样是冒号分隔,字段比 passwd 多:
bash复制webapp:$6$rounds=656000$...:19250:0:99999:7:::
各字段的作用:
- 用户名。
- 密码哈希。
$6$开头是 SHA-512,$y$开头是 yescrypt(较新发行版默认),!或*开头表示账号被锁定或没有密码。 - 最后一次修改密码的日期,单位是“自 1970-01-01 算起的天数”。
- 密码最少使用天数(多少天内不允许再改)。
- 密码最长有效期(多少天后必须改)。
- 提前多少天警告用户密码即将过期。
- 过期后多少天账号被禁用。
- 账号的绝对过期日期(到哪天账号失效),空表示永不过期。
初学者最容易踩的坑是“为什么我明明设置了密码,看 shadow 里却是一串来历不明的字符串”——那正是哈希加密后的结果,不是明文,也不可逆。系统校验密码的方式是:把你输入的密码用同样的算法重新哈希,然后和 shadow 里的哈希比对。你不需要关心算法细节,但需要知道一件事:永远不要手动编辑 shadow 里的哈希,改密码请用 passwd。
2.4 /etc/group 和 /etc/gshadow
/etc/group 记录组信息,每一行 4 个字段,和 passwd 的结构很像:
bash复制devteam:x:1002:alice,bob
含义依次是:组名、组密码占位符、GID、组成员列表(逗号分隔的附加成员)。注意这里只列“附加成员”,主组成员不需要列在这里,因为主组关系已经写在 passwd 里了。
/etc/gshadow 则是 group 的“影子文件”,存放组密码和组管理员列表。组密码这个机制在现实中基本没人用,大部分场景下你就当它不存在,知道有这样一个文件就行。组管理员(组长)可以用 gpasswd 管理组成员,这个小众功能在少数团队协作场景会用到,后面组管理部分我会提一句。
这里要建立一个认知:用户和组的关系不是“一个用户属于一个组”,而是“一个用户有主组 + 若干附加组”。主组决定这个用户创建文件时的默认属组,附加组用于扩展权限。你在创建用户时如果不指定 -g,系统会建一个和用户名同名的组作为主组,这也是很多发行版的默认行为。
3. 用户管理三大件:useradd、usermod、userdel 实操
3.1 创建用户不是敲一条 useradd 就完事了
useradd 是创建用户的核心命令,但很多人只敲 useradd testuser,然后发现用户建好了,家目录却没有,登录 Shell 也怪怪的。原因是:不同发行版的 useradd 默认行为差异很大,Ubuntu 系默认会建家目录,CentOS 系反而不会。想写出可移植的命令,就必须显式指定关键参数。
我平时创建用户的最低标准是下面这条:
bash复制useradd -m -d /home/webapp -s /bin/bash -c "Web Application User" webapp
参数含义:
-m:如果家目录不存在则自动创建。-d:指定家目录路径,默认是/home/用户名,需要改路径时才用。-s:指定登录 Shell。要给交互登录权限用/bin/bash,如果只给服务跑进程用/usr/sbin/nologin。-c:写入/etc/passwd的注释字段,建议写上用途,方便后面对账。-u:手动指定 UID,某些场景(比如从旧服务器迁移账号)需要保持 UID 一致,否则文件属主会错乱。-g:指定主组,可以填组名或 GID。-G:指定附加组列表,多个组用逗号分隔。
还有几个进阶参数值得知道:-r 创建系统用户(UID 落在系统用户区间),-M 强制不创建家目录(适合纯服务账号),-e 指定账号过期日期,-f 指定密码过期后多少天禁用账号。这些参数在挖完坑之后你会觉得很香,但一开始别贪多,先把最基本的 -m -s -c 用熟。
创建用户时系统不只是往 /etc/passwd 和 /etc/shadow 各加一行,它背后还干了好几件事:从 /etc/skel 目录复制默认配置文件到新家目录(.bashrc、.profile 这些就是从这里来的),创建用户邮件目录(多数发行版在 /var/mail/用户名),设置家目录的初始属主和权限。所以如果你建完用户发现 .bashrc 没了,多半是 /etc/skel 被改过或者家目录没建成功。
必坑提醒:创建完用户后,密码是空的,shadow 里密码字段通常是
!或!!。这时候登录会被拒绝,必须先passwd 用户名设置密码。
3.2 密码设置和 passwd 的细节
设置密码很简单,root 执行:
bash复制passwd webapp
系统会提示输入两次密码。注意 root 给普通用户设置密码时,即使你输入“123456”这种弱密码也可能成功,因为 root 不受密码复杂度策略的强制约束。但不代表这是好习惯,生产环境建议配置 PAM 的密码策略(pwquality 模块),让系统强制检查复杂度。
还有一个很实用的技巧,批量初始化账号密码时用管道输入:
bash复制echo "TempPass2025!" | passwd --stdin webapp
--stdin 这个参数在 CentOS/RHEL 系支持,Ubuntu 系的 passwd 不支持,它会提示“unrecognized option”。Ubuntu 上可以用 chpasswd 达到同样效果:
bash复制echo "webapp:TempPass2025!" | chpasswd
强制用户首次登录后改密码,是新人账号初始化的标配操作:
bash复制chage -d 0 webapp
chage 的 -d 0 含义是“把密码最后一次修改日期设为 0”,也就是 1970 年,这样系统一算就知道密码早就过期了,下次登录必须先改密码。大家经常看到教程里写 passwd -e,两者本质等价,chage -d 0 是我个人更常用的写法,语义更清晰。
密码策略的管理都在 chage 手里。比如设置密码 90 天必须换一次,提前 7 天提醒:
bash复制chage -M 90 -W 7 webapp
查看某个用户的密码状态:
bash复制chage -l webapp
它会列出最后一次修改时间、过期时间、禁用时间等信息,排查“用户为什么不能登录”时非常有用。
3.3 usermod:用户建错了不用删,改就行
用户信息需要调整时,不需要删除重建,usermod 可以原地修改。最常用的几个场景:
改登录 Shell:
bash复制usermod -s /sbin/nologin webapp
把某个用户禁掉登录,但不删除账号、不动家目录,这是一个非常常见的“软封禁”手段,比直接删用户温和得多。配合 usermod -L 锁密码一起用效果更好:
bash复制usermod -L webapp
-L 会在 shadow 的密码哈希前面加一个 !,让密码失效;-U 可以解锁,把 ! 去掉。这两种操作和 passwd -l / passwd -u 是一回事,看你习惯用哪个。
把用户加入附加组:
bash复制usermod -aG docker webapp
这里的 -a(append)是追加的意思,和 -G 配合使用。如果只写 -G docker 而不加 -a,会把用户从原来所有的附加组里踢出去,只保留 docker 一个。我见过不止一个人因为漏了 -a,把一个用户从几十个组里“洗”成了光杆司令。凡是涉及修改附加组的操作,默认都先想想要不要加 -a。
修改家目录,连带迁移旧文件:
bash复制usermod -d /data/app -m webapp
-d 改家目录路径,-m 表示把旧家目录的内容搬过去。两个参数通常会一起用,否则家目录指过去了里面却是空的。
修改用户名:
bash复制usermod -l newname webapp
这里有个坑:改用户名不会自动改家目录名称,也不会自动改邮件目录。如果希望家目录也改名,需要手动 mv 或配合 -d -m 操作。而且改名后,用户创建的所有 crontab、进程里的旧属主信息可能需要手动清理。所以生产环境改用户名要格外谨慎,能不动就不动。
usermod 修改的信息在 /etc/passwd、/etc/shadow、/etc/group 里是即时生效的。但有一个例外:已经登录的用户。某个用户正在登录状态,你把他从附加组踢掉,他的当前会话仍保留原来的组成员资格,要重新登录才生效。排查“为什么踢了组权限还有”的问题时,先问一句“用户重登了吗”。
3.4 userdel 删除用户的正确姿势和残留处理
删除用户最简单的方式:
bash复制userdel webapp
但这条命令只删 /etc/passwd、/etc/shadow 里的条目,用户的 /home/webapp 和邮件目录通常会残留。要连家目录一起删,用:
bash复制userdel -r webapp
-r 表示同时删除家目录和邮件目录。请注意:它不会删除用户在其他地方拥有的文件,比如 /data 下用户创建的文件、用户的 crontab(部分发行版会提示清理)、用户拥有的进程关联资源。删除用户后,这些文件会变成“孤儿文件”,属主显示为一串 UID 数字。
有个使用场景要注意:如果用户还有进程在跑,userdel 会删除不成功或给出警告,并且不会杀掉进程。正确流程是先确认这个用户没有运行中的进程:
bash复制pgrep -u webapp
有输出就先 kill 或用 systemd 停掉相关服务,再执行删除。很多“删不掉用户”的问题都出在这一步。
删除后检查有没有残留的孤儿文件,可以用 find:
bash复制find / -nouser -o -nogroup 2>/dev/null
-nouser 表示属主在 passwd 里查不到,-nogroup 类似。看到结果后,决定是 chown 给别的用户,还是直接删除。这一步是清理工作的收尾,别省。
安全提示:删除用户是不可逆操作。生产环境我建议先
usermod -L锁密码,把用户状态冻结观察几天,确认不影响业务后再userdel -r。这不是保守,是给自己留退路。
4. 组管理:groupadd、groupmod、groupdel 与附加组策略
4.1 groupadd:什么时候需要自建组
系统里每个用户默认都有自己的私有组,那什么情况下需要手动创建组?最典型的就是“多个用户共同访问同一批文件”。比如一个项目组有 5 个开发,需要共享 /data/project 目录,你不可能把权限开放给所有人,最合理的方式就是建一个 devteam 组,把 5 个人都加进去,然后目录权限配置成组内可读写。
创建组:
bash复制groupadd devteam
指定 GID 创建:
bash复制groupadd -g 3000 devteam
创建系统组(给服务用的组):
bash复制groupadd -r backupgroup
-r 会让 GID 落在系统组区间。这种组的典型用途是:某个备份脚本需要管理一组特定文件,就给脚本单独建一个系统账号和系统组,避免和普通用户混在一起。为什么要限制在系统区间?因为普通用户的 UID/GID 可能增长到那个范围,造成意外重合,系统区间保证了不会和普通用户抢号。
groupadd 之后,组信息会写入 /etc/group。此时组是空的,可以用 usermod -aG devteam 用户名 往里面加人,也可以用 gpasswd -a 用户名 devteam。两条命令效果相同,gpasswd 的好处是支持把某人设为组管理员:
bash复制gpasswd -A alice devteam
组管理员可以用 gpasswd -a 和 gpasswd -d 自己管理组成员,不需要每次找 root。这个功能在部门内的小团队里挺实用,但要控制好赋予的人选。
4.2 groupmod 和 groupdel:改名、删除与常见限制
组改名用 groupmod -n:
bash复制groupmod -n devops devteam
把 devteam 改成 devops,GID 不变。注意:这只是改名字,组成员关系、相关目录的属组显示都会跟着变,但目录里如果之前用 chown -R :devteam 设置过属组,那些目录记录的还是旧 GID,名字变了 GID 没变所以显示依然正常,不会丢权限。真正要注意的是:如果别的脚本或配置里写死了旧组名的字符串,那就会失效,需要同步改。
删除组:
bash复制groupdel devops
一个限制:如果一个组是某个用户的主组,groupdel 会拒绝删除,提示“cannot remove the primary group of user”。这是保护机制,防止你把用户的主组删掉后文件属主陷入混乱。解决方法:先把这些用户的主组切换到别的组,或者先删掉这些用户,再执行 groupdel。
组管理里还有一块很容易被人忽略:/etc/group 里的组成员列表是“快照式”的。你用 usermod -aG 加人时,系统更新的是组文件,但目标用户如果有已经打开的会话,当前会话不会立即获得新组成员身份,必须重新登录。这和前面讲 usermod 时的现象是同一个原理——组成员资格在登录时快照到进程的属性里,运行中的进程不会实时刷新。
另外补充一个实用小知识:判断用户属于哪些组:
bash复制groups webapp
id webapp
id 输出更详细,包含 UID、GID 和所有附加组。日常排查权限问题时,id 用户名 几乎是我敲得最频繁的命令之一,比 groups 信息全得多。
5. 实战案例:从零搭建一个多人共享项目环境
前面讲的是零散操作,这一节把它们串起来。假设场景:某公司要上线一个内部数据分析平台,需要三拨人协作——运维负责部署和服务器管理,开发负责写代码和调试,数据库管理负责维护数据目录。他们需要共享 /opt/dataplatform 作为工作目录,开发之间有独立的个人账号,同时对共享目录有完全读写权限。
5.1 账号规划
先规划用户和组,这是很多人忽略的“设计环节”。没有规划,直接一顿 useradd,后面权限必乱。
- 组:
ops(运维)、devs(开发)、dbas(数据库管理)、dataplatform(共享项目组)。 - 用户:
ops01(运维)、dev01、dev02(开发)、dba01(数据库管理)。 - 所有参与项目的人都要加入
dataplatform附加组,因为共享目录只对这个组开放写权限。
规划完成后,创建组:
bash复制groupadd ops
groupadd devs
groupadd dbas
groupadd dataplatform
默认 GID 会自动递增,想固定 GID 就手动指定,比如 groupadd -g 5000 dataplatform。固定 GID 的好处是:跨服务器同步账号、备份恢复文件属主时,不会因 GID 错位导致权限错乱。生产环境建议一律指定。
5.2 批量创建用户
用前面讲的参数逐个创建:
bash复制useradd -m -s /bin/bash -g ops -G dataplatform -c "Ops User 01" ops01
useradd -m -s /bin/bash -g devs -G dataplatform -c "Dev User 01" dev01
useradd -m -s /bin/bash -g devs -G dataplatform -c "Dev User 02" dev02
useradd -m -s /bin/bash -g dbas -G dataplatform -c "DBA User 01" dba01
每条命令的 -g 指定各自的主组,-G 加上共享组。设置初始密码并强制首次登录修改:
bash复制echo "InitPass@2025" | chpasswd
chage -d 0 ops01
chage -d 0 dev01
chage -d 0 dev02
chage -d 0 dba01
这里我用了 chpasswd 而不是 passwd --stdin,因为前面提过后者在部分发行版不可用,chpasswd 的兼容性更好。
5.3 配置共享目录
创建共享目录并设置权限:
bash复制mkdir -p /opt/dataplatform
chown root:dataplatform /opt/dataplatform
chmod 2770 /opt/dataplatform
2770 这个数字值得展开说。第一位 2 是 setgid 位,它的作用是:目录下新建的文件和子目录,属组自动继承父目录的组,而不是创建者自己的主组。没有这一位,dev01 在目录里建文件,属组会是 devs,ops 组的人就没法访问了。第二位 7 是属主(root)完全权限,第三位 7 是属组(dataplatform)完全权限,第四位 0 是其他人无权限。
setgid 位用 chmod g+s 也能设置,等价于数字里的 2。我习惯直接写 2770,一行命令搞定。设置完后 ls -ld 看一下,目录权限应该显示 drwxrws---,中间的 s 就是 setgid 生效的标志。
5.4 验证权限是否生效
用新用户的角度验证一下。先切换到 dev01:
bash复制su - dev01
cd /opt/dataplatform
touch test_dev01.txt
ls -l
如果一切正常,test_dev01.txt 的属主是 dev01,属组应该是 dataplatform 而不是 devs,这就是 setgid 的效果。如果是 root 或其他人访问,可以用 sudo -u dba01 touch test_dba01.txt 模拟。
再验证隔离性:/opt/dataplatform 权限是 2770,其他用户没有权限,dev01 是无法访问 /home/dev02 的,因为家目录默认权限通常是 755 或 700,取决于发行版默认的 HOME_MODE 设置。这一步验证的是“用户间隔离”,和“组内共享”是同一个权限模型的两面。
这个案例覆盖了用户创建、密码策略、组管理、共享目录权限四个核心点。实际项目中,你通常还需要考虑 sudo 权限、ACL 扩展权限、配额等,但骨架就是这一套。
6. 常见问题与排查技巧速查
实际操作中,用户和组相关的报错和异常,很多都是重复出现的。我把高频问题整理成表,直接对照排查。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
useradd: user 'xx' already exists |
用户名冲突,或 /etc/passwd 里有残留(比如删除不彻底) |
检查 passwd 和 shadow 中是否都有该用户,残留则备份后清理 |
userdel: user 'xx' is currently used by process 1234 |
用户还有进程在运行 | 先 ps -u 用户名 找到进程,确认业务影响后结束进程再删 |
| 用户能登录但没家目录或不加载环境 | 创建时没加 -m,或家目录权限不对 |
手动 mkdir 并 chown 用户:组 目录,检查 /etc/skel 是否存在 |
su 切换用户时报 incorrect password |
密码确实错,或账号被锁定 | passwd -S 用户名 查看状态,被锁定则 usermod -U 解锁 |
| 用户明明在组里,却访问不了组目录 | 会话未重新登录,或目录缺 setgid/组权限不对 | 重新登录;用 id 用户名 确认组成员;检查目录权限和属组 |
| 新建文件属组不对 | 共享目录没设置 setgid,或用户在子目录建文件时子目录的 setgid 丢掉了 | chmod g+s 共享目录,并检查子目录是否继承(用 ls -ld 看 s 位) |
| 删除用户后文件显示“数字属主” | 文件属于已删除用户的 UID,passwd 里查不到 | find / -nouser -o -nogroup 找到后 chown 或删除 |
| 用户被踢出附加组后权限还在 | 旧会话没退出,进程保留旧组成员身份 | 让该用户退出所有会话或重启相关服务 |
passwd 不支持 --stdin |
Ubuntu/Debian 的 passwd 没有该参数 | 改用 chpasswd 或 chage 结合其他方式初始化 |
排查用户问题,我一般按三步走:先 id 用户名 看身份和组成员是否正常,再 ls -ld 看目标目录的权限和属主属组,最后 chage -l 用户名 看密码状态。这三个命令能覆盖八成问题。剩下两成,多半是 PAM、sudoers 或 SELinux 层面的,那些属于进阶范畴,今天先不展开。
再补充一个经验:不要在 /etc/passwd 和 /etc/group 上直接动手编辑。虽然这两个文件是纯文本,理论上可以手动改,但一旦语法写错,可能导致系统无法正常解析用户信息,最坏情况连登录都进不去。改用户和组,永远走 usermod、groupmod 这些封装好的命令。如果实在需要批量处理,也要先 cp /etc/passwd /etc/passwd.bak 备份一份。
我自己在维护服务器时,还有个习惯:建一个 /root/accounts.txt,记录每个账号的用途、创建日期、关联的组和到期策略。用户一多,光靠记忆根本撑不住。等到需要清理账号、盘点权限的时候,这份清单比任何命令都管用。
本地用户和组的管理,这部分内容操作本身不复杂,难点在于理解权限模型背后的设计逻辑。把这一个章节的机制和命令吃透,后面再接触 sudo 提权、ACL 权限、PAM 认证这些进阶内容,就有了扎实的地基。下篇我会继续拆解组管理的高级用法、批量创建账号脚本的写法,以及 sudoers 和常见安全加固项,到时候再接着聊。
