我第一次 cat /etc/passwd 的时候,看到一整屏这种东西:
rainbow:x:1000:1000:RAINBOW:/home/rainbow:/bin/bash
七个字段用冒号隔开,中间还有个孤零零的 x。当时我完全不知道这些是干什么的,也不知道为什么密码不在这里面------不是叫 passwd 吗?
后来做运维,真正让我把这套东西搞明白的,是两件小事:
-
用
useradd给同事建了个账号,建成功了,但他登不进去; -
给一个用户加个组,顺手敲了
usermod -G,结果把他的sudo权限冲没了。
这两件事的答案,都在这三个文件里。而且它们是一套三件套,分开看永远看不懂:
| 文件 | 管什么 | 谁能看 |
|---|---|---|
/etc/passwd |
身份:谁能登录、UID 是多少、家在哪、用哪个 shell | 所有人可读 |
/etc/shadow |
密码:密码的哈希、有效期 | 只有 root |
/etc/group |
归属:组里有哪些人 | 所有人可读 |
这篇文章就按这三块讲。
〇、先花两分钟:一个「用户」其实是三件东西
把「用户」想成公司里的一名员工:
-
他得有工号(UID)------系统认号不认名;
-
他得有门禁卡密码(存在 shadow 里);
-
他得属于某个部门(组)------部门决定了能进哪些公共区域。
whoami 之所以能打出你的名字,是因为系统拿你的 UID 去 /etc/passwd 里查了一遍:编号 1000 对应名字 rainbow。
因此有个反常识的结论先记住:Linux 内核根本不知道「用户名」这回事,它只认 UID。 用户名只是给人看的标签。这就解释了后面几个坑------比如删了用户又新建同名的,文件属主会莫名变成数字。

