
最近帮一家小公司处理线上事故后端服务半夜突然连不上数据库登录服务器一看问题根源不是数据库崩了而是运维同事临时创建的部署账号家目录权限设成了 777被人顺手改了 .bashrc全局环境变量被替换服务起不来。这种问题在真实环境里太常见了。Linux 账号和权限管理听起来是每个用过 Linux 的人都会两下子的基本功但真正上了生产服务器因为账号建得不规范、权限给得太随意引发的故障比内核崩溃出现的频率高得多。这篇文章不打算讲教科书式的概念罗列而是把我实际踩过的坑、排查过的故障、以及现在搭建服务器时固化成习惯的那套做法完整写出来。无论你是刚入门的运维、写代码但对服务器半生不熟的开发还是自己买了云主机折腾个人站点的爱好者照着这套思路去检查你的服务器多少能提前排掉几个雷。1. 账号管理不只是 useradd从用户表结构到生命周期管理1.1 建号之前先看懂三张表很多人用 Linux 几年useradd一敲就完事但从来没打开过/etc/passwd。我强烈建议你先执行一次cat /etc/passwd把文件结构看明白因为后续所有账号问题最后都要回到这三个文件里找答案。/etc/passwd每一行是一个用户账号冒号分隔了 7 个字段root:x:0:0:root:/root:/bin/bash deploy:x:1001:1001:Deploy User:/home/deploy:/usr/sbin/nologin依次是用户名、密码占位符、UID、GID、注释信息、家目录、登录 Shell。注意第二个字段现在几乎都是x真正的密码哈希在/etc/shadow里。/etc/passwd是对所有用户可读的如果把密码哈希放这里等于把钥匙挂在门上。这也是 Linux 把密码拆出去的核心原因。/etc/group管理用户组每一行是组名、组密码占位符、GID、组成员列表。先弄清楚一个概念用户创建时默认会有一个同名私有组这个组的 GID 会写进/etc/passwd的第四个字段叫主组primary group。而/etc/group里第四个字段列出来的组是用户的附加组supplementary group。主组决定新文件的默认属组附加组决定你能额外访问哪些资源这两个东西混在一起是最常见的权限事故源头。1.2 useradd、usermod、userdel 的组合用法生产环境建号我一般不用裸的useradd因为不同发行版对默认值的处理不同Debian/Ubuntu 上你敲useradd可能连家目录都不给你建。现在我用这类命令组合useradd -m -d /home/deploy -s /bin/bash -G www-data -c Deploy User -e 2026-12-31 deploy-m创建家目录-d指定家目录路径。-s指定登录 Shell。如果是跑服务的账号我通常给/usr/sbin/nologin禁止交互登录。-G加入附加组比如让部署账号加入www-data组以便读写 Web 目录。-e账号过期时间配合内部规范防止账号永久有效。账号信息建错的时候用usermod改。这里有个我吃过亏的点usermod -G如果不加-a会把用户从原来的所有附加组里踢出去只保留你指定的新组。比如一个用户原来在docker组你敲usermod -G www-data zhangsan他就失去 docker 权限了而且你不会立刻发现。正确写法是usermod -aG www-data zhangsanuserdel同理。裸的userdel deploy只删账号不删家目录服务器上会留一堆 UID 归属不清的孤儿文件。我一般用userdel -r deploy连家目录和邮件池一起删。但注意一点如果一个 UID 曾经拥有过某些文件删号之后这些文件的属主会显示成一串数字将来新用户如果被系统分配了同一个 UID他就会变成这些文件的新属主。这属于高风险的 UID 复用问题后面细说。1.3 僵尸账号、UID复用和过期清理有段时间我接手一批旧服务器发现/etc/passwd里有十多个三年前的离职员工账号Shell 还是/bin/bash家目录还在。这种僵尸账号是最容易被忽略的入口。我的处理习惯是# 锁定账号禁止登录 usermod -L zhangsan # 过期账号 chage -E 2025-01-01 zhangsanpasswd -l zhangsan也能锁定但chage -E可以直接设定过期日期比手动管理状态更可控。对于确定要删除的账号动手之前先扫描它名下的文件find / -user zhangsan -ls 2/dev/null如果文件很多先chown -R给接手的人再删号避免留下 UID 孤儿。UID 复用的问题我在一次迁移中碰到过旧系统里一个内部工具以 UID 1005 运行后来这个 UID 被分配给了一个新员工账号结果新员工发现自己能读那个工具的历史缓存文件里面还有别人的临时数据。后来我定了一条规矩内部系统账号固定使用特定 UID 段比如 1000-1999 给人2000 给服务账号并登记在案禁止随意复用。2. Linux 权限模型拆解普通权限、ACL 与隐藏位2.1 为什么 rwx 只是第一层筛子聊权限不能只聊那些rwxr-xr-x的字母你得先理解文件和目录的权限语义完全不同。文件的r是能读内容w是能改内容x是能当成程序执行。目录的r是能列出目录里的文件名w是能在目录里新建或删除文件x是能进入目录。这里最容易被忽略的是如果目录没有x权限哪怕你有r你也进不去列出名字有什么用反过来很多服务报Permission denied不是文件本身没权限而是路径上某个目录的x权限没给够。举个例子Nginx 要读/var/www/html/index.html它必须对/var、/var/www、/var/www/html三级目录都有x权限。很多人只chmod 755了最里层的目录外层是系统默认的 755 没问题但如果你自己建了个/data/app/logs中间某层目录权限是 700服务进程就会莫名其妙读不到文件。排查这类问题一定要用下面的方式逐层看namei -l /data/app/logs/app.lognamei会列出路径上每一层的权限比一层层ls -ld快得多。2.2 SUID、SGID、粘滞位三个最容易被误解的位普通 rwx 之外还有三个特殊权限位分别是 SUID4、SGID2和粘滞位1。它们的数字位会体现在 chmod 的第一位比如4755、2770、1777。SUID 的作用是用户执行这个程序时进程的有效身份是程序属主而不是执行者本人。最常见的例子是/usr/bin/passwd普通用户修改自己的密码时需要写/etc/shadow这个文件只有 root 能写靠的正是 passwd 程序上的 SUID 位。但 SUID 是双刃剑任何一个带 SUID 的程序如果有漏洞就可能被人利用来读取 root 才该访问的文件。所以我在巡检时经常会扫一遍全系统的 SUID 文件find / -perm -4000 -type f 2/dev/null有人可能会问SGID 用在目录上是什么意思。我给协作目录设置 SGID 后目录里新建的文件和目录会继承目录的属组而不是创建者自己的主组。这对共享目录来说非常实用。比如团队共享目录/data/shared属组是devteam你给它加上 SGIDchgrp devteam /data/shared chmod 2770 /data/shared之后不管谁在这个目录里建文件属组都会自动是devteam不用每次手工chgrp。粘滞位更简单/tmp就是drwxrwxrwt。粘滞位的作用是在公共目录里你可以创建文件也能改自己的文件但你不能删别人创建的文件。没有粘滞位一个777的公共目录里任何用户都可以删掉其他人的文件。判断目录是否设置了粘滞位看权限尾部的t就行。2.3 umask默认权限是怎么来的每次新建文件系统不会直接给你666或777而是用默认值减去 umask。当前 shell 的 umask 用umask命令查看一般默认是022。新建文件666 - 022 644即rw-r--r--。新建目录777 - 022 755即rwxr-xr-x。这里有个细节文件默认本来就不应该给执行权限所以是666目录因为需要x来进入所以是777。减法的结果是文件 644、目录 755这也解释了为什么你touch出来的脚本总要先chmod x才能跑。如果目录是多人协作场景umask 建议设成002这样新文件是664、新目录是775组内成员可写。修改用户默认 umask可以写在/etc/profile、/etc/bash.bashrc或/etc/login.defs里。但注意systemd 管理的服务进程默认 umask 是022跟 shell 里的设置无关。我曾经遇到开发在容器里改了 umask 以为生效了结果服务以 systemd 方式启动后还是生成 644 文件导致协作目录里别人没法改配置。最后是在 service 文件里加了[Service] UMask0002这才符合预期。3. chmod/chown 实战高频误区和完整排查链路3.1 符号模式与数字模式什么时候用哪个chmod有两种写法一种是chmod ux file一种是chmod 755 file。我的经验是交互式调整时用符号模式能清楚表达意图批量配置和脚本里用数字模式结果确定、不易被当前 umask 干扰。举几个常用的chmod ux script.sh # 给属主加执行权限 chmod g-w config.ini # 去掉属组的写权限 chmod -R 755 /data/app # 递归设置chown的格式是属主:属组比如chown -R deploy:devteam /data/app这里的坑是很多人只chown了属主忘了:组名结果文件属组还是 root应用账号能读不能写。写完chown建议立刻ls -ld确认一下。3.2 一次 Permission denied 的完整排查链路遇到应用报Permission denied不要上来就chmod 777。777是解决问题的捷径也是把服务器拱手让人的捷径。我现在的排查顺序是固定的第一步确认服务进程到底是谁在跑ps aux | grep nginx以 nginx 为例worker 进程属主是www-data还是nginx不同发行版不一样。先搞清楚身份再谈权限。第二步用id看这个用户属于哪些组id www-data第三步逐层检查目标路径权限。重点不是看最后一层而是每一层目录namei -l /var/www/html/index.html第四步检查 ACL 和隐藏属性。普通 rwx 显示为时说明存在 ACLgetfacl /var/www/html lsattr /var/www/htmllsattr检查i不可修改属性。有一次我排查了一个多小时chmod、chown都试遍了还是报权限错误最后发现文件被加了chattr i任何写入都被拒绝。那个时刻真的会让人怀疑人生。第五步如果系统开启了 SELinux 或 AppArmor还需要确认安全上下文getenforce ls -Z /var/www/html/index.html有时候chmod是对的但 SELinux 的 type 标签不对一样给你拦下来。这类问题在dmesg或/var/log/audit/audit.log里通常有avc: denied的明确记录。不建议直接setenforce 0关掉 SELinux那等于把最后一道门也拆了正确做法是调整文件上下文或者写对应的策略模块。3.3 批量变更、误操作恢复和权限基线批量操作最容易出事。chown -R和chmod -R跑在大目录上一旦路径写错可能直接把系统目录的属主改坏。我处理过一次同事在/根目录上执行chown -R deploy:deploy /的现场命令还没跑完他就发现sudo已经不可用了因为/etc/sudoers的属主变成了 deploy。最后只能重启进单用户模式修复。所以我现在的做法是动工之前用getfacl把权限备份下来getfacl -R /data/app /tmp/app-perms.acl真出了事恢复也只是反着执行一次setfacl --restore/tmp/app-perms.acl这套东西对普通权限也有效因为它会把属主、属组、ACL 一起记录。另外我强烈建议给服务器建立一套“权限基线”哪些关键目录必须是 root 拥有、哪些脚本必须无写权限写进内部文档巡检时直接比对。权限管理最怕的不是改了错权限而是改了之后没人知道基线是什么。4. sudo 提权最小化配置、日志审计与后门排查4.1 sudoers 语法和最小授权原则多用户服务器需要提权但直接交出 root 密码是最坏的做法。root 密码一旦多人知晓根本查不出是谁执行了什么命令。sudo的价值在于既能提权又能记录还能限制命令范围。所有 sudo 配置都通过visudo来改它会检查语法错误避免你写错行导致 sudo 整个不可用。配置文件主文件是/etc/sudoers但我习惯把业务配置拆到/etc/sudoers.d/下面主文件只做 include方便管理和备份。一条最基本的规则长这样deploy ALL(ALL) /usr/bin/systemctl restart nginx含义是deploy用户在所有主机上可以以 root 身份执行 systemctl restart nginx 这一个命令。注意如果用户还能执行/bin/bash、/usr/bin/vim这类命令那白名单就形同虚设。vim 里可以:!bash直接弹 shellfind 也可以-exec执行任意命令。给 sudo 白名单一定要想清楚这个命令本身有没有脱离参数约束的能力。另一个常见需求是让某个组的人都能执行运维命令%ops ALL(ALL) /usr/bin/systemctl4.2 sudo 日志审计与提权痕迹排查启用 sudo 之后我接手服务器第一件事就是检查日志是否完整。Debian/Ubuntu 看/var/log/auth.logRHEL/CentOS 看/var/log/secure。日志里会记录每次 sudo 的时间、用户、执行命令。我还建议在 sudoers 里加一行把 sudo 命令统一记录到专属文件Defaults logfile/var/log/sudo.log Defaults log_input, log_output第二步sudo.log会把用户敲进去的字符和命令输出也记录下来对事后审计非常有帮助。开这个功能之后要注意磁盘占用日志增长会比普通记录快很多建议配合 logrotate 轮转。如果有人试图绕过 sudo也常在日志里留下痕迹。比如多次输错密码、反复尝试/etc/sudoers内容在auth.log里都会出现大量authentication failure。我巡检时关注几个点/etc/sudoers和/etc/sudoers.d/里有没有出现诡异的 NOPASSWD。/root/.ssh/authorized_keys有没有新增的不认识公钥。/etc/passwd里有没有 UID 0 的非 root 用户。判断 UID 0 账号用一条命令awk -F: $3 0 {print $1} /etc/passwd正常情况下只会输出root。如果多出来了别的账号基本可以断定被留了后门。4.3 NOPASSWD 与命令白名单的取舍很多自动化脚本为了方便给服务账号配了NOPASSWD也就是 sudo 不需要密码。我能理解自动化场景下的需求但建议限制范围不要用这种写法deploy ALL(ALL) NOPASSWD: ALL这相当于给 deploy 用户发了一张 root 终身会员卡。脚本需要免密也应该只免密那几条固定命令比如deploy ALL(ALL) NOPASSWD: /usr/bin/systemctl restart app关于 sudo 的密码缓存时间默认是 15 分钟意思是 15 分钟内同一终端再执行 sudo 不用重复输密码。时间短了影响效率时间长了风险变高。我在安全要求高的服务器上会把timestamp_timeout调成 5Defaults timestamp_timeout5对于内部堡垒机场景我甚至建议用 0每次都必须输密码。多一层确认多一分审计价值。5. 远程登录、密钥与密码策略账号权限的另一半5.1 SSH 公钥登录与 authorized_keys 的权限坑账号权限管理在远程场景下最容易被忽略也最致命的就是 SSH 配置。我见过很多人创建了用户、设置了密码然后把密码通过微信发给对方。这种做法一旦被截获或者聊天记录泄露服务器就等于拱手送人。生产环境我全面切换到密钥登录。生成密钥对ssh-keygen -t ed25519 -C deploycompany然后把公钥追加到目标用户~/.ssh/authorized_keys。这里权限必须严格遵守chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys chown -R username:username ~/.sshSSH 服务端有StrictModes保护如果authorized_keys或.ssh目录的权限过于宽松sshd 会直接忽略这个公钥文件导致明明密钥放对了却一直提示认证失败。我排查过太多次这种问题基本都是因为公钥文件是从别处拷贝过来属性不对。同时建议在/etc/ssh/sshd_config里设置PermitRootLogin no PasswordAuthentication no禁止 root 直接 SSH 登录禁止密码认证改用密钥。普通用户需要提权时走 sudo这样每一步操作都有记录。5.2 PAM 密码策略与账号锁定对于必须使用密码登录的内部系统密码策略不能靠自觉。Linux 的 PAM 模块可以在/etc/security/pwquality.conf里配置密码复杂度比如最小长度、字符种类。配合/etc/pam.d/common-password或/etc/pam.d/system-auth引入password requisite pam_pwquality.so retry3密码输入错误可以配置账号锁定。我现在常用的方案是pam_faillock连续失败 5 次锁定 15 分钟auth required pam_faillock.so preauth audit silent deny5 unlock_time900 auth required pam_faillock.so authfail audit deny5 unlock_time900注意不同发行版 PAM 配置文件的 organization 不同Ubuntu 和 CentOS 写法有差异改之前一定要先备份对应文件并且保持当前会话不退出用新窗口验证语法没问题再收工。直接在 PAM 上把自己锁在外面是非常经典的翻车事件。5.3 多用户环境的权限规划我最后想强调的是账号和权限不能“按人头拍脑袋”。我现在给服务器规划用户时会把账号分成几类账号类型示例登录方式权限策略人工运维账号zhangsanSSH 密钥sudo 白名单服务运行账号app-webnologin仅服务所需文件权限数据同步账号rsync-botnologin仅同步目录读写审计备份账号backup密钥限制命令定期轮换密钥服务运行账号一律nologin不给 Shell不给密码也不加入过大的组。多个服务共用一个账号虽然方便但出了问题无法区分责任排查隔离也难。能用组来控制的权限就不要单独给用户放行能把权限缩小到目录级别就不要给整个文件系统的写权限。6. 一次线上故障复盘权限管理到底能坏到什么样6.1 故障现象服务起不来日志全是 Permission denied去年有一回公司一个 Java 后端服务在发版后频繁报错日志里不断出现java.io.IOException: Permission denied at java.base/sun.nio.ch.FileDispatcherImpl.write0(Native Method)服务账号是appuser运行目录是/opt/backend日志目录是/opt/backend/logs。第一反应是日志目录没写权限但ls -ld一看是drwxr-xr-x属主 root属组 root。appuser 既不是属主也不在 root 组当然写不进去。6.2 排查过程从 id、ls -ld 到日志定位我这边完整的排查顺序是ps aux | grep java id appuser namei -l /opt/backend/logs/app.log ls -ld /opt/backend /opt/backend/logs getfacl /opt/backend/logs排查发现/opt/backend是 755/opt/backend/logs也是 755appuser 根本没有任何写权限。按常规操作我把日志目录属主改掉chown -R appuser:appuser /opt/backend/logs chmod -R 750 /opt/backend/logs本以为重启服务就恢复了结果照样Permission denied。这次报错变成了无法创建临时文件。用strace跟踪进程后发现服务启动时会往/opt/backend/tmp写文件而这个目录属主还是 root。这就是只看目标文件、不看全局导致的疏漏。6.3 修复与加固把账号权限做成制度化清单最后修复很简单把整个运行时目录按角色分配给 appuserchown -R root:root /opt/backend chown -R appuser:appuser /opt/backend/logs /opt/backend/tmp chmod 750 /opt/backend/logs但真正让我印象深刻的不是命令本身而是复盘时的一个问题为什么每次发版部署都要手动去调一次权限根本原因是部署脚本用 root 解压了压缩包解压出来的文件属主全是 root导致服务账号永远在“等 someone 来 chown”。从那以后我定了一个规矩应用代码包在打包阶段就固定好属主和权限部署脚本里把chown -R appuser:appuser作为固定步骤写入而不是等故障出现再补。权限管理如果每次都是被动救火那问题永远解决不完。把账号规划、目录归属、sudo 白名单、密钥策略这些全部固化成模板新服务器上线直接套用才算是真正把权限这件事从“技能”变成了“制度”。我个人在实际操作中的体会是Linux 账号和权限管理越是看起来基础的东西越值得花时间建立规范性流程。一次chmod 777能换来一时的省事但等服务器真的被人拿到权限代价就不是一个命令能挽回的了。