☰
Linux UID和GID详解:从概念到实操,彻底搞懂用户权限体系
2026/10/1 5:39:30 网站建设 项目流程

写这篇学习记录时,我刚在云服务器上折腾完一批用户权限,顺便把之前一直没彻底搞懂的几个基础概念重新过了一遍。UID和GID,中文就是用户ID和组ID。如果你和我一样,刚开始学Linux时对着ls -l输出里的“root root”发过呆,或者好奇为什么不直接用用户名而是用一串数字来标识身份,那这篇文章大概能帮你把这些疑问一次性理清楚。它适合所有刚接触Linux的初学者,也适合那些已经在用Linux但总觉得基础不扎实、准备面试或者想深入理解权限体系的朋友。

我会从概念、原理、实操到常见问题,按我自己的学习路径来记录,全程配上可以直接复制的命令和踩坑心得。这不是教科书式的罗列,而是我经过实际测试、带着思考写下来的经验总结。

1. UID和GID到底是什么:先搞清概念再动手

1.1 从文件的属主和属组说起

第一次接触ls -l时,我看到每行前面都有类似-rw-r--r--的权限串,后面还跟着两个名字。比如:

drwxr-xr-x 2 root root 4096 Jan 15 10:30 testdir

我当时有两个疑问:这里显示的“root root”到底是什么?第二个“root”是不是多余?后来才明白,第一个root是文件属主,也就是文件的主人;第二个root是文件属组,决定了这个文件所属的组。而内核在真正判断权限时,并不直接看“root”这个字符串,而是看root这个用户名背后对应的数字标识——这就是UID(用户ID)和GID(组ID)。

类比一下就很好懂:UID和GID就像身份证号,用户名和组名就像身份证上的名字。平时我们打交道时用名字更方便,但真正办理业务、核对身份时,系统认的还是那个唯一的编号。Linux里也是一样,内核只认数字,人名是给人类看的。

1.2 /etc/passwd和/etc/group里的奥秘

要理解UID和GID,必须打开两个文件:/etc/passwd和/etc/group。

/etc/passwd里每一行代表一个用户,典型的一行长这样:

zhangsan:x:1001:1001:张三:/home/zhangsan:/bin/bash

用冒号分成7个字段,我刚开始时总记不住顺序,后来总结成一个口诀:名字、密码占位、UID、GID、注释、家目录、登录Shell。

  • 第1个字段:用户名,比如zhangsan。
  • 第2个字段:以前存密码哈希,现在普遍放一个x占位,真正密码挪到了/etc/shadow,普通用户不可读。
  • 第3个字段:UID,用户的数字ID。
  • 第4个字段:GID,用户主组的数字ID。
  • 第5个字段:注释信息,一般填姓名或说明。
  • 第6个字段:家目录路径。
  • 第7个字段:登录Shell,也就是用户登录后使用的解释器。

再看/etc/group,每一行代表一个组:

dev:x:1002:zhangsan,lisi,wangwu

同样用冒号分隔:组名、组密码占位符、GID、组成员列表。这里有个容易忽略的点:一个用户创建时会被分配一个主组(primary group),同时还可以加入很多附加组(supplementary group)。文件权限判断时,主组和附加组都会被检查,但登录时的有效组通常是主组。这个细节在共享目录权限配置时特别关键,后面实操部分会再提。

2. 内核只认数字,不认名字:UID/GID为什么这么设计

2.1 用户名的存在只是给人看的

我一度觉得Linux这个设计很“反人类”:明明有更直观的用户名,为什么权限判断非要绕一层数字?后来接触了网络和嵌入式相关的场景才明白,数字ID在系统内部处理时效率高得多,而且不会因为改名或不同机器上用户名不同而产生歧义。

举例来说,你在两台服务器上各有一个用户,都叫test,但一台上UID是1001,另一台是2001。如果两台服务器通过NFS共享同一个目录,那么第一台服务器上的test用户写的文件,在第二台服务器上可能就变成另外一个用户的了。原因很简单:NFS协议在传递文件属主信息时,传的是UID/GID数字,不是用户名。也就是说,跨系统协同工作时,真正一致的不是名字而是数字ID。这也是为什么有经验的管理员在搭建多机环境时,会提前规划统一的UID/GID范围。

