1. 内容整体设计与思路拆解
1.1 用户与文件权限:一枚硬币的两面
Linux 系统里的用户管理和文件目录配置,说白了就干一件事:搞定"谁能进、能看什么、能改什么"。这两块内容从来不是割裂的——用户是身份的来源,文件权限是身份的落点。你创建了一个用户,不给他配置合理的目录权限,他登录进来要么一无所有,要么四处碰壁;你设计了一套漂亮的目录结构,没有配套的用户和组策略,权限要么形同虚设,要么把系统锁得连管理员都想砸键盘。
我在帮朋友调试服务器时经常遇到个典型场景:某公司新来了一批实习生,管理员图省事,直接让他们都用 root 或同一个公共账号登录。结果没过两周,有人误删了另一个项目组的配置文件,整个服务重启后起不来。这事儿真不能怪实习生手滑,错在权限设计没跟用户体系挂钩。Linux 安全模型的精髓,就是把「谁」和「能做什么」拆成两个独立维度,再由文件系统把它们捏合在一起。
所以这篇文章我不会只讲几条命令怎么敲,而是把「用户管理」和「文件目录权限」作为一套组合拳来拆:先理清身份体系怎么建,再搞明白权限位怎么读怎么写,最后用真实场景把它们串起来。这样你学完之后,遇到的不只是"会敲命令",而是"知道该敲什么命令、为什么这么敲"。
1.2 为什么这两块必须放在一起学
可以这么说:文件权限离开了用户体系,就是一堆没有主人的标记;用户离开了文件权限,就是一群无处安放的身份。很多初学者先翻用户管理教程,背熟useradd和passwd之后,转头学chmod,明明都会,但一遇到实际问题就卡壳。为什么?因为真实世界的问题长这样:
/data/project_a目录,想让 A 组能读写、B 组只能读、其他人都不能碰,你怎么配?
这种问题,你单独翻任何一章教程都找不到答案,必须把用户、组、权限位、粘滞位这些概念拼起来才能解。我在实际写配置的时候,脑子里运行的是一个"三要素模型":
- 系统视角:用户和组是写在
/etc/passwd、/etc/group里的账号条目。 - 进程视角:你登录后,Shell 进程带着你的 UID/GID 去访问文件。
- 文件视角:每个文件记录着自己的属主(owner)、属组(group)和权限位(mode)。
三个视角对齐了,权限问题就能迎刃而解。后续所有命令,其实都是在调整这三个视角里的某个属性而已。
2. 用户管理核心细节与实操要点
2.1 用户账号的创建、修改与删除:每个参数都有讲究
2.1.1 useradd 的正确姿势
先用最常用的一条命令打底:
useradd -m -s /bin/bash -G wheel,develop -c "Zhang San" zhangsan这里的每个参数都不白给:
-m:自动创建家目录/home/zhangsan,并把/etc/skel里的初始化文件(.bashrc、.bash_profile等)拷贝进去。不加这个参数,用户登录后连个落脚点都没有,直接陷在/下面。-s:指定登录 Shell。我习惯统一指定/bin/bash,避免用户拿到一个无从下手的/bin/sh。-G:附加组。多个组用逗号分隔。注意区分-g(主组)和-G(附加组)。主组决定用户创建文件时默认的属组;附加组决定用户能访问哪些额外授权的目录。-c:备注信息,写全名、部门、用途都行,方便后续管理。
提示:新用户在能登录之前必须设置密码,否则账号处于锁定状态。
passwd zhangsan2.1.2 创建用户之前先想清楚三件事
我见过太多人上来就批量建账号,结果三个月后收拾烂摊子。创建用户前花一分钟问自己三个问题:
第一,这个用户的主组该是什么?如果公司有明确部门划分,比如研发、运维、产品,建议按团队建组,把用户的主组指定到对应团队,而不是默认新建一个和用户名同名的私有组。这样做的好处后面会体现:共享目录只要把属组改成dev,整个团队就自动获得访问权。
第二,这个用户需要哪些附加组?比如需要执行管理命令的扔进wheel组,需要访问数据仓库的加data组。附加组可以随时增减,但想清楚再给,比事后追着改要好。
第三,这个用户的使用周期有多长?临时项目组用户,干脆在用户名里带个标记,比如proj_x_temp,到期直接删。账号清理这件事,前期命名规范了,后期效率能翻一倍。
2.1.3 usermod 与 userdel 的踩坑记录
usermod是修改用户属性的万能钥匙,最常用的几个组合:
# 把 zhangsan 加进 docker 组,同时从 oldgroup 组移除 usermod -G docker,oldgroup zhangsan # 注意:-G 后面如果不加其他组,会覆盖用户原有的全部附加组 # 所以需要先看一下当前组 groups zhangsan我在这儿踩过一个很典型的坑。批量给一批用户加sudo权限时,用了一条命令:
usermod -G sudo user_a执行完发现user_a原来所在的那个项目组也没了,共享目录进不去。原因就是-G是全量覆盖,不是追加。后来我改成先读原组、再追加新组,问题解决。正确的追加方式其实更稳:
usermod -aG sudo user_a-a表示 append,这是给用户加组的推荐做法,尤其在生产环境上。
删除用户的坑,集中在userdel上:
# 只删账号,保留家目录和邮件文件 userdel zhangsan # 连家目录和邮件池一起删 userdel -r zhangsan生产环境上我建议养成习惯:先看清楚家目录里有没有业务数据再决定要不要-r。遇到过某团队把备份脚本写在用户家目录下,删用户的时候加了-r,连同唯一一份没入库的备份一起带走了。所以我现在都先tar打包家目录,再删用户。
2.2 用户组管理与 sudo 授权
2.2.1 组管理的底层逻辑
用户组解决的是"批量授权"问题。总不能让管理员对着五十个用户挨个设置文件权限。合理做法是:把用户按职能分组,文件权限只跟组挂钩。
# 创建组 groupadd dev groupadd ops # 修改组名或 GID(很少用,但要知道) groupmod -n developers dev # 删除空组 groupdel developers查看用户属于哪些组,用:
id zhangsan groups zhangsanid输出里包含了 UID、GID、附加组列表,排查权限问题时的第一道命令就是它。
2.2.2 sudo:把钥匙分成权限粒度
sudo是 Linux 权限管理里最容易被误解的工具。不是"给 root 权限",而是"按规则授予特定命令的执行权"。配置写在/etc/sudoers里,强烈建议用visudo编辑,因为它自带语法检查,能防止你把自己锁在门外。
一个常规配置模板:
# 允许 wheel 组用户执行所有命令,但需要输入自己的密码 %wheel ALL=(ALL) ALL # 允许运维组用户免密执行 systemctl 相关命令 %ops ALL=(ALL) NOPASSWD: /usr/bin/systemctl # 允许某用户只可执行指定脚本,不授予其他 root 权限 deploy ALL=(root) /usr/local/bin/deploy.sh我自己的习惯是:把真正需要管理机器的用户放进wheel组,日常操作全部用自己的普通账号,只有安装软件、改系统配置时才sudo。运维组账号只授予和服务管理相关的命令,避免一上来就给全套 root。
注意:
NOPASSWD虽然省事,但风险显著提高。机器如果被入侵,相当于 root 命令入口全开。除非是无人值守的自动化脚本,否则不建议给交互式账号开免密。
2.3 用户管理的实操心得与检查清单
我把自己平时批量创建用户的流程贴出来,这条流程在不同机器上反复用,比较成熟:
# 1. 批量创建小组用户,主组按角色划分 groupadd -r webapp 2>/dev/null || true for name in zhangsan lisi wangwu; do useradd -m -s /bin/bash -g webapp -G webapp "$name" # 交互式设置密码,避免脚本里明文存储 echo "设置 $name 的密码:" passwd "$name" done # 2. 验证 id zhangsan tail -n 5 /etc/passwd ls -ld /home/*批量操作完,一定要做三项验证:
tail /etc/passwd:确认账号创建成功,UID 没有重复。ls -ld /home/*:确认家目录属主、权限正常。id 用户名:确认主组、附加组符合预期。
3. 文件目录权限深度解析
3.1 权限位:rwx 到底在说什么
ls -l输出的第一列长这样:
-rw-r--r-- 1 root root 1234 Jun 8 10:30 config.yml drwxr-xr-x 2 root root 4096 Jun 8 10:30 scripts第一个字符表示类型,-是普通文件,d是目录,l是软链接。后面九个字符分成三组,每组三位,分别对应属主、属组、其他用户的权限。
对普通文件来说:
r:读文件内容w:修改文件内容x:把文件当作程序执行
对目录来说,含义完全不一样,这是很多人的知识盲区:
r:能列出目录里有啥(ls能看到文件名)w:能在目录里创建、删除、重命名文件x:能进入这个目录(cd),访问目录内文件的具体属性
注意:目录没有x权限,就算有r权限,你也没法cd进去读取文件内容,只能干瞪眼看到名字。这是我排查权限问题时遇到最多的一种奇怪现象——用户说"我能看到目录但打不开里面的文件",一查目录权限dr--r--r--,全是读没有执行,自然进不去。
数字表示法三组权限各按二进制累加:r=4、w=2、x=1。比如755是属主rwx(7)、属组r-x(5)、其他r-x(5)。换算关系记熟,比每次查表强:
| 数字 | 权限 | 说明 |
|---|---|---|
| 0 | --- | 无权限 |
| 1 | --x | 仅可执行/进入 |
| 2 | -w- | 仅可写(目录无执行会很怪异) |
| 3 | -wx | 可写可执行 |
| 4 | r-- | 只读 |
| 5 | r-x | 可读可执行/进入 |
| 6 | rw- | 可读可写 |
| 7 | rwx | 全部权限 |
3.2 chmod、chown、chgrp 的使用要点
3.2.1 chmod 的两种写法
符号法适合精细调整,数字法适合整体设定。两者我都用,但各有适用场景:
# 数字法:直接设定完整权限,适合新目录一次性配置 chmod 750 /data/project_a # 符号法:在现有权限基础上增减,适合微调 chmod g+w /data/project_a chmod o-rx /data/project_a chmod u+x /data/deploy.shchown的写法有一个很容易记反的点。正确语法是「先属主,后属组」,用冒号或点号分隔:
# 将目录属主改为 zhangsan,属组改为 dev chown zhangsan:dev /data/project_a # 只改属主,不改属组 chown zhangsan /data/project_a # 只改属组(等价于 chgrp) chown :dev /data/project_a3.2.2 递归权限操作的危险性
递归修改权限是排查问题时的常用手段,但也是最容易制造更大问题的操作:
chown -R zhangsan:dev /data/project_a-R会把目录下所有文件和子目录一并改掉。如果/data/project_a里有几个特殊文件需要保留 root 权限(比如某个需要 root 才能读的密钥文件、某个受保护的系统脚本),这么一改,权限模型被全部打平,用户能读到本不该读的内容。
我的防守策略是:先看目录下有什么特殊文件,再用find定向排除:
# 先把整体属主改掉 chown -R zhangsan:dev /data/project_a # 再把密钥文件改回 root 专属 chown root:root /data/project_a/.keys/private.pem chmod 600 /data/project_a/.keys/private.pem3.2.3 新建文件默认权限:umask
umask决定新建文件和目录的默认权限。多数发行版默认umask 022,所以新建文件是644(666去掉组和其他用户的写权限),目录是755(777去掉写权限)。
如果系统是多人协作环境,想强制所有新建文件的默认权限更保守,可以把umask 027写进/etc/profile或用户的.bashrc:
umask 027这样新文件默认是640,组外完全不可读。这个细节经常被忽略,但它其实直接影响数据泄露的风险面——默认权限太宽,等用户想起来收紧,敏感数据早就被人读走了。
3.3 特殊权限位:setuid、setgid、粘滞位
3.3.1 setuid:让普通用户临时获得属主身份
在可执行文件上设置setuid(chmod u+s或数字4xxx),用户执行这个文件时,进程的「有效用户 ID」会临时变成文件属主。
最典型的就是passwd:
ls -l /usr/bin/passwd -rwsr-xr-x 1 root root 59976 Feb 6 2023 /usr/bin/passwd那个s就是 setuid 位。普通用户执行passwd时,进程临时以 root 身份修改/etc/shadow,改完立刻放弃特权。这是 Linux 中最精巧的权限设计之一,但也是安全重灾区。自己写脚本时不要随便开启 setuid,脚本解释器加 setuid 越权执行是后门最常见的手法。当然如果系统里出现了一个非 root 属主的 setuid 程序,基本可以认为已经被入侵了,这是排查时最优先需要关注的信号。
3.3.2 setgid:让子文件继承目录属组
目录设置setgid(chmod g+s或数字2xxx)后,在这个目录下新建的文件或子目录,属组自动继承目录的属组,而不是创建者自己的主组。
这条规则是多人共享目录最核心的权限设计。举个例子:/data/share属组是dev,如果这个目录没有setgid,A 用户在里面创建的文件属组是 A 的主组,B 用户即使同属dev组,也可能因为文件属组是 A 的私有组而无法访问。开了setgid,所有新文件自动变成dev组,团队协作瞬间顺畅:
chown root:dev /data/share chmod 2775 /data/share ls -ld /data/share drwxrwsr-x 2 root dev 4096 Jun 8 10:30 /data/share注意权限位显示drwxrwsr-x,rws里那个s就是 setgid 位,与普通r-x长得不一样。这个细节用眼睛看比记忆更有效。
3.3.3 粘滞位:/tmp 的守护神
/tmp目录的权限是drwxrwxrwt,最后的t就是粘滞位(chmod +t或数字1xxx)。粘滞位的作用很直接:目录里所有人都可以创建文件,但只有文件属主(或 root)能删自己的文件,谁也别想动别人的。
没有粘滞位,共享写目录会变成一场灾难——任何有写权限的人都能删掉别人的临时文件。所以只要是需要多用户共享写入的目录,比如团队的临时文件目录、上传目录,一律加粘滞位:
chmod 1777 /data/shared_tmp我见过的经典事故就是某团队把上传目录设成了777,没有粘滞位。结果有用户清理磁盘时直接误删了别人的文件,甚至不能算"误"——他有权限,删得理直气壮。加一行chmod +t就解决的纠纷,没必要用制度去堵。
3.4 权限检查的实战清单
每次配完权限,我不会急着收工,而是按这组问题绕一遍:
- 目录属主是谁?属组是谁?其他用户有什么权限?
- 目录有没有
setgid?新文件和新的子目录会不会继承属组? - 文件执行权限给到了谁?是程序要执行还是用户要执行?
umask默认值是多少?新文件会不会默认过宽?- 有没有隐藏的特殊权限位?哪些文件不该有
s位?
这些检查看着琐碎,但每一个疏漏都对应一起真实的线上事故。养成习惯,比事后追责有意义得多。
4. 实操过程与核心环节实现
4.1 场景设定:为一个模拟项目搭建多用户协作环境
现在把一个完整场景拆开走一遍。假设某公司新启动了一个模拟项目 X,团队成员包括后端开发、运维和测试三类角色,共享一台 Linux 服务器。目录结构要满足这些需求:
- 后端开发人员可以读写
/data/project_x/code - 运维人员可以查看所有日志、执行服务重启脚本,但不能改代码
- 测试人员只能查看部署产物,不能修改
- 所有人在
/data/project_x/shared里可以自由交换临时文件,但不能删别人的文件 - 日志目录可以由服务进程写,但不允许普通用户直接乱改
4.2 用户与目录的落地配置
4.2.1 创建用户组与用户
# 创建三个角色组 groupadd dev_x groupadd ops_x groupadd qa_x # 每类角色各建一个示例用户 useradd -m -s /bin/bash -g dev_x -G dev_x -c "Dev User" alice useradd -m -s /bin/bash -g ops_x -G ops_x -c "Ops User" bob useradd -m -s /bin/bash -g qa_x -G qa_x -c "QA User" carol # 设置初始密码 passwd alice passwd bob passwd carol4.2.2 创建目录并设定权限
mkdir -p /data/project_x/{code,logs,shared,deploy} # 项目根目录收紧,防止无关用户闯进来 chown root:dev_x /data/project_x chmod 750 /data/project_x # code 目录:开发组专属,setgid 保证组内共享 chown root:dev_x /data/project_x/code chmod 2770 /data/project_x/code # logs 目录:运维可读,服务进程可写,普通用户不可读 chown root:ops_x /data/project_x/logs chmod 750 /data/project_x/logs # shared 目录:所有项目成员可读写,粘滞位保护个人文件 chown root:dev_x /data/project_x/shared chmod 1770 /data/project_x/shared4.2.3 需要解释的权限方案
code目录权限2770:第一个2是 setgid,后三位770表示只有属主和属组(dev_x)能读写,其他人一律拒绝。这样 alice 在 code 里创建的所有文件自动属于dev_x组,bob 和 carol 即使有服务器登录权限,也无法偷看代码。
logs目录的权限750:属主 root 完全控制,属组ops_x可读可进入,其他用户完全不可访问。运维需要看日志时,通过sudo读取或者临时加组。
shared目录权限1770:1是粘滞位,770是组内全读写。组里任何用户都能创建文件,但只能删自己的。跨组交换文件时,可以复制进去,但不要想着替别人清理,删不了。
这套配置对外的效果是:项目成员各司其职,权限既不过度收窄影响协作,也不过度敞开制造风险。整个过程我不需要用setfacl这种高级工具,基础权限位加特殊权限就足够覆盖需求。
4.3 从根上规范配置:目录权限的复核流程
配置完成之后,我习惯系统性复核一遍:
# 1. 列出 /data 下所有内容,检查属主、属组、权限位 ls -l /data/project_x/ # 2. 用 find 找到所有权限过宽的文件 find /data/project_x -perm -o+w -type f # 3. 用 find 查找所有 setuid/setgid 文件 find /data/project_x -perm -4000 -o -perm -2000 # 4. 抽查新建文件的默认权限 sudo -u alice touch /data/project_x/code/test_probe.txt ls -l /data/project_x/code/test_probe.txttest_probe.txt的属组应该是dev_x,权限应该是664或660(取决于umask),如果出现其他属组,说明 setgid 没生效或者 umask 配置有误。这个"探针文件"技巧是我经常用的,比肉眼猜靠谱得多。
4.4 使用 SSH 密钥与锁定弱口令
用户管理除了命令层面的操作,还有一层容易被忽略:认证方式。密码认证在公网服务器上就是一场持久战,密码不强,扫描脚本跑一个晚上就能撞开。我一直建议:
- 生产环境开启 SSH 密钥登录,并禁止 root 直接密码登录。
- 每个用户在服务器上拥有自己的密钥对,私钥留在本地,公钥放入
~/.ssh/authorized_keys。 - 至少保留一个管理通道(比如 Web 控制台的 VNC 或带外管理),防止密钥丢失后人进不去。
密钥管理的核心理念是:账号是身份的标记,密钥是身份的凭证。把凭证和账号绑定好,后续怎么审计、怎么回收权限都有据可查。
5. 常见问题与排查技巧实录
5.1 高频报错速查表
| 报错或现象 | 常见原因 | 排查/解决 |
|---|---|---|
Permission denied | 文件或目录的属主/属组/权限位不匹配 | ls -l和id对照检查 |
su: Authentication failure | 用户没有设置密码或密码过期 | passwd 用户名重置 |
sudo: user is not in the sudoers file | 用户没加入 sudo 组 | usermod -aG wheel 用户名,或确认 sudoers 配置 |
cannot create directory: Permission denied | 父目录无写权限,或无 setgid 但属组不对 | ls -ld父目录,chmod调整 |
| 能看到目录名但进不去 | 目录有r无x | chmod +x 目录补执行位 |
删不掉/tmp里的文件 | 粘滞位生效,只有属主能删 | 确认属主,或改用 root |
莫名Operation not permitted | 特殊文件系统挂载参数,如nosuid、ro | mount -l查看挂载参数 |
5.2 权限类问题排查的标准流程
遇到权限问题,我有一套固定的排查路径,倒着走比顺着想快得多:
第一步,确认当前身份:
id第二步,确认目标文件的属主和权限:
ls -ld /path/to/file第三步,沿着目录逐级往上查。这是最容易忽略的一步。比如你检查/data/project_x/code/readme.md发现权限644,属主正确,但提示拒绝访问,八成是/data/project_x、/data甚至/这层路径里某级目录的权限把用户挡在了门外。排查时要一层层看:
namei -l /data/project_x/code/readme.mdnamei会显示从根目录到目标文件的每一层目录权限,一眼定位是哪一层卡住了。这个命令平时用的人不多,但在排查"路径可达性"问题时效率极高。
第三步,检查用户是否在文件属组中:
groups 用户名第四步,考虑特殊权限和挂载选项:
getfacl /data/project_x/code/readme.md mount | grep /data5.3 那些文档里不会写的踩坑经验
第一,不要在多人共享的目录里直接用chmod 777图省事。777意味着任何登录用户都能读写,如果目录里还有可执行文件,基本等同把系统门敞开了。正确的共享目录姿势是:2770加 setgid,或者1770加粘滞位。多花一秒钟想清楚,比事后删除后门快得多。
第二,删除用户前先检查 crontab。很多自动化脚本挂在用户级别的 crontab 下,直接userdel会把定时任务一起带走。我的习惯是删用户前三连查:
crontab -u 用户名 -l find /home/用户名 -type f -name "*.sh" find / -user 用户名 -type f 2>/dev/null | head -50第三条从根扫一遍这个用户留下的文件,虽然慢,但有时候能救回一个关键脚本。
第三,小心chown -R的连带效应。曾经我把整个/data的属主误改成某个用户,导致所有系统服务因为权限变更起不来,排查了两小时才定位到。从那以后,凡是有生产服务的路径,我用chown前一定会先执行一次:
find /data -maxdepth 2 -uid 0 -printf "%u %p\n" | less看清楚哪些部分是 root 专属,再决定要不要全量改。
第四,用户组不要轻易删。删groupdel前系统会检查是否还有用户以该组为主组,有的话会拒绝删除。但附加组引用不会拦截,导致组删掉后,某些用户配置文件里残存的 GID 变成一个无归属数字,后续再对应到新组时可能产生权限错乱。我处理这类问题的方式是:保留空组,不主动清理,最多改个名表明废弃。
第五,sudo配置改错了,留一扇后门。用visudo编辑时,语法错误会直接报错拒绝保存,但如果你编辑完成后直接退出,忘了用sudo -l验证修复,下一次sudo可能要等很久才能生效。我的习惯是每次改完 sudoers 就开一个新的 SSH 会话,跑一下sudo -l确认用户能正常提权,再关闭旧会话。如果感觉自己随手改的规则可能有问题,先tmux开个隐藏会话,避免配置错误后连退路都断了。
5.4 用脚本辅助日常巡检
权限和用户管理的日常维护,靠人记不现实,我习惯写巡检脚本挂定时任务。即使不写全自动脚本,一条段命令组合也够用:
# 查找系统里的空密码用户(重点安全项) awk -F: '($2 == "") {print $1}' /etc/shadow # 查找 UID 为 0 的非 root 账号(特权账号排查) awk -F: '($3 == 0) {print $1}' /etc/passwd # 罗列所有可登录用户 awk -F: '($3 >= 1000) && ($7 != "/sbin/nologin") {print $1, $3, $7}' /etc/passwd把这三条加进每周定时巡检,能发现大部分危险账号。再配合查看/var/log/secure里 SSH 登录失败次数,就能摸清服务器被扫描、被尝试爆破的频率,提前发现入侵前兆。
6. 写在最后的经验小结
踩过几次权限配置和用户管理的坑之后,我越来越理解为什么 Linux 设计者把用户体系和文件权限做得这么紧密、这么细致。它不是故意给你添堵,而是把「谁能做什么」这个安全问题的答案,刻在从内核到命令行每一层的设计基因里。掌握它不需要你背下所有命令的参数,但需要你形成一整套判断习惯:谁能进、能看什么、能改什么、出了事能追查到谁。这套判断习惯,比任何一条单一命令都值钱。
最后再分享一个我实际使用中的小技巧:权限配置完,别急着给用户账号,自己先用那个用户的身份完整走一遍所有操作(用su 用户名临时切换过去,跑一遍建目录、写文件、读文件、执行脚本)。体验一遍用户真正会做的事情,比你对着权限位看半天更能发现配置漏洞。这个"角色扮演"式的验收动作,我每次配完都会做,从来没有白做过。