一、/etc/passwd:七个字段,一行一个人
先看几行真东西(我把系统账号和真实用户混在一起,正好对照):
$ grep -E '^(root|daemon|www-data|systemd-network|rainbow):' /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin
rainbow:x:1000:1000:RAINBOW:/home/rainbow:/bin/bash
七个字段,逐个拆:
| # | 字段 | root 那行 | rainbow 那行 | 说明 |
|---|---|---|---|---|
| 1 | 用户名 | root |
rainbow |
给人看的名字 |
| 2 | 密码占位 | x |
x |
只是占位符,真密码在 shadow 里 |
| 3 | UID | 0 |
1000 |
系统真正认的编号 |
| 4 | GID | 0 |
1000 |
主组编号 |
| 5 | 备注 | root |
RAINBOW |
全名/电话等,逗号分段,可空 |
| 6 | 家目录 | /root |
/home/rainbow |
登录后落在哪 |
| 7 | 登录 shell | /bin/bash |
/bin/bash |
登录后跑哪个程序 |
三处最值得停下来说:
① 第二个字段为什么是 x?
因为早期 Unix 的密码哈希就明晃晃写在这一列 ,任何用户 cat /etc/passwd 就能把全机器的密码哈希拷走离线爆破。后来做了「影子化」改造:把哈希搬到只有 root 能读的 /etc/shadow,原位置留一个 x 占坑。
所以 passwd 这个名字是有历史包袱的 ------它现在只是「用户清单」,不是「密码清单」。这条历史的痕迹,就是那个 x。
② 最后一列的 /usr/sbin/nologin 是什么?
看 daemon 那行------它是个系统账号,用来跑服务,不允许人登录 。如果有人拿它的名字去登录,系统会执行 /usr/sbin/nologin 这个程序,它就干一件事:打印一句提示然后把你踢出去。
常见的几种「禁止登录」写法:
| 写法 | 效果 |
|---|---|
/usr/sbin/nologin |
拒绝登录,并打印一句「This account is currently not available.」 |
/bin/false |
沉默拒绝,什么都不打印 |
/bin/bash |
正常登录(普通用户和 root 都是这个) |
看到这里你就明白为什么安全建议里总说「服务账号必须设成 nologin」------它没有 shell,就算密码泄露了也进不来。
③ UID 的区间是有约定的(虽然系统不强制):
| 区间 | 用途 |
|---|---|
0 |
root |
1~999 |
系统账号(服务、守护进程),Ubuntu 会自动分配 |
1000 起 |
普通用户(useradd 默认从这里开始发号) |
这也是为什么你新建的用户总是 1000、1001 这样往上走。
二、/etc/shadow:密码在这儿,所以它必须是 640
看一眼权限,答案就出来一半:
$ ls -l /etc/passwd /etc/shadow /etc/group
-rw-r--r-- 1 root root 2775 Sep 16 15:20 /etc/passwd
-rw-r----- 1 root shadow 1385 Sep 18 19:07 /etc/shadow
-rw-r--r-- 1 root root 1161 Sep 16 16:00 /etc/group
怎么读:三个文件权限完全不同------
-
/etc/passwd、/etc/group是644(人人可读); -
/etc/shadow是640,属组是shadow------普通用户连读都读不到。
这就是「密码哈希为什么安全」的全部秘密:不是哈希不可破,而是它根本不让你拿走。 一台被普通用户登录的机器,攻击者拿不到影子文件,就没法离线爆破。
(所以任何让你 chmod 644 /etc/shadow 的教程,不管理由多好听,直接关掉。)
root 才能看的内容长这样:
$ sudo grep -E '^(root|rainbow):' /etc/shadow
root:$y$j9T$ZL4.b...:20714:0:99999:7:::
rainbow:$6$G3UddCIG8...:20712:0:99999:7:::
怎么读:冒号分段,第一段是用户名,第二段是密码哈希,后面几段是密码的「有效期管理」。逐段对照:
| # | 含义 | root |
rainbow |
|---|---|---|---|
| 1 | 用户名 | root |
rainbow |
| 2 | 密码哈希 | $y$j9T$...(yescrypt) |
$6$G3Ud...(SHA-512) |
| 3 | 上次改密日期(1970 年起的天数) | 20714 | 20712 |
| 4 | 最短使用天数(0=随时可改) | 0 | 0 |
| 5 | 最长使用天数(99999=永不过期) | 99999 | 99999 |
| 6 | 过期前警告天数 | 7 | 7 |
| 7 | 过期后宽限天数 | 空 | 空 |
| 8 | 账号失效日期 | 空 | 空 |
第二段是重点,加密串开头的一个字符就能说明账号状态:
| 第二段长什么样 | 什么意思 |
|---|---|
$y$... / $6$... |
有可用密码 ($y$ 是 yescrypt,$6$ 是 SHA-512) |
! 或 !! |
账号被锁,没有可用密码 |
* |
同样表示不可用密码(系统账号常见) |
| 空 | 空密码,可以直接登录,极度危险 |
回到上一篇文章(C1)那个谜题------为什么 su root 会「鉴定失败」? 看上表第 2 段就能分清是哪种情况:
-
字段是
!→ root 被锁,压根没有密码可比对(Ubuntu 出厂状态); -
字段是
$y$或$6$→ root 有密码 ,此时失败只说明密码敲错了。
顺带留个心眼:我这台机器上
root是$y$j9T$...、rainbow是$6$...------两个账号用的哈希算法都不一样 。$y$(yescrypt)是现在设密码的默认算法,$6$(SHA-512)多见于早期创建的账号。同一台机器上并存两套算法很正常,看开头几个字符就知道谁是谁。
顺便记住一条排查经验 :账号「登不进去」时,passwd -S 用户名 看状态、sudo grep ^用户名: /etc/shadow 看第二个字段,两条命令就能区分「密码错」「账号锁了」「密码过期了」。
$ passwd -S rainbow
rainbow P 2026-09-16 0 99999 7 -1
怎么读 :第二位 P = 有密码;后面那串 2026-09-16 0 99999 7 -1 对应 shadow 的第 3~8 段------同一个信息,两处都能看。
三、/etc/group:组里有哪些人
$ grep -E '^(rainbow|sudo|adm):' /etc/group
adm:x:4:syslog,rainbow
sudo:x:27:rainbow
rainbow:x:1000:
四个字段,比 passwd 简单得多:
| # | 字段 | 含义 |
|---|---|---|
| 1 | 组名 | sudo |
| 2 | 密码占位 | x(组密码早就不用了,忽略即可) |
| 3 | GID | 组编号 |
| 4 | 成员列表 | 属于这个组的附加成员 |
怎么读这里有讲究 ------注意 rainbow:x:1000: 最后是空的。
为什么?因为 rainbow 是主组 成员,而主组关系记在 /etc/passwd 的第 4 个字段(GID)里,不重复写在这里 。而 sudo:x:27:rainbow 里写出了 rainbow,说明 rainbow 是 sudo 组的附加成员。
一句话:主组看 passwd,附加组看 group。
这也解释了一个常见困惑------「我明明在 sudo 组里,/etc/passwd 里怎么没有?」
再验证一下你是谁、在哪些组:
$ id
uid=1000(rainbow) gid=1000(rainbow) groups=1000(rainbow),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),111(lpadmin),114(lxd)
groups= 那一串,就是「主组 + 所有附加组」的合并结果。id 是这三个文件的最终汇总视图------你压根不用自己去拼。
四、useradd 和 adduser:一个什么都不做,一个什么都替你做
这是新手最容易被绊的一跤:两条命令名字只差两个字母,行为差出一整个流程。
adduser 是 Ubuntu/Debian 提供的友好封装 ;useradd 是几乎所有发行版都有的底层命令。区别在于:
| 行为 | adduser |
useradd |
|---|---|---|
| 是否交互 | 会问你密码、全名等 | 全静默,可加参数 |
| 建家目录 | ✔ 自动 | ✘ 默认不建 |
复制 /etc/skel 骨架文件 |
✔ | ✘ |
| 设置密码 | 让你当场输入 | ✘ 不设,账号是锁的 |
| 默认 shell | /bin/bash |
看 /etc/default/useradd,常常是 /bin/sh |
| 日志 | 写在 /var/log/auth.log |
同 |
这就解释了那个经典场景 :sudo useradd zhangsan 敲完,/etc/passwd 里多了一行,看着很成功------但用户登录时可能遇到两类问题:
-
登录落在
/目录(因为家目录根本没建,或建了但没设对); -
如果没设密码,账号是锁的------连密码都没得输。
所以 useradd 要用的话,参数得给全:
sudo useradd -m -s /bin/bash zhangsan # -m 建家目录,-s 指定登录 shell
sudo passwd zhangsan # 手动设密码(这步不能忘)
怎么读 :-m 是 make home,-s 是 shell。少了 -m,家目录就得你自己建;少了 passwd,账号就是锁的。
日常在 Ubuntu 上,直接用它就行:
sudo adduser zhangsan
它会交互着问密码、全名,自动建好 /home/zhangsan、从 /etc/skel 复制骨架文件(.bashrc、.profile 这些)、并设好 shell。
这两处真机看一眼:
$ ls -A /etc/skel
.bash_logout .bashrc .profile
$ grep SHELL /etc/default/useradd
SHELL=/bin/sh
怎么读 :/etc/skel 里就这三个文件,adduser 建号时原样复制到家目录;而 useradd 的默认 shell 写死在 /etc/default/useradd 里,是 /bin/sh 不是 /bin/bash ------这就是为什么 useradd 不带 -s 建出来的账号,登进去没有你熟悉的 bash 提示符。
结论:Ubuntu 上用 adduser;useradd 留给脚本批量建号(要把 -m -s 和 passwd 都补全)。
五、改和删:四条命令 + 一个必踩的坑
| 需求 | 命令 |
|---|---|
| 改密码 | sudo passwd 用户名 |
| 加入一个附加组 | sudo usermod -aG 组名 用户名 |
| 改登录 shell | sudo usermod -s /bin/bash 用户名 |
| 改名 / 改家目录 | sudo usermod -l 新名 -d /home/新名 -m 旧名 |
| 删用户(连家目录) | sudo userdel -r 用户名 |
| 只停用、不删除 | sudo usermod -L 用户名(锁定)→ -U 解锁 |
那个必踩的坑在第二行 :usermod -G 组名 用户名 少了 -a,它会把用户的附加组整个替换掉。
想想就会后怕:你只想给用户加个 docker 组,敲了 usermod -G docker rainbow,结果 rainbow 的 sudo、adm、cdrom 那些附加组全没了------用户当场失去提权能力,而你在终端里也快没辙了(如果你就是那个用户)。
记法:加组永远写 -aG,一个字母都不能少。
六、四个坑
坑 1:用 useradd 建完就以为完事了。 它默认不建家目录、不设密码、shell 可能是 /bin/sh。要么用 adduser,要么把 -m -s /bin/bash 和 passwd 补齐。
坑 2:usermod -G 忘了 -a,把附加组冲掉。 见上一节。
坑 3:手动改 UID 或用户名,导致文件属主变成一串数字。
因为内核只认 UID:你把用户的 UID 从 1000 改成 2000,他旧文件的所有者记录里还是 1000 ,而 1000 这个号现在没有对应用户名了,ls -l 就显示成一串数字。
这就是前面那句「Linux 只认编号」的真实代价。真要改,得用 usermod -u,并且手动把家目录下所有文件的属主一起改过来:
sudo usermod -u 2000 rainbow
sudo find /home/rainbow -user 1000 -exec chown -R rainbow {} + # 把残留的旧号改掉
坑 4:userdel 不删家目录,留下一堆「数字属主」的垃圾。
sudo userdel zhangsan 只删账号,/home/zhangsan 还在,而且它的属主这时已经是个不存在的 UID。要一起删就加 -r ;如果想留数据,就先 chown 给新用户再删账号。
(另外提一句:改 /etc/passwd、/etc/shadow 这类文件不要直接拿编辑器硬改,用 sudo vipw / sudo vipw -s------它们会加锁并检查格式,避免两个管理员同时改把文件写坏。)
速查表(文末收藏版)
我在哪些组 → id
看用户清单 → cat /etc/passwd (7 段:名:密码x:UID:GID:备注:家目录:shell)
看密码哈希 → sudo grep ^用户名: /etc/shadow (第 2 段:$y$有密码 / ! 锁 / * 不可用 / 空=无密码)
看组与成员 → cat /etc/group (第 4 段是附加成员,主组在 passwd 第 4 段)
账号状态 → passwd -S 用户名 (L=锁定,P=有密码,NP=无密码)
← 查 root 要加 sudo;shadow 第 2 段同理(! 锁 / $y$·$6$ 有密码)
建用户(Ubuntu 推荐) → sudo adduser 用户名
建用户(脚本用) → sudo useradd -m -s /bin/bash 用户名 && sudo passwd 用户名
加附加组 → sudo usermod -aG 组名 用户名 (-a 不能省!)
删用户连带家目录 → sudo userdel -r 用户名
停用而不删除 → sudo usermod -L 用户名
安全编辑三个文件 → sudo vipw / sudo vipw -s
禁用登录 shell → /usr/sbin/nologin(服务账号必设)