2.2 从文件权限到进程身份:UID如何生效

权限判断的过程可以简单概括为:当一个进程试图访问文件时,内核用进程的身份ID和文件属主、属组的ID进行比对,再根据比对结果决定应用哪一段rwx权限。

在Linux里,进程的身份比我们想得更复杂一点。一个运行中的进程至少有三种UID:

类型含义典型场景
Real UID进程实际由哪个用户启动记录“发起者”是谁
Effective UID进程当前以谁的权限运行决定真正拥有的权限
Saved UID被保留的旧EUID,以便程序在必要时切换回用于setuid程序的临时降权/恢复

大多数普通进程的这三种UID是相同的,但执行了带有setuid位的程序后会发生变化。最经典的例子是/usr/bin/passwd,你看它的权限时注意看属主权限部分的s:

ls -l /usr/bin/passwd -rwsr-xr-x 1 root root 59976 Feb 10 2023 /usr/bin/passwd

普通用户执行passwd修改自己的密码时,按道理只能访问/etc/shadow,而这个文件是只有root能读写的。可实际上普通用户能正常改密码,关键就在于passwd程序带了setuid位,它会让进程的Effective UID临时变成root,从而获得读取/etc/shadow的权限。这就是“程序权限替身”的机制,理解它对排查权限问题非常有帮助。

2.3 普通用户、系统用户和root的边界

Linux里UID并不总是分配给真实用户,有很大一片保留给系统服务。按照常见发行版的约定:

  • UID 0:特权用户root,拥有整个系统最高权限。
  • UID 1~999:系统保留账号,供守护进程或服务使用。这类账号通常没有登录Shell,也没有家目录,比如bin、daemon、mysql等。
  • UID 1000~60000:普通用户,用于日常登录和业务操作。

不同发行版的具体范围略有差异,但大方向一致。比如Debian系曾经的普通用户起点是1000,而一些老系统可能从500开始。了解这个范围有助于你在创建用户时合理地手动指定UID,避免和系统用户撞车。

3. 实操环节:查看、创建、修改一套带走

3.1 查看用户的UID/GID

排查权限问题时,第一时间肯定是确认当前用户和文件属主。我最常用的查看命令是id:

id uid=1000(test) gid=1000(test) groups=1000(test),4(adm),27(sudo)

输出第一行显示当前用户的UID和主组GID,groups部分显示所有附加组。如果只想看某个用户的,直接后面跟用户名:

id root uid=0(root) gid=0(root) groups=0(root)

也可以用getent命令来查询用户数据库,它在某些场景下比直接cat /etc/passwd更可靠,因为它会用到NSS配置的完整用户来源(本地文件、LDAP等)。比如:

getent passwd test test:x:1000:1000::/home/test:/bin/bash

想快速列出所有用户的UID和GID,可以配合awk处理/etc/passwd:

awk -F: '{print $1, $3, $4}' /etc/passwd

想看文件真实的数字属主,用ls -n,它会用数字UID/GID取代用户名显示;或者用stat:

ls -n myfile.txt stat -c "%u %g" myfile.txt

3.2 创建用户时如何规划UID和GID

刚开始我创建用户特别随意,后来发现不规范的用户名和ID会让权限管理变得很痛苦。推荐的做法是创建之前先规划好ID段,比如业务用户统一从10000开始,服务账号继续使用系统保留段下的自定义范围。

创建用户的命令是useradd,常用参数如下:

useradd -u 10001 -g dev -G docker,wheel -m -d /home/zhangsan -s /bin/bash zhangsan
  • -u 10001:手动指定UID。
  • -g dev:指定主组为dev组(该组必须已存在)。
  • -G docker,wheel:指定附加组,多个组用逗号分隔。
  • -m:同时创建家目录。
  • -d /home/zhangsan:指定家目录路径。
  • -s /bin/bash:指定登录Shell。

