记得我刚接触Linux那会儿,觉得用户管理不就是useradd加个用户、passwd设个密码吗?直到后来在真实的服务器上吃了几次亏——同事离职后账号没禁用、有人拿着root密码到处登录、一个usermod把人家从附加组里踢出去导致服务起不来——才意识到用户与组管理是整个Linux运维体系的地基。地基没打好,上面跑再花哨的服务都是危房。这篇东西不打算按手册给你罗列命令,而是从“为什么要这么管”的角度,把用户、组、权限、sudo授权这些事一次聊透。适合刚接手服务器的新手,也适合那些 command 背得熟但没形成体系的运维同学对照查漏。
1. 用户不是用户名:先搞清楚Linux账号体系的底层逻辑
很多人用了很久Linux,依然搞不清用户、UID、用户组之间的关系。这不怪你,因为日常操作中你很少直接跟UID打交道,但一旦涉及用户删除、文件归属排查、权限异常,看不懂底层逻辑就会一脸懵。
1.1 UID才是用户的真正身份
Linux内核不认用户名,它只认UID(User ID,用户ID)。用户名是给人看的,UID是给内核用的。你在终端敲ls -l看到的所有者叫root,本质上内核记录的是UID 0,只是通过/etc/passwd把这个数字翻译成了root。
打个比方:UID就像身份证号,用户名像是名字。你可以改名,但身份证号不会变。如果两个用户不小心用了同一个UID,在系统看来他们就是同一个人,彼此的家目录、文件权限完全互通。这在生产环境是严重事故级别的配置错误。
查看一个用户的UID很简单:
bash复制id zhangsan
uid=1001(zhangsan) gid=1001(zhangsan) groups=1001(zhangsan)
也可以用getent直接查passwd数据库:
bash复制getent passwd zhangsan
zhangsan:x:1001:1001::/home/zhangsan:/bin/bash
每一段用冒号分隔,含义依次是:用户名、密码占位符、UID、主组GID、备注信息、家目录、登录Shell。
1.2 四个配置文件决定了你能怎么登录
用户管理绕不开四个文件,它们共同构成了本地账号体系的“数据库”:
/etc/passwd:存储用户基本信息,包括用户名、UID、GID、家目录、Shell。注意,这里的密码字段永远是x,真正的密码在shadow里。/etc/shadow:存储加密后的密码哈希、密码最后修改时间、有效期、失效时间等安全策略信息。只有root或shadow组可读。/etc/group:存储组信息,包括组名、GID、组成员列表。/etc/gshadow:存储组密码及组管理员信息,日常用得少,但组密码机制存在。
这四个文件必须保持一致。很多新手图省事直接vim /etc/passwd改文件,一旦语法写错,轻则用户无法登录,重则系统都进不去。所以我一直强调:能用命令就别手改文件,命令底层会帮你做一致性校验。
看shadow文件了解密码策略时,重点看这几个字段:
bash复制zhangsan:$6$rounds=656000$...:18923:0:99999:7:::
从前往后依次是:登录名、密码哈希、最近修改时间(从1970-01-01起算的天数)、密码最少使用天数、密码最长使用天数、过期前警告天数。一个账号如果被人为锁定,通常哈希字段前面会出现!或*前缀。
1.3 用户的默认值从哪来
useradd创建用户时,UID、家目录路径、Shell、密码过期策略等默认值并不是写死在代码里的,而是从配置文件中读取:
/etc/default/useradd:定义默认的家目录基路径、默认Shell、默认的SKEL目录(用户家目录的模板来源)等。/etc/login.defs:定义UID/GID范围、密码过期天数、创建家目录时的默认umask等。
我自己习惯在装机后先看一眼/etc/login.defs里的几个关键项:
bash复制grep -E "^(UID_MIN|UID_MAX|SYS_UID_MIN|SYS_UID_MAX|CREATE_HOME|UMASK|PASS_MAX_DAYS|PASS_MIN_DAYS)" /etc/login.defs
比如CREATE_HOME如果被设成no,你执行useradd zhangsan就不会自动创建家目录,到时候用户登进去发现自己连home都没有,容易当场懵掉。
还有一个隐藏的模板目录:/etc/skel。你新建用户后,家目录里默认出现的.bashrc、.profile、.bash_logout等文件,全部是从这个目录复制过去的。想给所有新用户统一加一个自定义环境变量,直接往/etc/skel/.bashrc里写就行,省得一个一个用户去配置。这一点对批量交付服务器账号特别有用。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 用useradd建一个用户很简单,但“建好”是另一回事
我见过太多新手建用户的方式:useradd zhangsan,然后passwd zhangsan设个密码,就认为完事了。实际上这样建出来的用户只能说是“能用”,距离“好用”和“安全”还差好几步。
2.1 useradd和adduser不是一回事
这个问题在Ubuntu系和CentOS系的表现还不一样。简单说:
useradd是Linux原生的底层命令,所有发行版都有,参数丰富但不会帮你生成密码、不会帮你创建家目录(取决于配置),也不会问你任何问题。adduser在Debian/Ubuntu系是一个Perl脚本,封装了useradd,会以交互式问答的方式引导你完成创建用户、设置密码、填写备注,对新手更友好。- 在CentOS/RHEL系,
adduser和useradd其实是同一个命令的符号链接,行为完全一样。
所以如果你用Ubuntu,习惯adduser没问题;如果你管理的是CentOS服务器,请直接记useradd。写自动化脚本时,不要依赖adduser的交互逻辑,统一用useradd加参数最省心。
2.2 核心参数一次讲透
useradd参数看着多,真正高频的就这几个:
| 参数 | 含义 | 实际场景 |
|---|---|---|
-u |
指定UID | 需要固定UID同步NFS文件权限时 |
-g |
指定主组(只能是已存在的组) | 把用户归入某个现有部门组 |
-G |
指定附加组(可以多个,逗号分隔) | 给用户追加多个项目组的权限 |
-d |
指定家目录路径 | 默认是/home/用户名,但也可以改成/data/zhangsan |
-s |
指定登录Shell | 不希望用户登录时才需要设为/sbin/nologin |
-M |
不创建家目录 | 创建仅用于运行服务的虚拟账号 |
-m |
强制创建家目录 | 某些发行版默认不创建时很有用 |
-c |
备注信息 | 写用户姓名、工号、部门,方便后续审计 |
-e |
账号过期日期 | 设置临时账号的有效期,格式YYYY-MM-DD |
一个比较完整的创建例子:
bash复制useradd -u 1050 -g devgroup -G docker,www -d /home/zhangsan -s /bin/bash -c "Zhang San - DevOps" -e 2025-12-31 zhangsan
这条命令做了这些事:指定UID 1050,主组是devgroup,同时加入docker和www两个附加组,家目录在/home/zhangsan,Shell是bash,备注里标了身份,并且账号在2025年底自动过期。
2.3 创建用户的完整姿势
我的生产环境习惯分四步走。
第一步,创建用户并加入必要的组:
bash复制useradd -m -s /bin/bash -G wheel zhangsan
这里-m确保家目录生成,-G wheel让用户具备sudo授权的基础资格(具体看sudoers配置)。注意,我没有急着指定UID,除非有明确要求,否则让系统从/etc/login.defs定义的范围内自动分配更安全,避免和已有用户撞UID。
第二步,设置高强度初始密码:
bash复制echo 'Tmp@2024XyZ!' | passwd --stdin zhangsan
--stdin从标准输入读取密码,适合脚本批处理。但这样会留在shell历史里,如果是交互式操作,我建议直接用passwd zhangsan再手动输入。线上禁止使用-stdin时密码会出现在history中,这是我特别强调的一点。
第三步,强制用户首次登录修改密码:
bash复制chage -d 0 zhangsan
这行的作用是把密码的最后修改时间设为0,用户下次登录时系统会强制要求先设置新密码才能继续操作。这是对付“管理员知道初始密码”这个安全短板的标准做法。
第四步,验证结果:
bash复制id zhangsan
ls -ld /home/zhangsan
chage -l zhangsan
检查用户、组、家目录权限、密码策略是否都符合预期。
2.4 为什么家目录权限这么讲究
很多教程不会提,但生产服务器上出问题最多的就是家目录权限。useradd -m创建家目录时默认权限通常由umask决定,一般是755或750。但如果目录权限是777,任何用户都能进你的家目录读写文件,等于没设防。
不同场景建议如下:
- 普通用户家目录:750即可,保证同组用户能进入(有时需要协作),其他用户不可见。
- 高敏用户(如管理员账号):700,只有自己可进。
- 服务账号家目录:一般不存在,或设成750并限制属主为服务账号。
调整命令:
bash复制chmod 750 /home/zhangsan
chown zhangsan:devgroup /home/zhangsan
顺带说一句,/etc/skel里模板文件的属主和权限也要检查。如果模板文件权限不对,比如.bashrc是全局可写,新建用户的家目录继承了这种危险权限,这属于比较容易忽略的安全隐患。
2.5 别忘了服务账号和登录Shell的区别
创建用户时,要想清楚这个用户到底需不需要交互式登录:
- 真正的人:
-s /bin/bash - 只需要跑定时任务:
-s /bin/bash也行,或者-s /usr/sbin/nologin(如果确定不需要shell) - 纯服务进程(如nginx、mysql):建议
-s /sbin/nologin,从源头杜绝密码登录和Shell交互
MySQL的mysql用户、Nginx的nginx用户这类服务账号,系统里往往默认就是/sbin/nologin。但你在给类似服务新建账号时,也一定要记得加-r选项创建系统账号(UID在系统范围内),并配合-M不建家目录、-s /sbin/nologin禁用登录,这样创建的才是符合规范的服务账号:
bash复制useradd -r -M -s /sbin/nologin -d /var/lib/myapp myapp
3. 组成员管理里最容易翻车的“-a”参数
用户组是Linux权限体系里的中间层。没有组的话,你要给20个人配同一个目录的访问权限,就得一个一个设置ACL,想想就头大。有了组,把20个人塞进同一个组,目录权限只要对组开放一次就够了。
3.1 主组和附加组的区别
每个用户有且只有一个主组(Primary Group),这是用户创建文件时默认的属组。useradd如果不指定-g,许多发行版会创建一个同名的私有组(UPG,User Private Group),比如创建zhangsan时自动生成一个zhangsan组,UID和GID相同。
用户的附加组(Supplementary Groups)可以有多个,用来赋予用户在特定资源上的额外权限。比如用户主组是devgroup,但你希望他能读写www组共享的网站目录,就把他加进www组。
这个设计有点像职场:主组是行政关系所在部门,附加组是项目组、委员会这些临时或跨部门身份。一个人行政上属于A部门,但不影响他同时参与B项目、C委员会。
3.2 usermod -G和usermod -aG只差一个字母
这里我要重点强调一个网上问烂了但现实中依然频繁踩坑的问题。
假设用户zhangsan原本在devgroup组,也在docker组。你想让他再进入www组,有人会写:
bash复制usermod -G www zhangsan
问题来了:-G的作用是“覆盖用户的附加组列表”。上面这条命令会把zhangsan原有的附加组(devgroup、docker)全部清掉,只保留www。如果他本来靠docker组权限在跑容器,这个操作一执行,服务立刻报权限错误。
正确的做法是加-a选项(append,追加):
bash复制usermod -aG www zhangsan
-aG才是“在原有附加组基础上追加”。我在多条服务器上亲眼见过因为少了这个a导致的事故,轻则用户没法访问原来的共享目录,重则应用起不来。所以,请把usermod -aG当成肌肉记忆,不加-a的-G只在你有意清空用户全部附加组时才使用。
3.3 查看用户所属组的三种方式
想确认用户现在到底在哪些组里,有三种方法:
bash复制groups zhangsan
id zhangsan
getent group | grep zhangsan
groups和id显示的都是用户当前生效的组,区别是id会顺带显示UID和GID信息。前两个命令查的是用户数据库里记录的组成员关系,第三种实际上是反向从组文件里找哪些组包含该用户,在组成员较多时非常直观。
有一个细节多数人不知道:用户修改附加组后,如果该用户当前已经登录,新的组关系不会立刻生效,需要重新登录或执行newgrp命令刷新会话的组身份。 如果你给用户加了组,对方反馈“还是没权限”,第一反应先让他退出重登,别急着改权限。
4. sudo授权:服务器提权管理的正确打开方式
服务器管理绕不开提权。传统做法是把root密码直接给需要管理服务器的人,但这带来两个问题:第一,root密码在团队里传一圈,基本等于公开了,出事儿根本查不到是谁执行的;第二,root的权限是全量、无差别的,你本来只想让运维重启一下nginx,结果他拥有删库的能力,这属于权限过度授予。
sudo的出现解决了这两件事:可以精准控制“谁能以谁的身份执行哪些命令”,并且执行的所有命令都有日志审计。它的使用前提是用户知道自己的登录密码,而不是知道root密码。
4.1 先理解和su的区别
su -是全量切换用户身份,需要输入目标用户(通常是root)的密码,切换后拥有完整的目标用户权限。这带来一个弊端:大家共用root密码,无法追溯到底是谁在什么时候执行了什么操作。
sudo则不同。它是“在某个命令前临时提权”,验证的是当前用户自己的密码。授权范围可以细化到某条命令:
bash复制# 允许zhangsan以root身份重启nginx,但不能干别的
zhangsan ALL=(root) /usr/bin/systemctl restart nginx
这就是最小权限原则落地到命令级的一个例子。所以,我的建议是:root密码只保留给极少数真正需要直接登录root的人(或者干脆禁止root远程登录),日常操作一律走sudo。
4.2 sudoers文件怎么改才安全
sudo的授权规则写在/etc/sudoers里。这个文件语法非常挑剔,写错一个字符可能导致所有sudo操作异常,所以绝对不能用vim直接改,一定要用visudo命令编辑。visudo会在保存前做语法检查,发现有语法错误会拦下你,避免你把自己锁在门外。
一个常见的安全配置是,在/etc/sudoers.d/目录下创建独立文件来管理不同的用户组。比如创建/etc/sudoers.d/devops文件:
bash复制# 允许devops组成员执行所有命令,但需要输入自己的密码
%devops ALL=(ALL) ALL
让运维组成员免密执行systemctl相关命令,同时不开放其他root权限:
bash复制%devops ALL=(root) NOPASSWD: /usr/bin/systemctl
写sudoers规则的时候有几个容易踩的坑:
ALL=(ALL) ALL中的第一个ALL表示允许在任意主机执行,第二个ALL表示可以切换成任意身份,第三个ALL表示任意命令。如果不是对完全信任的人,不要轻易给完整ALL。- 设置
NOPASSWD一定要确认被赋权用户的可信度,因为它意味着如果你猜到了对方密码,他就能直接免密执行你指定的提权命令,窃取面更广。 - 一个技巧是,明确允许的命令应该用绝对路径。比如
/usr/bin/systemctl而不是systemctl,既保证安全,也避免PATH被劫持。
4.3 别给所有人完整的root
我在做权限设计时,会先对使用者做分级,然后再授予对应sudo规则:
- 普通开发:一般不需要sudo,或者只授予查看日志的sudo权限,比如
/usr/bin/tail。 - 运维工程师:授予服务管理类命令的sudo权限,同时开放部分只读命令。
- DBA:仅授予数据库相关命令的运行权限,同时限制shell的执行入口。
- 安全/合规人员:可以做审计、日志收集,但不具有对生产配置的写权限。
举两个实际的sudoers规则片段作为参考:
bash复制# 允许dev组执行systemd服务管理,但不能执行systemctl daemon-reexec(防止重载环境)
%dev ALL=(root) /usr/bin/systemctl list-unit-files, /usr/bin/systemctl status *, /usr/bin/systemctl start *, /usr/bin/systemctl stop *, /usr/bin/systemctl restart *
注意最后的是*通配符,但在sudoers里通配符不能跨目录层级,比如/usr/bin/systemctl restart *理论上能匹配同一层级的参数。如果想限制到“只能重启nginx”,则直接写具体服务名更稳妥:
bash复制%webops ALL=(root) /usr/bin/systemctl restart nginx
4.4 用户能sudo执行哪些命令?随时自查
运维过程中经常要确认一个用户到底被赋予了什么权限,你可以直接切换到那个用户(如果知道其密码),执行:
bash复制sudo -l
会列出当前用户被允许(和禁止)执行的全部sudo命令。注意,sudoers里配置了规则但用户不存在或组不匹配时,sudo -l会提示“用户不在sudoers文件中”,这种通常表示授权未生效。
另外,sudo和用户管理的日志记录在/var/log/secure(CentOS/RHEL系)或/var/log/auth.log(Debian/Ubuntu系)。排查安全事件时,这里是你搞清楚“谁在什么时候执行过什么提权命令”的头号证据源。
5. 删除用户和锁定账号的“善后”工作,比创建更重要
大部分教程只讲怎么创建用户,不讲怎么安全地回收账号。但一个完整的账号生命周期管理里,离职、调岗、临时工到期这些场景才真正考验管理水平。
5.1 userdel的-r要慎重
删除用户的基本命令是:
bash复制userdel zhangsan
这样只删账号,家目录和mail spool都保留着。如果想连同家目录一起删除:
bash复制userdel -r zhangsan
问题是,-r只会删除该用户的家目录和mail spool,但用户在其他地方遗留的、以他自己身份创建的定时任务、系统服务文件、数据库中的授权记录并不会被自动清理。我以前处理过一个案例:删除了某用户,但两周后运维发现一台服务的定时任务还在定时执行,一查原来是该用户之前放在/var/spool/cron/下的crontab还保留着。
所以我的建议是:在删除用户前,先做一次全面扫描:
bash复制find / -user zhangsan 2>/dev/null
crontab -l -u zhangsan 2>/dev/null
ls -l /var/spool/mail/zhangsan
确认没有需要保留的数据后,再执行删除。生产环境的合理流程是“先锁定,后删除”,而不是直接一条userdel -r把数据清掉。
5.2 锁定账号的三种方式,别只记住一种
有时候你不确定要不要彻底删除账号,或者需要临时停用某人的登录权限,可以锁定账号而不是删掉。
方式一:passwd -l。密码字段前会加!,用户无法用密码登录。
bash复制passwd -l zhangsan
但要注意,这只会禁止密码登录。如果用户配置了SSH密钥登录,密钥依然有效,需要同时禁用密钥登录或者配合其他方式处理。
方式二:usermod -L。效果和passwd -l类似,会在shadow文件的密码哈希前加!。对应解锁命令是usermod -U。
方式三:chage -E 0。把账号过期日设为0,相当于立即过期。这种方式比较好的一点是,它会记录在chage信息里,审计时能看到明确的操作痕迹,解锁时把过期时间改回将来就行:
bash复制chage -E 0 zhangsan
chage -E 2025-12-31 zhangsan
三种方式没有绝对好坏,关键是要想清楚场景。如果是员工离职,我一般会先用chage -E 0让账号过期,再顺手把用户从所有附加组中移除,同时删掉它的SSH公钥,最大程度封堵所有登录路径。
5.3 排查空密码账户和异常UID 0账号
账号管理中有一个必须定期自查的安全指标:系统里不能存在空密码的账号,也不能出现多个UID 0的账号。
空密码意味着用户不需要密码就能登录(本地终端或通过某些配置不当的服务),这是底线级别的安全隐患。排查方法:
bash复制awk -F: '($2==""){print $1}' /etc/shadow
正常情况下这个命令什么都不输出。如果有结果,立即用passwd 用户名设置密码或直接锁定该账号。
排查UID 0账号(即root权限账号)的方法:
bash复制awk -F: '($3==0){print $1}' /etc/passwd
正常情况只有root一行。如果出现了其他用户名,说明有人创建了影子root账号,这是一种经典的权限维持手法,一旦发现要立刻调查,并移除其UID 0权限。
再配合检查所有可登录用户的Shell是否合理:
bash复制awk -F: '($7!="/sbin/nologin" && $7!="/bin/false" && $7!="/usr/sbin/nologin"){print $1, $7}' /etc/passwd
每隔一段时间跑一次这几条命令,能帮你快速发现账号体系的异常变化。
6. 一个完整的实战演练:新同事入职账号创建的标准化流程
前面把知识点拆开了讲,最后我串一个完整的实战场景,展示一下在真实服务器上给新同事开通账号时,一组合理的操作流程长什么样。
6.1 场景设定与需求梳理
假设团队来了一个运维工程师叫李四(lisi),他的职责包括:日常巡检、重启服务、查看日志。服务器是三台CentOS 7.9。当前已有的组有dev(开发组)、ops(运维组)、nginx(nginx进程组)。
需求整理一下:
- 用户登录名:
lisi - 归属:主组设为
ops,附加组加入nginx(因为需要查看nginx日志目录) - 需要sudo权限:能够执行systemctl管理服务和tail查看日志,但不能随意修改系统配置
- 密码策略:首次登录必须强制改密,密码最长使用90天,过期前7天提醒
- 家目录:需要创建,且权限为750
6.2 操作步骤全过程
第一步,确认组都存在:
bash复制getent group ops nginx
如果nginx组不存在,先创建(虽然nginx安装时一般会自动建好):
bash复制groupadd nginx
第二步,创建用户并设置主组和附加组:
bash复制useradd -m -g ops -G nginx -s /bin/bash -c "Li Si - Ops Engineer" lisi
检查结果:
bash复制id lisi
uid=1002(lisi) gid=1003(ops) groups=1003(ops),1004(nginx)
第三步,设置初始密码并强制首次修改:
bash复制passwd lisi
chage -d 0 lisi
第四步,配置密码有效期策略:
bash复制chage -M 90 -W 7 lisi
第五步,配置sudo授权。创建独立授权文件:
bash复制visudo -f /etc/sudoers.d/lisi
写入以下内容:
bash复制lisi ALL=(root) /usr/bin/systemctl list-unit-files, /usr/bin/systemctl status *, /usr/bin/systemctl start *, /usr/bin/systemctl stop *, /usr/bin/systemctl restart *, /usr/bin/tail, /usr/bin/less, /bin/journalctl
这里只授权了服务管理、日志查看相关的命令,没有开放/usr/bin/vim、/usr/bin/passwd等可能借机修改系统配置的入口。如果业务上确实需要他用vim编辑配置文件,再另申请针对具体文件的sudo规则,这比全量开放安全得多。
第六步,校验sudo配置语法:
bash复制visudo -c
看到parse OK才算通过。
6.3 登录验证与最终检查
账号创建完不能直接交付,至少要做一轮登录验证。
尝试直接用密码登录会提示需要修改密码(因为chage -d 0生效了):
bash复制ssh lisi@192.168.1.10
You are required to change your password immediately (root enforced)
修改密码后登录成功,执行id确认用户组关系,执行sudo -l确认授权的sudo命令列表正确展示。
最后检查家目录属主和权限:
bash复制ls -ld /home/lisi
drwxr-x--- 2 lisi ops 4096 Jan 15 10:23 /home/lisi
到这里,这个账号才算正式交付。后续如果他想在服务器间做免密登录,再按规范添加SSH公钥到/home/lisi/.ssh/authorized_keys,并确保家目录和.ssh目录权限严格(家目录750,.ssh700,authorized_keys600)。
最后补充一点经验
这些年下来,我自己给用户和组管理定了几条原则,分享出来供参考:一是能用组管理权限就不要单独给人授权,组才是权限分配的单位;二是创建账号时把备注信息(-c)写全,工号姓名部门都写上,不然半年后回头看一堆用户名根本对不上是谁;三是删除或锁定账号前先扫一遍用户关联的crontab、systemd服务、文件属主和SSH公钥;四是所有root权限的授予都必须走sudo并保留日志,禁止直接分发root密码。用户管理出问题通常不是命令不会敲,而是流程不规范。把规范前置到日常操作里,很多半夜的故障根本不会发生。
实操中还有一个我踩过多次的坑,就是批量创建用户前一定要先确认系统里没有重复的UID或用户名,脚本执行前用getent passwd扫一遍,否则用户建重了轻则创建失败,重则把已有账号的属主关系搞乱。如果你经常要批量交付账号,建议把上面这套创建流程写成脚本,每次执行前自动检查用户是否存在、组是否存在、密码策略是否设置成功,输出一份交付清单,这样既省事又不容易出错。
