干了好几年Linux运维,我有一个很强烈的体会:用户管理是绝大多数Linux管理员都会,但很少有人真正重视的一环。很多人一提到用户管理,脑子里就蹦出三五个命令——useradd建个用户,passwd改下密码,删的时候来个userdel -r收尾。这套流程在小机器上确实能跑,但一旦到了多用户的生产服务器上,坑就一个接一个地冒出来。前阵子帮同事排查一台服务器,发现项目目录里一堆文件归属变成了数字“1005”,查了半天才发现是上个管理员删用户的时候没处理干净,新用户又把那个UID给顶上了。这种擦屁股的活,干一次就够难受的。
所以这篇想把Linux用户管理从头到尾掰开揉碎讲一遍。不单是命令怎么敲,更重要的是弄清楚系统底层是怎么对待用户的、账号的完整生命周期应该怎么管理、删账号怎么才能不留后患,顺带会分享一些日常维护中容易被忽略的安全习惯。无论你是刚接触Linux的新手,还是在生产环境摸爬滚打过的运维,这篇应该都能给你一些可以直接上手用的东西。
1. 用户名的本质:一个数字引发的管理难题
1.1 内核只认UID,用户名只是给人类看的翻译层
很多人第一次看到ls -l输出的时候,都盯着那个文件属主(owner)看半天:这到底是个什么神仙用户,怎么一会儿显示root一会儿显示数字?答案其实很朴素——Linux内核完全不认识root、zhangsan这种名字,它处理进程、文件权限的时候,用的全都是整数ID,也就是UID(用户ID)和GID(组ID)。用户名是给人类看的翻译层,内核拿到root这个名字要先去查表换算成0,然后才能真正判断“这个进程有没有权限打开那个文件”。
拿最简单的场景举例,你的项目目录里有个文件,属主显示成了一个光秃秃的数字,比如1005,翻遍/etc/passwd也找不到这个用户。这种情况十有八九是文件原来的主人被删掉了,但文件还在。系统不会因为属主没了就自动把文件删掉,它只知道“这个文件归UID 1005管”,至于这个UID现在有没有对应的用户名,它根本不关心。
如果你想让内核视角现原形,一条命令就能做到:
ls -ln /some/file-n参数的作用就是“显示数字身份而不是用户名”。看到真实的UID以后,很多权限问题就清晰了:你不一定能直接get到www-data是谁,但看到33这个数字,你就知道这是Web服务在用的账号,很多发行版里www-data固定占着UID 33这个坑。
1.2/etc/passwd:一张所有人都能读的“员工花名册”
用户名到UID的映射关系,全部写在/etc/passwd这个文件里。格式是每一行一个用户,字段用冒号分隔,一共7段:
| 字段 | 示例 | 含义 |
|---|---|---|
| 用户名 | root | 登录名,用于给人看 |
| 密码占位符 | x | 真正的密码哈希不在这里,这里只是占位 |
| UID | 0 | 用户ID,内核实际识别用的 |
| GID | 0 | 初始组ID,用户创建文件时的默认组 |
| 注释信息 | root | 一般写用户全名或用途,可以随便填 |
| 家目录 | /root | 用户登录后的起始目录 |
| 登录Shell | /bin/bash | 用户登录后执行的Shell程序 |
这个文件有个非常重要的特点:它是所有用户都可读的(权限通常是644)。因为很多程序都需要通过用户名查UID、查家目录,但这些信息不涉及密码哈希,所以不怕被窥探。可一旦你在这个文件里看到密码字段不是x,而是一长串密文,那就说明系统配置有严重问题——这个文件不该承载密码哈希。
顺便说一句,很多新手会忍不住用一个叫什么编辑器直接改这个文件。我的建议是,正经做法用vipw命令,它会帮你加锁,防止两个管理员同时编辑把文件搞坏。改完它还会提示你同步更新/etc/shadow。
1.3/etc/shadow:把密码单独锁进保险柜
既然/etc/passwd任何人都能读,那密码哈希再放里面就是自掘坟墓。过去老Unix系统确实把加密后的密码放在passwd文件里,后来发现暴力破解太容易,才拆出了/etc/shadow这个新文件,把密码哈希和账户有效期信息单独放进去,权限收紧为root可读(通常是000或640)。
一行shadow记录长这样:
root:$6$9h3nF2kd$eU4bQ...:18765:0:99999:7:0:1::字段倒也不算复杂:
| 位置 | 含义 |
|---|---|
| 第1段 | 用户名 |
| 第2段 | 加密后的密码哈希,开头$6$表示SHA-512算法,$y$是yescrypt |
| 第3段 | 最近一次改密码的日期(从1970年1月1日起算的天数) |
| 第4段 | 两次修改密码之间的最小间隔天数 |
| 第5段 | 密码有效期(最大天数) |
| 第6段 | 密码过期前多少天提醒用户 |
| 第7段 | 密码过期后宽限天数 |
| 第8段 | 账号失效日期 |
| 第9段 | 保留字段 |
如果密码字段是!或*,表示这个账号被锁定了,无法用密码登录。这也是许多管理员判断“这个用户到底还能不能登”的主要依据。日常排查的时候,看到!就不要去纠结他密码是不是忘了——他就是被锁的账户。chage -l 用户名可以查看某用户完整的密码有效期信息,比肉眼看shadow文件直观得多。
1.4 组也是一个数字:GID与主组、附加组的区别
用户管理的另一个大块是组(group),对应的映射文件是/etc/group。组的作用是把一群用户归拢到一起,然后通过组权限统一控制文件访问。比如项目目录设置成drwxrwx---,属组是webdev组,那组里的成员就都能读写,省得一个个给用户授权。
每个用户都有一个主组(primary group),创建文件时文件默认的组就是它。一个用户还可以同时加入若干个附加组(supplementary groups),获得这些组的访问权限。id 用户名命令会把这两类组都列出来:
$ id zhangsan uid=1050(zhangsan) gid=1050(zhangsan) groups=1050(zhangsan),4(adm),27(sudo),999(docker)gid=后面跟的是主组,groups=后面那些是附加组。这里有个容易绕晕的概念:主组不一定只有一个,但对用户来说它就只有一个“初始组”。而附加组可以有多个,用于给用户叠加额外权限。比如把开发者加进docker组,他就有了调用Docker的权限,不用往sudoers里塞人就解决了工具授权问题。
2. 新建用户的完整操作与权限规划
2.1useradd背后的默认值机制
新手建用户习惯于useradd zhangsan一把梭,但这样建出来的用户往往不带家目录,登录Shell也可能不是bash,密码当然是空的,属于“半成品”账号。这背后的原因是useradd的所有默认值都由/etc/default/useradd和/etc/login.defs两个文件控制。
比如/etc/login.defs里有一组常用配置直接决定新用户的形态:
| 配置项 | 含义 | 常见默认值 |
|---|---|---|
UID_MIN/UID_MAX | 普通用户UID范围 | 1000~60000 |
CREATE_HOME | 是否默认创建家目录 | yes或no,取决于发行版 |
USERGROUPS_ENAB | 是否自动创建同名用户组 | yes |
UMASK | 新文件的默认权限掩码 | 022,即文件644、目录755 |
所以有些发行版(比如Debian系)默认建用户是带家目录和同名组的,而有些精简系统可能什么都不建。我的建议是,不要依赖发行版默认行为,创建用户的时候把关键参数显式写出来,这样不管换到什么环境,行为都一致。
2.2 生产级创建用户五步走
我现在创建用户基本按照一套固定流程来,每一步都有明确目的。假设要给新同事小张建一个开发账号,需要加入docker组和webdev组,且禁用SSH密码登录(只允许密钥):
第一步:创建用户。显式指定家目录、Shell、UID,并加入需要的附加组。
sudo useradd -m -d /home/zhangsan -s /bin/bash -U -u 1050 -G docker,webdev zhangsan参数拆解:
-m:如果家目录不存在就自动创建,并把/etc/skel里的骨架文件复制进去。-d:指定家目录路径。默认会在/home下用用户名做目录,但显式写出来更稳妥。-s:指定登录Shell。不改的话有些系统默认是/bin/sh,操作体验和bash差很多。-U:创建同名用户组,并把该组设为主组。-u 1050:手动指定UID。自己固定UID范围,方便后续用数字识别,也避免和系统已有用户冲突。-G:附加组列表,多个组用英文逗号分隔。
第二步:设置初始密码并强制首次登录修改。
sudo passwd zhangsan sudo chage -d 0 zhangsanchage -d 0这行很多人会漏掉。它的作用是把“上次修改密码日期”归零,这样用户一登录就会被强制要求修改密码。新账号发出去的时候,初始密码是管理员设置的临时密码,不强制改的话,小张可能半年都不换一次,等于这个密码一直处于半公开状态。
第三步:写入SSH公钥,关闭密码登录。
sudo mkdir -p /home/zhangsan/.ssh sudo tee /home/zhangsan/.ssh/authorized_keys <<'EOF' ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... EOF sudo chown -R zhangsan:zhangsan /home/zhangsan/.ssh sudo chmod 700 /home/zhangsan/.ssh sudo chmod 600 /home/zhangsan/.ssh/authorized_keys生产环境里密码登录能关就关。有了公钥,再配合sshd_config里禁用PasswordAuthentication,密码泄露面就少了一大块。这里顺带提醒一句,公钥文件的权限如果太宽松(比如644),sshd会直接拒绝使用这个文件,因为任何人都能往里加自己的公钥,那还怎么保证安全。
第四步:验证账号状态。
getent passwd zhangsan id zhangsan sudo -l -U zhangsangetent比直接grep /etc/passwd更好用,因为它会同时查NSS(名字服务开关)配置里的所有数据源,不光是本地文件。如果以后接入了LDAP或者AD域,这套验证方式依然有效。
第五步:告知用户初始信息。把账号名、初始密码(需要单独走安全通道发)、服务器地址、登录方式(密钥验证)一起整理好发给对方,同时提醒他第一次登录必须改密码。
2.3 骨架目录/etc/skel的价值
创建用户的时候-m参数会把/etc/skel目录下的所有文件复制到用户的家目录。默认情况下这个目录一般只放.bashrc、.profile这些基础配置,但我们完全可以利用它来统一新用户的环境。
比如公司统一用vim,可以在/etc/skel/.vimrc里写上一段常用配置,新用户一登录就自动拥有这些设定,不用每个人手动配一遍。再比如想给新用户一个欢迎信息,可以在/etc/skel/.bashrc末尾加几行echo。
这里有个细节要注意:/etc/skel只对“之后创建”的用户生效,已经存在的用户不会被更新。如果想批量把骨架文件推给老用户,可以写个脚本手动复制,但复制前最好确认不要覆盖用户自己改过的配置。比起直接覆盖,更稳妥的做法是只复制那些用户没有的自定义文件。
2.4 组策略:什么时候建新组,什么时候搭上已有组
很多新手建组很随意,今天想到一个项目就建一个组,过两天项目黄了组也不清理,到最后/etc/group里一堆僵尸组。我的原则很简单:组跟着角色走,不跟着个人走。
比如你有一台服务器专门跑Web项目,那固定组就应该是webdev,所有需要开发这台机器的人都被加进这个组。项目临时扩编,不要急着为某个一次性需求新建组,先看看能否复用已有组。只有当你发现某个权限集合长期存在、多次出现时,才考虑单独建组。
另外,用户的主组默认是它的同名组,这本身不是坏事。但如果你的业务特点是多个用户共享同一个数据目录,那就需要考虑把他们的主组统一改成项目组模式。比如四个前端都归frontend这个大组管,直接给它们设置主组为frontend,新文件创建出来默认就是组内可写,大家协作会很舒服。唯一要留神的是,修改主组后新文件的默认属主依赖umask,需要配合setgid目录等方式来保证组写权限,这个后面会讲到。
3. sudo 和 su:给临时管理员发“有限通行证”的正确姿势
3.1 su 与 sudo 的本质区别
先厘清两个概念。su是“切换用户”,你输入su - root,整个Shell会变成root的Shell,所有后续命令都以root身份执行。关键点是它需要输入的是目标用户的密码。也就是说,想让某人有root权限,就得把root密码交给他,一旦给出去,root密码就满天飞了,而且谁什么时候用root干了什么,你完全没日志可查。
sudo的思路不一样,它是“以某个用户身份执行单条命令”。用法是sudo cat /etc/shadow,执行完这一条,命令就结束了,当前Shell还是普通用户身份。它验证的是当前用户自己的密码,不需要暴露root密码。另外,sudo的每次授权行为都会记录到日志里,出事了能回溯。所以就管理安全性而言,sudo远胜su。
我的建议是:生产服务器上root密码尽量只有机房带外管理口或极少数核心管理员知道,日常运维全部走sudo。
3.2 用 visudo 管理授权规则的细节
sudo的授权规则写在/etc/sudoers文件里。这个文件语法很讲究,写错了轻则sudo不可用,重则可能把整个管理通道锁死。所以Linux专门提供了visudo命令来编辑它,保存时会自动做语法检查。如果你非要手动改,改完一定要跑一下visudo -c做校验。
先看一行典型规则:
zhangsan ALL=(ALL) NOPASSWD: /usr/bin/systemctl逐段拆解:
zhangsan:被授权的用户名(也可以用%组名来针对整个组)。ALL:允许在哪台主机上执行,单机场景直接填ALL。(ALL):可以以哪个用户的身份执行,(ALL)表示可以切换到任意用户。NOPASSWD::执行sudo时免密码。/usr/bin/systemctl:允许执行的命令路径。
我见过很多人把NOPASSWD用得很泛滥,搞得所有sudo操作都免密,security几乎等于零。更合理的做法是:把“日常高频、低风险”的命令设为免密,比如服务启停;把“高风险、低频率”的操作保留密码确认,比如编辑sshd配置。
还有一个容易被忽略的好习惯:把自己的自定义规则放到/etc/sudoers.d/目录下一个独立文件里,而不是直接堆在/etc/sudoers里。
echo 'zhangsan ALL=(ALL) NOPASSWD: /usr/bin/systemctl' | sudo tee /etc/sudoers.d/zhangsan这样每个用户或每个项目组的规则互相隔离。至于原因很简单:有一次我收到一台交接的服务器,所有人都在往/etc/sudoers里加行,里面既有老同事留下的deploy账号,还有临时借调的实习生账号,根本分不清谁是谁。拆分成独立文件后,一个账号一个文件,删的时候直接删对应文件,干净利落。
3.3 setuid、setgid 与 sticky bit:目录和文件上的隐藏权限位
和sudo不同,文件系统里还有一套更底层的提权机制,就是setuid和setgid位。它们和用户管理的关系极其密切。
setuid位用一个s代替x出现在文件属主的执行位上,比如rwsr-xr-x。当一个可执行文件带setuid位时,普通用户执行它,进程会临时获得该文件属主的身份。最经典的就是/usr/bin/passwd:
$ ls -l /usr/bin/passwd -rwsr-xr-x 1 root root 68208 May 28 2024 /usr/bin/passwd为什么要这么设计?因为普通用户要改自己的密码,需要写/etc/shadow,而shadow文件只有root能写。如果passwd命令没有setuid位,普通用户根本改不了密码。所以在可控范围内,setuid是必要的。
但它也是最常见的提权漏洞点。如果一个root拥有的可执行文件被加了setuid位,而它本身又有漏洞,普通用户就可能通过它来提权到root。所以定期扫描系统里所有setuid文件是个好习惯:
sudo find / -perm -4000 -type f 2>/dev/nullsetgid位发生在目录上的时候,效果是“新创建的文件自动继承目录的属组,而不是创建者的主组”。这是多用户协作中非常好用的工具。比如/data/project目录属组设为webdev,再设置setgid位,无论谁在里面建文件,文件属组都会自动变成webdev,项目成员就能顺畅地共同读写。
sticky bit则主要用在/tmp这类共享目录上:加了t位的目录里,除了root,只有文件自己的属主才能删文件,别人就算对这个目录有写权限也删不动别人的文件。这三个隐藏权限位,是你理解Linux多用户协作绕不开的底层知识点。
4. 删除与清理账号:最容易出事的一个环节
4.1userdel的局限
删账号是最容易被低估风险的操作。很多人以为userdel zhangsan一下就把用户删干净了,其实它做的事非常有限——只是把/etc/passwd、/etc/shadow、/etc/group里对应的记录移除。用户的家目录、邮箱文件、crontab任务、以及用户拥有的其他文件,统统原地不动。
带-r参数会顺手删掉家目录和mail spool,但依然不会清理用户在其他地方留下的文件。如果这个用户曾经管理过项目的某个目录,删完以后这个目录里所有文件的属主就变成数字UID了。用ls -l看是这样:
-rw-r--r-- 1 1050 1050 2048 Jul 12 10:22 config.yaml过两天有个新用户建出来,系统分配UID正好是1050,那这个新用户就莫名“继承”了这些文件的操作权。在多人共享的服务器上,这是很严重的数据泄露隐患。真要避免这种情况,就不能只靠一个userdel解决。
4.2 一个安全的删除流程应该长什么样
我的建议是把删除当项目来做,至少走下面六步:
第一步:锁定账号,防止继续登录。
sudo usermod -L zhangsan sudo chage -E 0 zhangsanusermod -L会给shadow里的密码哈希前面加!,立即阻止密码登录。chage -E 0把账号过期日期设为0,双保险。注意这步只影响新的登录尝试,不会踢掉已经登录的会话。
第二步:终结该用户的残留进程。
sudo pkill -u zhangsan # 或者强制杀 sudo pkill -KILL -u zhangsan一般建议先发TERM信号让进程优雅退出,等几秒再KILL。如果你不确定有没有漏网之鱼,可以复查:
ps -u zhangsan第三步:找出用户所有的文件。
sudo find / -user zhangsan 2>/dev/null这条命令会在整个文件系统里找出所有属主是该用户的文件。输出可能很长,别急着看完就下结论。资料备完、代码提交完、日志归档完,再决定哪些需要保留,保留的就移交给别人:
sudo chown -R otheruser:othergroup /data/important/zhangsan/第四步:清理定时任务和相关服务。用户自己的crontab用ls /var/spool/cron/看看有没有对应文件,有就删掉或转移。现在的systemd系统里,用户还可能有自己的systemd用户单元,在~/.config/systemd/user/目录,这也要一并处理。
第五步:删除用户及相关文件。
sudo userdel -r zhangsan这里的-r会删家目录和mail spool。如果前面你已经手动移走过重要数据,这一步就不会丢东西。
第六步:验证清理结果。
getent passwd zhangsan find / -name "*zhangsan*" 2>/dev/null重点确认两件事:/etc/passwd里没有该用户了;家目录和其他明显归属文件都不在了。
4.3 UID复用带来的权限残留
接着前面提到的数字归属问题继续讲。最稳妥的办法其实是:删完用户,不要急着释放UID。如果短时间内不打算把同一个UID分配给新用户,你可以暂时让那些残留文件保持数字属主状态。虽然看着不优雅,但至少不会出现“新用户自动继承旧用户文件”的乌龙。
如果你希望新用户明确接管某些目录,就要用chown -R显式把文件归属改过去,而不要指望系统自动处理。在UID复用这个问题上,系统不会管你是不是同一个“人”,它只认数字。
4.4 锁账号与删账号怎么选
很多场景下,锁账号比删账号划算得多。比如同事离职了,但短期内可能需要审计他的历史操作;或者某个服务账号暂时不用了,但配置里还在引用它。这时候推荐的做法是:
- 用
usermod -L锁密码; - 用
usermod -s /usr/sbin/nologin(或/bin/false)把登录Shell换成不可交互的; - 用
chage -E 0设账号过期。
这样账号还在系统里,文件属主信息不会变成一串数字,配置也不用改,但任何直接登录行为都会被挡在外面。等确认可以彻底清除了,再按前面的6步流程走。
5. 账号安全底线与审计习惯
5.1 检查系统里是否存在异常账户
安全审计这件事,平时不做没事,做一次能发现一堆陈年旧账。我每次接手一台服务器,第一件事就是看用户列表:
# 查看所有普通用户 awk -F: '$3>=1000' /etc/passwd这条命令把UID在1000以上的普通用户全列出来。看完之后问自己几个问题:这些人都是谁?还有联系方式的用户有几个?有没有离职半年了账号还活着的?有没有UID是0的非root账户?
检查UID为0的账户要特别在意。UID为0意味着超级用户权限,如果除了root之外还有一个账户也是0,那基本可以断定是有人故意留后门:
awk -F: '$3==0{print $1}' /etc/passwd正常的输出应该只有root一行,多出来的都有嫌疑。
5.2 审计登录记录和sudo日志
每天花两分钟看看登录日志,比装一堆复杂的安全软件有效得多。几个实用命令:
# 查看最近成功登录记录 last # 查看最近失败登录记录 lastb # 当前在线用户 who失败登录记录lastb特别值得看。如果发现同一个IP在短时间内尝试几百上千次,说明服务器正被扫描或暴力破解。另外,sudo的日志一般会进入/var/log/auth.log(Debian系)或/var/log/secure(RHEL系),想看谁用了管理员权限,直接搜sudo就能找到:
sudo grep "sudo:" /var/log/auth.log | tail -20也可以借助journalctl统一查询:
journalctl -u ssh --since "7 days ago" | grep "Failed password" | wc -l平时不必盯着每一条日志看,但养成每周刷一次的习惯,很多问题能在周报里发现端倪。
5.3 文件层面的一致性检查
最后提一个容易被忽略的命令:pwck和grpck。这两个命令会遍历/etc/passwd与/etc/group文件,检查字段格式是否正确、家目录是否存在、用户主组是否有对应记录。如果文件被误编辑出问题,这两个命令会直接报错。
比如系统里某个用户的家目录被误删了,但/etc/passwd里的记录还在,pwck会提示“目录不存在”。这时候要么重建家目录,要么删除该用户,避免用户登录后跑到一个不存在的路径里。
再比如有些用户主组指向的组记录不存在,grpck也会报警。这类问题平时不显眼,但一旦触发,用户可能突然发现自己无法创建文件、无法访问目录,排查起来很费劲。定期跑一遍这两个命令,属于成本极低、收益很高的维护习惯。
另外还有一点,别把太多的服务跑在root账号下。我见过很多业务进程图省事,启动脚本里写死用root跑,导致一个配置错误就能让整个系统暴露在风险中。正确的姿势是给每个服务建独立账号,比如nginx跑nginx、postgres跑PostgreSQL、app-runner跑业务应用。就算某个服务被攻破,攻击者能拿到的也只是这个服务的有限权限,而不是整个系统的root。这也是用户管理在应用层最重要的价值。
我在实际维护中最深的体会是:真正把用户管理做好的服务器,往往不是因为用了多复杂的工具,而是管理员对“用户”这个概念本身理解得足够清楚——用户名只是一个翻译层,UID才是系统真正在乎的东西;账号创建时要规划好权限边界,删除时要全盘清理,平常还要坚持看日志、查异常。这套基本功打扎实了,后面再去碰什么容器、云原生、IAM体系,都会轻松很多。