这里有个特别容易踩的坑:不同发行版的useradd默认行为不一样。在CentOS/RHEL上,useradd默认会创建家目录并生成对应的邮件目录;但在Ubuntu/Debian上,直接用useradd创建用户往往不会自动创建家目录,也不会要求设置密码。很多新手在Ubuntu上创建用户后,切过去发现连home目录都没有,就是这个原因。如果是Ubuntu环境,我更推荐用adduser这个友好交互命令,它会引导你设置密码并创建家目录;如果你坚持要用useradd,记得带上-m参数。

3.3 修改已有用户的UID/GID

如果一个用户创建之后ID不合适,可以用usermod修改。修改UID:

usermod -u 10002 zhangsan

修改主组:

usermod -g dev zhangsan

添加附加组:

usermod -aG docker zhangsan

注意这个-aG里的-a一定要带上,它表示追加(append),如果不加,用户会被移出原来的所有附加组,只保留新指定的组。这个误操作我帮同事排查过一次,他本来想把用户加到docker组,结果用户直接被踢出了sudo组,导致机器上没法执行sudo,非常耽误事。

修改UID有个风险要特别提醒:usermod -u只修改了/etc/passwd里的UID记录,不会自动修改这个用户原来拥有的文件属主。也就是说,改ID之后,用户家目录或业务目录里的文件还会显示成旧的数字ID。正确的操作顺序是,先记录旧UID下有哪些文件,修改后再用chown把它们统一改过来。

批量改属主的命令可以这样写,先找出所有属于旧UID的文件再执行chown:

find /home/zhangsan -user 10001 -exec chown 10002:dev {} \;

执行前先不加-exec只跑一遍find,确认列出的文件都在预期范围内,再执行真正的修改。尤其是服务器上有大量业务数据的场景,这个习惯能避免改错目录权限。

3.4 修改文件属主和属组

日常用得最多的应该就是chown和chgrp这两个命令了。基础用法如下:

chown zhangsan /data/test.txt chown zhangsan:dev /data/test.txt chown :dev /data/test.txt

第一种只改属主,第二种同时改属主和属组,第三种只改属组。实际修改目录时,遇到目录里还有大量子文件,加上-R递归执行:

chown -R zhangsan:dev /data/project

chgrp则专门用来改文件的属组,等价于chown :组名:

chgrp -R dev /data/project

有一点需要说明:普通用户只能修改自己拥有文件的属主和属组吗?不对,实际上普通用户几乎不能更改文件的属主,这个操作会直接拒绝,因为把文件“送”给别人是一个高风险操作。只有root或sudo权限才能执行chown。所以当你在权限受限的目录里执行chown报错时,先检查一下当前用户。

4. 特殊UID与权限位:很多人忽略的重要细节

4.1 uid 0、nobody和系统保留账号

uid 0是root,可以理解为系统的“最高权限身份证”。无论用户名改成了什么,只要UID是0,它就拥有root级权限。所以安全加固时,要特别检查是否有多余的UID 0账号:

awk -F: '$3 == 0 {print $1, $3}' /etc/passwd

正常情况下这个命令只应该输出一行root 0。如果看到其他账号也对应UID 0,说明系统很可能被留下了后门账号,需要立即处理。

另一个常见的特殊ID是nobody,通常在/etc/passwd里对应UID 65534或65535,GID同理。它代表“没有任何权限的普通用户”,通常用于Nginx、某些容器进程等以低权限运行的服务。理解nobody的定位,能帮你避免把服务账号直接配成root运行,降低被攻击后的影响面。

4.2 setuid、setgid和sticky bit

讲到UID和GID,就绕不开权限位上的三个特殊标志:setuid(suid)、setgid(sgid)和sticky bit。它们和普通rwx权限一起存储在权限位上,平时显示在可执行位的x位置:

  • 属主权限里出现s:setuid,执行时进程EUID变成文件属主的UID。
  • 属组权限里出现s:setgid,执行时进程EGID变成文件属组的GID,同时如果对目录设置setgid,目录内新建的文件会自动继承该目录的属组。
  • 其他用户权限里出现t:sticky bit,常见于/tmp这种共享目录,只有文件属主(或root)可以删除自己的文件,避免用户误删他人临时文件。

用chmod可以给文件或目录设置这三个位:

chmod u+s /path/to/file # 设置setuid chmod g+s /path/to/dir # 设置setgid chmod +t /tmp/shared_dir # 设置sticky bit

