接手一台线上服务器我通常做的第一件事不是装环境、跑服务而是先打开/etc/passwd和/etc/sudoers把账号和权限挨个过一遍。干这行越久越清楚一个规律很多所谓的安全事故源头根本不是黑客用了多高级的漏洞而是服务器上哪个普通用户多了个sudo权限、哪个离职员工的账号还挂在线上、哪条sudoers规则给了所有人一把万能钥匙。Linux 用户管理和 Sudo 权限精细化控制听起来像基础课但真正做扎实的系统管理员并不多。这篇文章我会从接手一台服务器开始把账号梳理、用户创建、密码策略、sudo 权限设计、日志审计完整串一遍适合刚入门想要规范自己服务器的同学也适合被一堆历史账号困扰、想动手清理一下的运维同行。1. 接手服务器先做账号梳理为什么乱建账号会让审计无从下手1.1 用户管理的本质是生命周期管理很多新手把建用户理解成添加一个能登录的账号这个思路其实一开始就偏了。一个服务器用户从创建到回收要经历申请、初始化、权限分配、使用维护、权限变更、离职/弃用回收这一整条链路。真正好的用户管理是让这台机器上每一个账号都能被追踪谁创建的、用来做什么、现在谁还在用、权限是不是还匹配当前职责。如果缺少这层管理意识问题会很快浮现一个项目做了一年参与的人换了三轮但账号全留在机器上有人离职了工牌都还了SSH key 还在服务器上挂着权限分配粗放凡是需要装个包、重启个服务的统统丢进wheel组等于人手一张 root 门禁卡。到哪一天日志里发现异常操作你根本无法定位是哪个人干的因为账号和真实人员早就不对应了。这就是我强调要先做账号梳理的原因——不是查一遍有多少账号而是把账号的真实状态、归属、必要性全部盘一遍。1.2 第一次梳理五步摸清服务器账号家底把账号家底盘清楚的工具其实就几个命令组合起来用效果很好。# 第一步列出所有普通用户UID大于等于1000的 awk -F: $31000 $365534 {print $1\t$3\t$6\t$7} /etc/passwd # 第二步看哪些用户能用sudo sudo grep -E ^(root|[^#]) /etc/group | grep -E sudo|wheel sudo visudo -c # 顺带检查sudoers语法是否正常 # 第三步当前谁登录在服务器上 who # 第四步最近谁登录过 last -20 # 第五步查看所有账号上次登录时间 lastlog | grep -v Never logged in这五组命令跑完/etc/passwd里的账号、sudo 组里的人员、当前在线、历史登录、沉睡账号就全在你的脑子里了。我第一次梳理线上机器的时候发现最惊人的不是账号多而是有一批从未登录过的账号——这种账号十有八九是某次部署时临时创建、之后再也没有人用过却一直保持着可以登录甚至 sudo 的状态。做完梳理建议你用表格把账号归类人工账号开发、运维、管理员、服务账号nginx、mysql、redis 这类、临时账号某次交付临时开的每一类给一个标记。后面做清理和权限调整这张表就是你的底图。1.3 命名、UID 与家目录规划先定规矩再动手永远比边做边改省事。我常用的规划思路是这样的规划项建议原因账号命名企业环境用工号/域账号名个人服务器按角色命名如 webadmin、dbadmin方便追溯真实人员UID 范围普通用户 1000~60000系统服务账号低于 1000与发行版默认约定一致误删系统账号的概率降低家目录统一放/home/用户名下备份、迁移、权限控制都好做登录 Shell人工账号给/bin/bash服务账号给/sbin/nologin最小化攻击面防止服务账号意外获得 shell这里提一句 UID 的重要性系统判定是不是普通用户基本靠 UID 数字。很多安全工具默认只扫 UID≥1000 的用户如果你图省事把某个用户直接指定成 UID 0那系统和安全软件都会把他当 root 看待这类隐藏账号是安全审计里的高危项尽量别用这种方式。2. useradd 建号三要素家目录、Shell、附加组2.1 useradd 参数全解一次建号把该做的都做掉useradd这个命令我没有让它裸奔用过一次。原因很简单默认行为经常不是你想要的行为。不同发行版对useradd的默认策略不一样有的默认不创建家目录有的默认 shell 是/bin/sh这些坑踩一次就能记住。看一下我实际建人工账号时最常用的一条命令sudo useradd -m -d /home/dev01 -s /bin/bash -G devs,www-data -u 2001 -c Dev Engineer ZhangSan dev01参数逐个拆开参数作用为什么不省略-m自动创建家目录默认不一定建不建的话用户登录后落到根目录问题很多-d指定家目录路径不显式指定可能落到默认位置乱了不好管-s指定登录 Shell默认可能是/bin/sh交互体验差建议显式给/bin/bash-G指定附加组把用户一次加到需要的组里省得后面单独usermod-u指定 UID配合前面的 UID 规划让账号身份不漂移-c备注说明写清楚是谁、什么用途几年后查账就不会一脸懵注意这里用-G指定附加组的时候默认是追加还是覆盖useradd -G直接创建新用户时会加入这些组不会影响系统默认组但如果是用usermod -G修改已有用户不加-a就会把用户从原有附加组中踢出去只保留你写的这一个。这个区别等于是差之毫厘失之千里下面讲 sudo 组时还会再碰到。2.2 批量建号newusers 与 chpasswd 的组合用法一次只建一个账号的场景不够的话批量创建就必须掌握。比如给团队一次性开五个测试账号手动跑五次useradd没问题但五十个就不行了。Linux 下批量建用户有成熟工具我用得顺手的是newusers配合chpasswd。先准备一个用户信息文本格式和/etc/passwd一致cat /tmp/users.txt EOF dev01:x:2001:2001:Dev One:/home/dev01:/bin/bash dev02:x:2002:2002:Dev Two:/home/dev02:/bin/bash dev03:x:2003:2003:Dev Three:/home/dev03:/bin/bash EOF sudo newusers /tmp/users.txtnewusers会读取这个文件批量创建用户但这一步只建账号、不设密码。接着用chpasswd批量为每个用户写初始密码echo dev01:初始密码一串 | sudo chpasswd echo dev02:初始密码一串 | sudo chpasswd更有经验的团队会把两件事合在一起脚本循环处理然后强制用户第一次登录就改密码sudo chage -d 0 dev01chage -d 0把密码最后修改时间设为 1970年1月1日等于强制该用户下次登录必须立刻换密码。这个动作对批量建号尤其重要——一次性发出去的初始密码如果不强制改后面就会变成大家都用同一个密码的安全隐患。2.3 建号之后的初始化动作账号建完只是第一步初始化动作不做等于门开了锁没装。我自己的流程里新账号建立后必做四件事拷骨架文件useradd -m会自动把/etc/skel下的文件复制到家目录这里的.bashrc、.profile等文件内容决定用户第一次登录的体验值得提前维护好一套统一的配置。设公钥登录建议直接给用户的~/.ssh/authorized_keys写入管理员持有的公钥而不是设置一个可以远程登录的密码。特别是服务账号能不用密码登录就绝不用。统一确认登录 Shell如果不是/bin/bash而是/sbin/nologin要确认账号真的不需要交互登录。服务账号拿到 shell 是权限边界问题宁可严格一点。记录台账把创建时间、创建人、用途、关联项目写进你的账号清单。别小看这个动作以后做季度巡检、等保审计、同事离职交接这份台账能救你命。这些不是花架子而是把临时想到才去做变成标准流程的一部分。账单混乱的服务器十有八九就是少了这一步。3. 密码策略与账户生命周期防范“沉睡账号”和弱口令3.1 用 chage 管理密码过期节奏密码策略不是设一个复杂的密码就行而是要有节奏感——多长时间必须换、换之前多久提醒、能不能反复使用旧密码。这些全由chage控制。# 查看某个用户的密码过期信息 sudo chage -l dev01 # 设置密码90天必须换7天内不能改密码到期前14天开始提醒 sudo chage -M 90 -m 7 -W 14 dev01 # 直接把账号设为某一天过期 sudo chage -E 2025-12-31 dev01-M 90最大有效期 90 天-m 7最短修改间隔 7 天防止用户刚改完密码立刻改回原来的-W 14提前两周提醒。这几个参数一组合密码策略的基本框架就出来了。配合/etc/login.defs里的PASS_MAX_DAYS、PASS_MIN_DAYS、PASS_WARN_AGE可以对全系统新建用户设置统一的默认值。我见过很多服务器建号时没改密码策略结果所有账号的密码永不过期等发现时已经是弱口令满天飞的状态。3.2 锁定、解锁与临时授权账号生命周期里的冻结和解冻同样重要命令不多但用法区别很大。# 锁住账号禁止登录 sudo passwd -l dev01 # 或者 sudo usermod -L dev01 # 解锁 sudo passwd -u dev01 sudo usermod -U dev01 # 强制用户下次登录修改密码 sudo chage -d 0 dev01passwd -l和usermod -L本质是在密码字段前加个!让密码验证无法通过但这个方法只影响后续的密码登录。如果用户已经登录状态还挂着或者正在跑着进程锁账号并不能把他们马上踢下来。完整操作是先锁账号再杀掉属于该用户的所有进程最后确认没有残留连接。sudo pkill -u dev01另一个我习惯的做法是临时授权时不直接改 sudoers而是用一个指定时间窗口。Linux 本身没有一个原生的sudo 临时授权到几点命令但可以用sudoedit配合Defaults条件或者 At 任务。最务实的还是把关键 sudo 权限直接绑定到具体命令毕竟大多数临时授权其实只是临时要用某个命令这就回到后面 sudo 精细化控制的范畴了。3.3 PAM 防暴力破解的基础配置密码策略定好后还要解决一个常见攻击路径SSH 暴力破解。除了把端口改成非默认、限制来源 IPPAM 层的失败锁定也值得配。RHEL/CentOS/Rocky 系机器上pam_faillock是常用的失败锁定模块。配置思路是在/etc/pam.d/system-auth或相关 PAM 配置里加上失败计数与解锁时间auth required pam_faillock.so preauth audit deny5 unlock_time600 auth sufficient pam_faillock.so authsucc audit deny5 unlock_time600翻译成人话就是同一用户连续输错 5 次密码锁定 10 分钟。Debian/Ubuntu 系的默认 PAM 结构和 RHEL 系有差异通常用pam_tally2或直接上fail2ban。无论哪种核心思路都是在密码验证之前加一道闸门把机器从无限试错变成有代价的攻击。这个配置我建议先在测试机上验证一轮再上线因为 PAM 配置写错导致所有用户无法登录的事故在运维圈里一点不罕见。改之前备份原文件改完另开一个 SSH 会话测试这是底线。4. 日常巡检中的用户审计三板斧4.1 第一板斧查在线与历史登录用户管理不是建完账号就结束了日常巡检要回答三个问题现在谁在机器上最近谁来过有没有人从未登录过# 谁当前在线 w # 谁在哪个终端登录 who # 最近登录历史 last -20 # 所有账号的上次登录时间 lastlogw和who一眼就能看清楚当下状态last看过去一段时间的登录记录lastlog则是把全部账号的上次登录时间列出来。这三招是每天早上一遍巡检的基础盘。重点看两类异常一是非工作时间登录的记录明显偏多二是长期不用的账号突然有登录痕迹。前者可能只是加班后者往往是账号被盗或被复用的信号。4.2 第二板斧查组与 sudo 权限分布比登录更敏感的是权限变化。审计 sudo 权限分布我一般用两条命令# 查看哪些用户在sudo/wheel组里 getent group sudo getent group wheel # 查看某个用户当前拥有的sudo权限明细 sudo -l -U dev01getent group比直接读/etc/group更全面因为它同时考虑了 NIS、LDAP 等远端来源。sudo -l -U则是查某用户能执行哪些 sudo 命令这个输出比想象中有用。有一次我巡检时发现某个不应该有权限的开发账号显示(ALL) ALL顺着查才发现是三个月前一次故障排错时有人图省事直接把他加进了 wheel 组事后完全忘了撤销。权限这种东西加了之后撤销的时点经常被遗漏。所以巡检频率要高一点我个人的节奏是核心机器每天看一眼登录每周查一次 sudo 组人员每月做一次完整账号盘点。4.3 第三板斧清理僵尸账号的正确姿势清理僵尸账号很容易踩坑直接userdel -r 用户名虽然方便但可能把还在运行的进程、目录软链接、日志归属全部搞乱。我清理的步骤是先用lastlog确认该账号确实长期未使用再确认没有对应进程在跑ps -u 用户名锁定账号sudo usermod -L 用户名观察一周确认没有异常告警一周后再删除sudo userdel -r 用户名。userdel -r会顺带删除家目录和邮件池。如果这个家目录里有其他服务在引用的文件删除前一定要备份或迁移。曾经有位同事清理账号时没确认软链接直接把家目录下挂载的数据目录删了教训相当惨痛。宁可慢一点、分两步走也别图快一刀切。5. 进入 sudo从加入 sudo 权限组到理解 sudoers 语法5.1 把普通用户加入 sudo 权限组的正确姿势几乎每周都会碰到有人问怎么把普通用户加入 sudo 权限组。答案很简单但分派系。Debian/Ubuntu 系的机器上sudo 组叫sudosudo usermod -aG sudo dev01RHEL/CentOS/Rocky 系的机器上sudo 组叫wheelsudo usermod -aG wheel dev01注意是-aG不是-G。这个-a是 append意思是追加到该组不加-a的话这条命令会把用户当前所有的附加组都清掉只保留下sudo或wheel一个组。如果一个用户本来在docker组、www-data组里一条不带-a的usermod -G wheel 用户名下去瞬间全没了。这个坑我见过不止一次务必记住。加入组之后能不能 sudo 还取决于/etc/sudoers里有没有给这个组授权。Debian 系默认有%sudo ALL(ALL:ALL) ALLRHEL 系默认有%wheel ALL(ALL) ALL所以你加入组后通常就能 sudo。但能 sudo和合理地 sudo是两回事——默认配置给的是整台机器全部权限是极其粗放的授权方式。5.2 sudoers 四段式语法读懂一条授权规则要让 sudo 权限精确到某个用户只能执行某些命令必须读/etc/sudoers。这个文件乍看复杂核心语法其实可以记成一句话谁在哪个机器上能以谁的身份执行哪些命令。完整格式授权对象 主机列表(可切换身份:可切换组) 命令列表拆开看部分含义示例授权对象用户名、%组名、别名dev01或%wheel主机列表允许在哪台机器上执行通常写ALL可切换身份允许以哪个用户身份执行(ALL)表示可以切换到任意用户命令列表允许执行哪些命令/usr/bin/systemctl restart nginx最简单的授权示例允许 dev01 以 root 身份重启 nginx 服务。dev01 ALL(ALL) /usr/bin/systemctl restart nginx用户执行时输入sudo systemctl restart nginx——注意命令要写绝对路径sudoers 里的命令名如果写成systemctl而不是/usr/bin/systemctl匹配行为会有差异。再看一个禁区写法也是最常见的错误授权dev01 ALL(ALL) ALL这行等于把 root 的钥匙直接给了 dev01。用户拿到这个权限后可以执行任意命令比如sudo su -直接切 rootsudoers 里写多少条规则都拦不住。所以我的原则很明确除非是管理员的专职账号否则不要给ALL(ALL) ALL。5.3 Cmnd_Alias、User_Alias 与 Defaults 配置sudoers 里最实用的部分是指定命令别名。命令一长串每次写全路径很啰嗦用别名就好很多Cmnd_Alias WEB_OP /usr/bin/systemctl start nginx, /usr/bin/systemctl stop nginx, /usr/bin/systemctl restart nginx, /usr/bin/systemctl reload nginx dev01 ALL(ALL) NOPASSWD: WEB_OP同理可以定义用户别名User_Alias DEVOPS dev01, dev02, dev03然后整体授权DEVOPS ALL(ALL) NOPASSWD: WEB_OPDefaults配置则控制 sudo 本身的运行行为。我最常改的两个Defaults secure_path /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin Defaults timestamp_timeout 5secure_path固定了 sudo 执行时的 PATH防止环境变量被用户劫持比如恶意脚本把 PATH 指向带同名命令的目录。timestamp_timeout是 sudo 免密验证的缓存时间默认 5 分钟意思是执行过一次 sudo 输入密码后5 分钟内再执行 sudo 不用重新输密码。时间设置太长不安全建议默认或调短尤其多人共用的机器。任何对 sudoers 的修改我都强烈建议用visudo而不是直接vim /etc/sudoers。visudo在保存时会做语法检查如果语法有误它会拒绝保存并提出警告。直接用 vim 改 sudoers 导致整台机器 sudo 失效的事故运维圈里每年都在发生别去踩这个坑。6. 精细化授权实战三个典型场景配置全解6.1 场景 A开发人员只需重启 Web 服务最常见的需求开发说我需要重启 nginx但是不能给他 root。解决方案是只授权 systemctl 操作 nginx 的那几个命令。编辑/etc/sudoers.d/dev-webCmnd_Alias WEB_OP /usr/bin/systemctl start nginx, /usr/bin/systemctl stop nginx, /usr/bin/systemctl restart nginx, /usr/bin/systemctl reload nginx dev01 ALL(ALL) NOPASSWD: WEB_OP配置完成先验证sudo -l -U dev01输出里应当能看到dev01允许执行的命令列表包含这几个 nginx 相关命令而不包含其他。用户执行时也很直观sudo systemctl restart nginx这里有两个细节值得说明。第一NOPASSWD为什么敢给因为授权范围窄用户只能操作 nginx 服务的启停无法进入 root shell即使误操作影响面也小。第二为什么不用service nginx restart或直接写nginxsudoers 匹配的是完整命令路径写成简写或相对路径很容易被 PATH 环境变量干扰。6.2 场景 B运维轮值的大权限与高危命令排除运维组的需求往往更复杂要给的范围大但雇人不愿给太危险的命令。常规做法是用!排除高危命令Cmnd_Alias FMT_OP /usr/sbin/mkfs.*, /usr/sbin/parted, /usr/sbin/fdisk %wheel ALL(ALL) ALL, !FMT_OP这套配置的意思是 wheel 组成员可以执行任意命令但mkfs、parted、fdisk这些格式化、分区命令被排除。看起来能防住手滑格式化磁盘的悲剧。但必须说清楚用!做排除并不绝对安全。sudoers 对命令匹配基于命令名 参数的模式mkfs.*的写法能否精确匹配所有格式化命令存在边界而且 sudo 的匹配不会拦截用户在成功执行某个白名单命令后利用该命令本身的漏洞去干别的。比如允许了某个交互式编辑器用户完全可以在编辑器里开个 shell。所以我给轮值运维的最终建议通常是确定性高危命令用白名单排除同时配合日志审计和线上操作审批而不是把宝全押在!规则上。真正的 运维但不会炸服务器 的思路是分级授权把运维组拆成 值班组能操作服务启停、日志排查和 管理组能改系统配置值班组用命令白名单给具体命令管理组用ALL但配上严格审计。这样既满足日常需要又把高危操作的入口缩小到可控范围。6.3 场景 C以指定服务账号身份执行命令sudo 不只是切换到 root还可以切换到任意用户。很多应用维护场景下开发需要执行某个命令但必须以服务账号的身份跑不能直接切 root。比如允许 dev01 以www-data用户身份执行 php 命令dev01 ALL(www-data) NOPASSWD: /usr/bin/php这样 dev01 执行sudo -u www-data /usr/bin/php -v就能以www-data身份运行 PHP不会影响系统其他部分。这在处理 Web 应用问题、调试权限、执行定时任务时非常实用。注意(www-data)限制了 sudo 切换到的目标用户dev01 不能借此切到 root如果想要同时限制目标用户和组可以写成(www-data:www-data)。6.4 免密 sudo自动化场景的平衡点NOPASSWD 这个词很多人又爱又怕。自动化 CI/CD 需要免密执行没人愿意在流水线里等着输密码但给 NOPASSWD 又担心权限被滥用。我的平衡点是NOPASSWD 可以给但只给具体命令且绝不给NOPASSWD: ALL。比如 CI 部署用户 deploy只允许 docker 和 git 操作deploy ALL(ALL) NOPASSWD: /usr/bin/docker, /usr/bin/git自动化任务执行时不用人工介入不需要密码但 shell 落到用户手里时也只能执行这两个命令无法sudo su -提权。这套做法满足 90% 的自动化需求同时把风险压在一个可接受范围。再补充一个原则即使配了 NOPASSWDsudo 日志照样会记录每一次执行。免密不等于免审计反而是自动化场景下审计显得更重要——没有人工交互过程如果出问题日志几乎是唯一的定位手段。7. sudo 日志审计与安全加固让每一次提权都留痕7.1 日志开在哪里auth.log、secure 与自定义日志sudo 执行记录默认是进系统认证日志的Debian/Ubuntu 系在/var/log/auth.logRHEL/CentOS/Rocky 系在/var/log/secure。还可以在 sudoers 里自定义一份单独日志便于集中排查Defaults logfile /var/log/sudo.log这个配置会让每次 sudo 执行写一条记录到独立文件。查询某个用户执行过哪些 sudo 命令grep dev01 /var/log/sudo.log如果机器上配置了 rsyslog 或 journald也可以用 journalctl 查询sudo journalctl _COMMsudo | grep dev01日志是一个字段一个字段看的sudo.log里每条记录包含时间、用户名、调用者、所在终端、执行的完整命令。一旦出现某个账号突然执行了历史列表里从未出现过的命令这条日志就是第一证据。7.2 sudoreplay 重放完整录屏级别的审计sudo 还有一个被低估的功能sudoreplay。它能把某个 sudo 会话的输入输出完整重放出来相当于给每次提权操作拍了一段视频。启用方式是在 sudoers 里开启输入输出日志Defaults log_input, log_output Defaults iolog_dir /var/log/sudo-io配置后每次 sudo 会话的输入输出会被记录到/var/log/sudo-io。之后用sudo -l或查日志找到会话 ID再用sudoreplay重放sudo sudoreplay -d /var/log/sudo-io 会话ID这个功能对到底是谁在哪个终端干了什么这个问题来说是终极答案。遇到需要取证或责任界定的场景比如某次误删文件但大家都不承认重放记录就能直观还原操作过程。代价是会产生额外的磁盘占用生产环境我建议只在核心机器或高权限账号上开启不必全量开启。7.3 安全加固的五条经验sudo 加固我平时按这五条执行捡几条你最容易受益的说禁止 root 直接 SSH 登录/etc/ssh/sshd_config里PermitRootLogin no让所有管理员先用普通账号登录再 sudo 提权。这样 root 密码不暴露在网络里谁都拿不到直接 root 的钥匙。sudoers 语法改动后立刻验证不要改了配置就直接干活。先新开一个 SSH 会话执行一次sudo -l确认正常再锁定当前会话。优先用 /etc/sudoers.d/ 下的独立文件把不同业务、不同用户的规则拆开放比如100-deploy、200-dev-web而不是全部堆在一个大文件里。可读性和可维护性都大幅提升排错时也一目了然。定期用sudo -l -U检查所有用户把机器上所有普通用户的 sudo 权限列一遍对照台账确认每个人的权限和他当前职责匹配。权限高于职责的当场调整。sudoers 里的命令永远写绝对路径避免用相对路径和通配符匹配不可控的命令防止 PATH 劫持或同名脚本伪造命令。这一点在多人维护的机器上尤其重要。8. 一些我踩过的坑和最后的建议写到这里关于用户管理和 sudo 权限控制的核心内容基本覆盖完整了。回头看一下我最大的心得其实是一个字小。权限给得越小出问题的范围越小授权的命令越具体审计的时候定位越准确。实操中我还总结了几条建议适合你直接拿来用每次创建用户前先问一句这个账号是给真人还是给程序用的这决定了大多数参数的取舍每次改 sudoers 前先备份/etc/sudoers和/etc/sudoers.d/同时打开一个新终端别在唯一的会话里改每个季度做一次账号盘点锁定不用的账号、撤销不该有的 sudo 权限、检查日志有没有异常提权记录。我自己接手新环境时会先把账号梳理命令跑一遍做成台账然后才敢动配置。系统管理员的核心价值不在于会多少炫酷命令而是让这台机器上的每一次身份切换、每一次权限提升都能被追责、被审计。把用户管理和 sudo 权限做细看起来啰嗦实际上是在为后续所有的运维动作打地基。地基稳了后面跑什么都踏实。