前两天有同事在群里发了个截图:一台服务器上某个目录里几乎所有文件,权限列里显示的属主不是人名,而是一串数字。他嘀咕了一句“这系统是不是坏了”。我在屏幕前笑出了声——这正是Linux用户与组管理里最有代表性的一个现象:系统里已经找不到某个UID对应的账号了,但文件还在。熟悉这套机制的人一眼就懂,新手却容易在这里绕晕。
Linux用户与组管理听起来像是最基础、最无聊的一块内容,但真正到了生产环境,它几乎决定了你后面所有踩坑的方向:文件权限不对、sudo授权失效、删除账号清不干净、新建账号莫名继承了别人的旧文件……这些问题的根子都在用户和组的底层设计上。这篇文章我会从内核视角、配置文件、常用命令、实战组队,再到常见的“翻车现场”,把这块掰开揉碎讲清楚。无论你是刚入门的新手,还是被权限问题折磨过的运维,这篇都值得仔细看一遍。
1. 先搞清楚底层逻辑:权限判断只认UID,不认“用户名”
1.1 用户名是给人看的映射,内核只认数字ID
很多人第一次接触Linux权限时,会把“用户”和“用户名”画等号,觉得系统里存在一个叫“张三”的东西,文件属主写着张三,那张三就是张三。真实情况完全不是这样。
Linux内核在做权限判断的时候,从不关心用户名是什么,它只认两个数字:UID(用户ID)和GID(组ID)。你在终端里看到的root、zhangsan、mysql这些名字,只是/etc/passwd文件提供的一张“数字到名字”的对照表。当你执行ls -l的时候,系统拿到文件inode里记录的UID,再去/etc/passwd里反查名字,查到就显示名字,查不到就显示那一串数字。
这也能解释很多奇怪现象。比如你看到文件属主显示1001,不是系统“坏了”,而是文件的UID=1001,但当前系统的/etc/passwd里已经没有这个UID对应的条目了。用户被删了,文件还在,于是它就成了“无主孤魂”,只能以数字形式展示。理解这点,后面所有用户管理操作都会通透很多。
1.2 组不是“通讯录”,是权限共享的通道
组(Group)的概念很多人也有误解,以为它类似企业里的部门标签,用来归档“哪个用户属于哪个团队”。如果只是这样,那组就没多大用处了。组在权限体系里的真实角色是:一组人可以共享某份权限的集合。
一个文件有三种权限位:属主(owner)、属组(group)、其他(other)。其中“属组”位控制的是:只要某个用户的GID或者附加组里包含文件的属组,就按属组权限来对待。所以组本质上是一条权限通道——你加入docker组,就能用docker命令;加入sudo组,就能用sudo提权;加入某个项目的组,就能访问项目共享目录。组的作用不是“分类”,而是“授权”。
一个用户有且只有一个主组(primary group,也叫初始组),但可以同时加入很多附加组(supplementary groups)。文件默认属组通常是创建者主组,这一点在后面设计共享目录时要特别注意——大部分人做团队协作权限时踩坑,都是因为没搞懂主组和附加组的区别。
2. 四个核心配置文件:Linux账号体系的“数据库”
2.1 /etc/passwd的七个字段,值得逐列记住
很多教程喜欢让你“知道有这个文件就行”,但我觉得,做用户管理,这四个文件的结构必须烂熟于心。因为所谓命令,本质上就是在帮你改这些文件;你明白它们在改什么,遇到诡异问题才能排查。
/etc/passwd每一行代表一个用户,用冒号分成七列,格式固定:
zhangsan:x:1000:1000:张三:/home/zhangsan:/bin/bash各列含义如下:
| 字段 | 示例 | 含义 |
|---|---|---|
| 用户名 | zhangsan | 登录名,给人看的 |
| 密码占位 | x | 真正的密码哈希已经挪去/etc/shadow,这里永远是x或* |
| UID | 1000 | 用户ID,内核唯一识别的凭证 |
| GID | 1000 | 主组ID,默认组编号 |
| 注释 | 张三 | 一般是全名或描述,随意填 |
| 家目录 | /home/zhangsan | 登录后的起始目录 |
| 登录shell | /bin/bash | 登录后执行的shell,/sbin/nologin表示禁止登录 |
七列里最容易忽略的是登录shell。你创建一个服务账号,如果给了/bin/bash,攻击者拿到这个账号就能得到完整shell;正确做法是给/sbin/nologin或者/usr/sbin/nologin,让该账号无法交互登录。很多人创建MySQL、nginx账号时懒得指定,结果系统里多了一堆能登录的“服务账号”,这是非常不安全的入口。
2.2 /etc/shadow:密码状态的真正存放处
/etccc/passwd里那个x是历史遗留:早期密码哈希直接存在passwd里,但passwd是全局可读的,等于把哈希拱手送人。后来引入了/etc/shadow,权限600,只有root和shadow组能读,密码哈希才真正安全。
/etc/shadow也不是只存一个“加密后的密码”那么简单,它同样有九个冒号字段:
zhangsan:$6$xxxx:$L:0:99999:7:::L核心几个字段:加密哈希、最后一次修改密码的时间(单位是1970年1月1日到现在的天数)、最小修改间隔天数、密码最长有效期、到期前告警天数、宽限期、账号失效日期。一个生产环境里你要做“密码90天强制改密、过期前7天提醒、过期后3天强制失效”,就是靠这些字段实现的,手动改shadow太累,用chage命令最舒服:
chage -M 90 -m 0 -W 7 -I 3 zhangsan我见过不少团队在审计时被要求“加强密码策略”,结果只在/etc/login.defs里改了个PASS_MAX_DAYS,却没对存量用户执行chage,审计时一查,用户密码根本没过期。这里提醒一句:/etc/login.defs只对新建用户生效,存量账户必须用chage单独处理。
2.3 /etc/group与/etc/gshadow:组的双重结构
组的配置同样拆成两个文件:/etc/group存组的基本信息和成员列表,/etc/gshadow存组密码和组管理员。组密码这个功能现在基本没人用了(相当于给组加口令,用户要输入密码才能临时加入这个组),但格式要懂。
/etc/group每行四个字段:
dev:x:1002:zhangsan,lisi组名、密码占位x、GID、成员列表。成员列表里不包含把该组设为主组的用户——主组关系写在/etc/passwd第4列的GID上。也就是说,lisi的passwd里GID是1002,那么他和dev组的关系并不需要出现在/etc/group的成员列表里。这个细节很多人没注意,会导致你“明明把用户加入了dev组,但member list里看不到他”,于是怀疑命令没生效。
操作上建议优先使用groupadd和gpasswd,而不是直接改文件。但必须清楚:这些命令本质是在改这四个文件,理解了文件结构,你能在紧急时刻手动作出正确修改,而不是等命令报错后抓瞎。
3. 用户管理实操:创建、修改、锁定与删除
3.1 useradd的默认行为,坑过的人才知道要用参数
大部分发行版里,裸跑一条useradd zhangsan,你得到的配置未必是你想要的。有些机器上它会按/etc/default/useradd和/etc/login.defs的默认值创建用户,比如家目录策略、默认shell、默认UID范围,不同发行版差异很大。最稳妥的做法是显式指定关键参数,别赌默认行为。
我在生产环境创建普通用户的习惯写法:
useradd -u 1005 -g dev -G docker,sudo -s /bin/bash -d /home/zhangsan -m -c "张三-后端开发" zhangsan- -u 1005:指定UID,手动规划UID段,后面审计和回收都方便
- -g dev:指定主组为dev组,而不是自动建同名私有组
- -G docker,sudo:追加附加组,这样该用户能使用docker和sudo
- -s /bin/bash:指定登录shell
- -d /home/zhangsan:指定家目录路径
- -m:如果家目录不存在就创建
- -c "描述":写入passwd注释字段
创建服务账号则用另一套逻辑:
useradd -r -s /sbin/nologin -M redis-r表示创建系统用户(UID落在系统范围内),-M表示不创建家目录,-s给nologin。服务账号不需要登录,不需要家目录,给了才是隐患。
创建完还要设置密码,否则账号处于无法登录状态:
passwd zhangsan注意:新用户不等于弱口令账号,初始密码必须要求对方登录后立即修改,这个习惯配合chage -d 0使用很有效:
chage -d 0 zhangsan这样强制用户在第一次登录时必须修改密码,不然后续一切操作都做不了。
3.2 usermod:改账号名、改家目录、加组、锁定,一次说清
usermod是修改已存在用户的主力命令,但它的很多参数存在“只改数据库、不改实际文件”的问题,这也是使用时的最大陷阱。
改名是最典型的例子:
usermod -l lisi zhangsan这只会把/etc/passwd里的用户名从zhangsan改成lisi,家目录路径还是/home/zhangsan,家目录的属主也还挂在zhangsan的UID上。很多新手改名后发现用户登录进去还在旧目录,一脸懵。正确做法是配套两条命令一起用:
usermod -l lisi -d /home/lisi -m zhangsan-l改用户名,-d指定新家目录,-m把原家目录内容迁移到新目录并修改inode属主。三条不敢说全做到,但至少单独改名一定给你留一半的问题。
加组和移除组的操作同样有讲究。最常见的错误是执行了usermod -G docker zhangsan,结果这个用户的附加组被“覆盖”成了只有一个docker组,原先把用户加入的其他组全部被移除。正确姿势是加-a,表示追加:
usermod -aG docker zhangsan如果你确实要清掉某个用户的某些附加组,才需要用不带-a的-G列出最终想要的全部组。我的经验是:不加-a的usermod -G,只应该在修改前先用id命令确认当前完整组列表,再动手,否则就是一场小事故。
锁定与解锁也是很实用的能力。临时禁止某个账号登录,不用删,直接锁定:
usermod -L zhangsan usermod -U zhangsan锁定本质是在/etc/shadow的密码哈希前加一个“!”,让任何密码都验证不过。要注意的是锁定不影响已登录的会话,真正要立即踢下线,还得配合其他手段。
3.3 userdel删除用户,为什么总是“残留”
删除用户的命令是userdel,参数就一个-r最关键:加-r会同时删除用户的家目录和mail spool,不加则只删除passwd和shadow里的账号记录,家目录原封不动留给你手动清。
生产环境里我建议不要贪图userdel -r这把默认的“全家桶”。原因是:-r只会删家目录和邮件池,用户在其他目录下创建的文件、crontab任务、systemd服务、自己目录外的临时文件,它一概不管。直接删完,系统里就散落着大量“无主文件”——属主显示数字或已删除的用户名残留。
我的标准删除流程是:
# 1. 先锁定,避免删除过程中用户还在登录制造新文件 usermod -L zhangsan # 2. 查看基本信息 id zhangsan # 3. 清理定时任务和运行中的进程 crontab -u zhangsan -l crontab -u zhangsan -r pgrep -u zhangsan # 4. 确认上面都处理完,再删除 userdel -r zhangsan # 5. 手动找剩余文件 find / -user zhangsan 2>/dev/nullfind那一步经常能发现意外惊喜:比如某个应用的日志目录、某个NFS挂载目录下的文件。这些都要一一处理,要么chown给接盘账号,要么备份后清掉。删除用户是个“确认三遍再动手”的活儿,没有之一。
4. 组管理和sudo授权:多人协作时的权限设计
4.1 groupadd与gpasswd:管理组员最舒服的姿势
创建组很简单:
groupadd -g 1002 dev但组员管理我几乎不用usermod去操作,而是推荐gpasswd。gpasswd专门干“往组里加人踢人”这件事,语义比usermod清晰得多:
gpasswd -a zhangsan dev # 把zhangsan加入dev组 gpasswd -d zhangsan dev # 把zhangsan从dev组移除用完gpasswd之后有个隐藏的坑:已经登录的会话不会立即生效。用户执行newgrp dev或者干脆重新登录一次,新组才会加入当前会话的组列表里。如果对方说“我加了组怎么还没权限”,别急着怀疑命令,先id看一眼是不是还没重新登录。
查看组内成员最简单的方式是getent:
getent group dev如果只想临时以某个组身份执行命令,用newgrp切换到该组,会开一个子shell,组关系以新组为主:
newgrp dev退出子shell用exit。这个命令在日常不算高频,但当你临时需要用一个组的身份去访问某个目录,又不想退出重登时,它就是救急妙招。
4.2 sudoers配置:最小授权原则,别拿ALL裸奔
sudo是Linux权限体系里最能影响安全边界的一块,也是很多人配置得最随意的一块。见过太多服务器上直接写着:
zhangsan ALL=(ALL) ALL这相当于给了这个用户完全的root权限,跟把root密码白送差别不大。正确的授权思路是:命令白名单+按角色分组。
/etc/sudoers的正确修改方式是visudo,它会在保存前做语法检查,语法错了不让保存,这能救你一条命——因为sudoers配置文件语法错误会导致sudo直接瘫痪。
推荐的结构是定义组级别授权。比如运维组和开发组的差异化权限:
%ops ALL=(ALL) ALL %dev ALL=(root) /usr/bin/systemctl restart *, /usr/bin/tail -f /var/log/*这样dev组的用户只能重启服务和看日志,改其他系统配置一概没戏。如果需要免密执行特定命令,加NOPASSWD:
%deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart app注意:NOPASSWD只对列出的那几条命令生效,不是说这个用户所有sudo都不用密码。很多人理解反了,就把NOPASSWD加在了整行上面,这等于给了该用户一个不需要验证的sudo,极其危险。
我还想强调一个新手特别容易忽略的点:把用户加入sudo组不等于立该用户马上就能用sudo。和gpasswd的生效机制一样,当前会话不会自动感知组成员变化,必须重新登录一次,sudo -l才能看到新授权。遇到“加了sudo组还是提示不在sudoers中”,九成是这个原因。
4.3 实战案例:模拟一家小团队共享环境的搭建过程
假设一个新项目组成立,三个开发者需要共享部署目录,还有一个自动化部署账号只能执行重启服务的命令。用前面学的东西,完整的搭建过程是这样的:
# 1. 创建组和用户 groupadd -g 1200 project useradd -g project -G project -s /bin/bash -d /home/alice -m alice useradd -g project -G project -s /bin/bash -d /home/bob -m bob useradd -r -s /sbin/nologin -M deploy # 2. 创建共享目录并设置setgid权限位 mkdir -p /srv/project_data chown -R root:project /srv/project_data chmod -R 2770 /srv/project_data这里用到的2770是重点:setgid位(数字2)会让目录里新建的文件自动继承目录的属组。否则即使三个人都属于project组,他们在目录里新建的文件属组默认是他们各自的主组(也是project,所以这里正好能体现相同效果),如果主组不是project,不同人新建的文件就会互相访问不了。setgid是共享目录规范化管理里几乎必用的东西。
再给deploy账号加上sudo授权,写入sudoers:
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart app这个例子麻雀虽小,五脏俱全:组负责纵向权限共享,setgid负责横向文件继承,sudo负责命令级的最小授权。一套下来,团队协作既不互相踩权限,又没有把root全盘交出。
5. 踩坑实录:UID复用、残留文件与Permission denied排查
5.1 删除用户后又新建同名用户,旧文件一夜“易主”
这是运维最常见也最坑的事故之一。一个用户离开了,管理员执行userdel -r把它删掉;过了几天新同事入职,重新执行useradd建了一个同名用户。系统不会给这个新建用户分配和原来一样的UID吗?不会,它会自动从可用UID里取下一个,大概率是一个新的数字。
于是问题来了:旧用户之前在服务器别的地方留下了一堆文件,这些文件的UID还是原来的老UID。如果系统分配的新UID正好和老UID撞上——尤其是手动指定过UID时更常见——那么新用户一登录,会发现整个服务器上一大堆文件全成了“自己的”,权限全部放开了。这不是“巧合”,这是UID复用的必然结果。
我之前接手过一台服务器,就是恢复一个历史备份后发现原用户UID=1001的文件归属诡异地转到了一个刚创建的系统账号上,排查了半天,根源就是备份文件里的UID和现有系统自动分配的UID冲突了。这种事无解,只能从规范上防治:删除用户时保留一张UID映射记录,将来重建用户时手动指定一个全新的UID,坚决不用自动分配的。
5.2 文件属主变成数字:无主文件的三种处理思路
前面说的“属主显示数字”,其实不只发生在删除用户后。还有一种常见场景:你从别的服务器打包拷贝整个文件系统目录过来,打包的环境里有uid 1005的用户,但当前环境里这个uid对应的是另一个用户甚至不存在,文件过来后,属主就会变成数字或者变成“其他用户”。
处理的思路有三种:
- 直接忽略:如果这些文件只是静态数据,没有活跃的权限需求,数字就数字,不影响使用
- 重新chown给现有用户:
chown -R zhangsan:dev /path,简单粗暴,最常用 - 先建个占位账号:如果文件量大而且需要保留属主关系,可以在新环境里创建一个同样UID的账号,属主就能正常显示
我一般是先跑find /path -o -uid 1005这类命令确认无主文件范围,再决定用哪种。别看到数字就恐慌,但也不要放任不管——无主文件在等保审计和故障排查时就是一颗定时炸弹,你永远不知道这个目录曾经属于谁。
5.3 Permission denied的标准排查顺序
最后说Permission denied。这不是一个错误,而是一连串可能原因的结果。我见过太多人一看到Permission denied就想着chmod 777,那不是解决问题,是在制造新的安全漏洞。按下面这个顺序排查,绝大多数情况五分钟内能定位:
- 确认当前身份:
id,看看自己到底是谁,是不是以为自己是root结果还在普通用户状态 - 确认目标对象的权限位:
ls -l和getfacl,看属主、属组、other三个位的权限,注意目录的读和执行权限缺一不可 - 确认自己属于哪个组:
groups,有时候文件属组对了,但你不在那个附加组里,sudo进入不了,重新登录才能生效 - 确认有没有被sudo限制:
sudo -l,看当前shell有没有授权;刚加入sudo组没重登,这里就会暴露 - 确认是不是文件系统挂载选项导致:比如NFS以root_squash挂载,root会被压缩成nobody,就会出现“明明是root却什么都干不了”的情形
这里面最容易忽略的是第2步的目录权限。很多人只盯着文件本身,结果目录没有执行权限(x位),你连目录都进不去,文件权限再好也白搭。Linux的目录权限逻辑是x对应“能否穿越”,r对应“能否列出内容”,一个共享目录如果只有r没有x,ls能看到文件,cd进去就报Permission denied,极其迷惑。
最后分享一个我从踩坑里总结出的习惯
做了几年Linux用户与组管理,我最想对新手说的一句话是:别把命令当黑盒,把四个配置文件当成你的第一手资料。遇到权限或账号问题,先别急着百度,先啃一下/etc/passwd、/etc/shadow、/etc/group,结合id、getent、find这些命令把现状摸清楚。很多看似复杂的权限事故,最后都是一张配置文件就能解释清楚的。
另外,有条件的话,在建账号前就先规划好UID段和GID段:普通用户从1000开始、服务账号在系统范围、虚拟账号单独留一段、共享组统一在某个区间。这个规划越早做,后面折腾的成本越低。一个账号从创建到删除,真正值得花时间的不是几条命令,而是每个命令背后对UID、对权限位、对配置文件的理解——这些理解了,Linux用户与组管理你就彻底过关了。