检查系统里有哪些setuid程序,可以用:

find / -perm -4000 -type f 2>/dev/null

这个命令应该养成定期检查的习惯,因为setuid位一旦配合程序漏洞,很容易被利用来提权。如果发现某些非常规程序带suid位,就要格外警惕。

4.3 跨服务器时UID不一致的坑

前面说过NFS的场景,再举一个具体例子:公司有两台服务器,同一批开发者的账号在A机器上UID是1001,在B机器上忘了统一,变成了1002。当开发者把A机器上产生的文件传到B机器后,B机器上所有者和属组显示不再是他的名字,而是一串数字。更麻烦的是,如果两边共享同一个存储,权限判断会完全错乱,本来有权限的目录突然没权限了。

解决办法是提前规划:要么在每台机器上建立相同UID/GID的账号,要么使用集中认证方式(比如LDAP),要么在文件传输后统一重启一遍属主。没有绝对完美的方案,但最基础的一点是:在项目初始规划时,就把UID/GID纳入一致性方案里。

5. 新手常见问题与排查技巧实录

5.1 用户删了文件却显示一串数字

现象:用ls -l查看文件时,属主和属组变成了一串数字,比如1001。原因很简单:文件属主的UID在/etc/passwd里已经查不到对应的用户名了,通常是因为用户被userdel删除,但文件还在。

解决办法是重新创建同一个UID的用户,或者用chown把文件归属调整到现有用户:

chown test:test /path/to/file

避免这个问题的最好习惯是删除用户之前先找出它名下的文件:

find / -user zhangsan -ls

确认这些文件是否有保留价值,再决定保留还是删除。我在一台测试机上删过一个业务账号,结果漏掉了/var/log下的应用日志,后续排查问题时看日志全是数字,非常痛苦。

5.2 新建用户没有家目录

现象:useradd test之后,su - test进入用户环境,发现当前目录是/,而且/home/test不存在。

原因通常是发行版默认行为不同,或者创建命令里漏了-m参数,也可能家目录被其他文件占用了。解决方法很简单:

mkdir -p /home/test chown test:test /home/test usermod -d /home/test test

如果是Debian/Ubuntu系,强烈建议直接用adduser交互式创建用户,它会自动创建家目录、设置密码,一次性处理好。

5.3 修改UID后文件权限“失效”

现象:给用户执行了usermod -u 10002 zhangsan,登录后发现自己的家目录进不去了,或者文件权限全乱了。

原因就是第3.3节提到的问题:文件属主还是旧UID,和用户新UID对不上,权限判断自然失败。解决套路是:

find /home/zhangsan -user 10001 -exec chown 10002:10002 {} \;

如果你修改的是业务目录,记得也要扫描业务路径,别只改家目录。另外提醒一句,修改UID前最好通知相关同事,避免在线服务正在写入文件时你改了属主,导致服务瞬间没有写权限。

5.4 常用排查命令速查

我把自己平时用到的高频命令整理成了一个小表,方便随时查阅:

需求命令
查看当前用户ID信息id
查看指定用户ID信息id username
查看passwd文件全部用户cat /etc/passwd或getent passwd
查看group文件全部组cat /etc/group或getent group
查找UID为0的特殊账号awk -F: '$3==0{print $1,$3}' /etc/passwd
查看文件数字属主ls -n或stat -c "%u %g" file
修改文件属主/属组chown 用户:组 文件
递归修改目录属主/属组chown -R 用户:组 目录
检查系统setuid程序find / -perm -4000 -type f 2>/dev/null

这套命令配合/var/log/secure或/var/log/auth.log里的认证日志,基本能覆盖绝大多数权限相关问题的排查。

踩过几次坑之后,我的体会是:UID和GID这套体系虽然概念简单,但真正理解它需要把“用户—组—文件—进程”这条链路串起来。很多权限问题看着复杂,拆到最后无非就是以下三件套:当前进程以什么身份在跑、目标文件的属主和属组是什么、文件的所属组和用户是否匹配访问规则。把这三个问题回答清楚,权限相关的故障就解决了一大半。下一篇我打算接着写权限位的三位八进制数值和umask的计算逻辑,如果你也在这个问题上卡过,欢迎留言交流。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询