在公司内部服务器上,权限问题永远是绕不开的日常。最常见的场景就是同事来问:你能让我读一下 /data/sales 目录吗?我只要看里面的周报,不会动其他文件。以前处理这种需求,我脑子里先冒出来的是 chmod 777,或者建一个销售组,但每次都这么处理,迟早出问题。后来我在生产环境里用起了 setfacl,这种问题就变成了一个命令的事。setfacl 的全称是 Set File Access Control List,翻译过来是“设置文件访问控制列表”,作用和 chmod 类似,但精细度完全不同。chmod 只能管理 owner、group、other 三个角色,setfacl 可以在文件上同时挂几十个用户和组的权限,非常适合多人在同一台服务器上协同时使用。如果你是运维、开发或者经常给同事开权限的兼职管理员,这篇就适合你。
1. 传统 chmod 的三种权宜之计,为什么最终都得向 setfacl 妥协
1.1 多用户访问需求出现时,chmod 的三个短板
先说一个我自己的亲身经历。公司在 /data/project 下面放了一套销售项目的资料,里面既有合同模板,也有各区域的报价单。日常维护的人只有我和另一个运维,但销售总监希望所有销售人员都能查看,还希望财务部张姐能单独读取报表,同时还不允许普通员工看到薪资相关附件。
这种多层次的权限需求,如果只用 chmod,你基本只能从下面三个方案里选一个,而且都有明显的坑。
方案一:直接 chmod 777
这是最省事的,但也是最危险的。它意味着任何登录用户对文件都有读、写、执行权限,完全交给了系统里的所有账号。我见过有同事在线上配置目录上执行 chmod -R 777 /data,结果第二天配置文件被乱改,程序直接崩了。出了问题还找不到是谁干的,因为每个人都能动。
方案二:为每种权限组合建一个新系统组
这个思路本身没错,但维护成本会随时间增长。比如“张姐只读”需要一个组,“销售可读写”需要一个组,“测试组只执行”又需要一个组。员工入职的时候要把人加进三个组,离职的时候还得挨个确认,时间一长,管理几十个系统组的人脑子比谁也清楚不了多少。而且一个用户可能属于多个组,权限判断的复杂度也跟着涨。
方案三:用 sudo 或者直接改属主
sudo 是权限粒度很粗的工具,它执行的命令可能远比你想要的范围大。让一个只想读报表的同事用 sudo,等于给了他一条进入系统的万能钥匙。而改属主就更不现实了,文件只能有一个属主,不可能同时又属于张姐又属于小王。
所以,传统 chmod 的权限模型并不是不好,而是它把“所有者、所属组、其他人”三个槽位设计得太固定了。当你的权限需求出现“给某一个人单独开权限”这种诉求时,三个槽位根本装不下。
1.2 setfacl 解决的正是权限分配中的交叉场景
setfacl 的思路和 chmod 完全不同。它不是在三个固定槽位里挑一个,而是给文件挂一个权限条目列表。在这个列表里,你可以为张三写一条规则,为李四写一条规则,为财务组写一条规则,任何一条规则都只影响一个主体,互不干扰。
实际使用中,setfacl 最大的价值就是省事。你不需要为了一个临时权限去创建一个系统组,也不需要为了给某个人读权限就修改文件属主。你只需要执行一条命令,把用户的名字和权限挂到文件上,不想要的时候再删掉就行了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. setfacl 的底层逻辑:搞懂五类 ACL 条目和 mask 掩码
2.1 ACL 条目的六种身份
打开一个文件的 ACL,你用 getfacl 看到的内容,本质上是一个权限列表。常见的 ACL 条目有以下六种。
| ACL 条目 | 控制对象 | 示例命令 |
|---|---|---|
| user:: | 文件属主 | setfacl -m u::rwx file |
| user:用户名 | 指定用户 | setfacl -m u:zhangsan:r-x file |
| group:: | 文件属组 | setfacl -m g::rwx file |
| group:组名 | 指定组 | setfacl -m g:sales:r-x file |
| mask:: | 对上述所有命名主体设置最高权限上限 | setfacl -m m::r-x file |
| other:: | 其他人 | setfacl -m o::--- file |
user:zhangsan 这类带具体用户名的条目,叫做“命名用户条目”。group:sales 这类带具体组名的条目,叫做“命名组条目”。系统和 chmod 一样,会根据当前登录身份决定使用哪条规则:如果你正好是文件属主,就走 user:: 这条;如果你命中了命名用户条目,就走你自己的那条;如果你属于某个命名组,就走组对应的那条;如果都没有,就落到 other:: 上。
听起来好像只是多挂了几条规则,但真正让我觉得 setfacl 值得学习的地方,是它的 mask 设计。
2.2 mask 为什么能让权限“看起来和实际不一致”
mask 是 ACL 里最容易让人误解的一个字段。它不是说“每个用户独立判断”,而是说“任何命名用户或命名组的权限,都要经过 mask 这层闸门再做一次与运算”。
举个例子。
code复制$ setfacl -m u:zhangsan:rwx /data/project
$ getfacl /data/project
# file: /data/project
# owner: deploy
# group: dev
user::rwx
user:zhangsan:rwx
group::r-x
mask::rwx
other::---
这时张姐的有效权限是 rwx。但如果后续有人执行了 chmod 750,mask 会被同步改成 r-x,你再看 getfacl 的输出就会发现:
code复制$ getfacl /data/project
# file: /data/project
# owner: deploy
# group: dev
user::rwx
user:zhangsan:rwx #effective:r-x
group::r-x
mask::r-x
other::---
张姐的 ACL 条目里写着 rwx,但 effective 是 r-x,write 权限被 mask 拦住了。这也是我最开始用 setfacl 时踩过最诡异的坑之一,权限明明加了,却怎么都不生效,最后发现是 mask 在作祟。
2.3 effective 权限的计算方式
effective 权限 = ACL 条目权限 & mask 权限,这是按位做“与”运算。把 r、w、x 分别看成三个开关,只有 ACL 条目和 mask 同时打开某个位,这个位才最终生效。mask 存在的意义就是给你一个统一闸门,当你想临时收紧所有命名用户的权限时,不用一条条改,改一个 mask 就够了。
我一般会建议团队里的人都记住一个判断顺序:先看自己是“属主、命名用户、属组成员、命名组成员、其他人”里的哪一类,再看 mask 是否允许。很多权限排查的问题,其实都出在这个顺序上。
3. 实战:给指定用户开一个目录的访问权限,并让新文件自动继承
3.1 动命令之前,先想清楚三个问题
第一次用 setfacl 的时候,我建议不要上来就敲命令,先做三个判断。
- 是给用户还是给组?给用户用
u:用户名,给组用g:组名。 - 给什么权限?读 r、写 w、执行 x。对目录来说,x 表示能进入目录,没有它连
ls都跑不了。 - 要不要递归到子目录?已有的子目录和文件需要
-R,如果不加-R,setfacl 只影响你指定的目录本身。
这三个问题想清楚,命令基本就成型了。
3.2 给目录和已有文件授权,注意文件和目录权限要分开
假设现在的目录是 /data/project,属主是 deploy,属组是 dev。需求是让审计人员 zhangsan 只读,让销售组 sales 能读写,但不需要执行任何脚本。
先给用户和组加上权限:
code复制setfacl -R -m u:zhangsan:r-x /data/project
setfacl -R -m g:sales:rwx /data/project
这里对 sales 组给的是 rwx,因为对目录来说,光有 rw 是进不去的。但如果目录里有很多普通文件,rwx 会把这些文件的可执行位也打开,显然不合适。更规范的做法是把文件和目录分开处理。
code复制find /data/project -type d -exec setfacl -m u:zhangsan:r-x {} \;
find /data/project -type d -exec setfacl -m g:sales:rwx {} \;
find /data/project -type f -exec setfacl -m u:zhangsan:r-- {} \;
find /data/project -type f -exec setfacl -m g:sales:rw- {} \;
这样做以后,目录有进入和列目录的权限,文件只有读写本身,不会平白多出执行位。虽然命令变长了,但在生产环境里这样做更干净。
3.3 default ACL:让新文件也自动继承权限规则
上面的命令只处理了目录里已有的文件,之后新建的文件不会自动带这些规则。要让新文件继承权限,得给目录设置 default ACL。
code复制setfacl -d -m u:zhangsan:r-x /data/project
setfacl -d -m g:sales:rwx /data/project
设置完以后用 getfacl 查看:
code复制$ getfacl /data/project
# file: /data/project
# owner: deploy
# group: dev
user::rwx
user:zhangsan:r-x
group::r-x
group:sales:rwx
mask::rwx
other::---
default:user::rwx
default:user:zhangsan:r-x
default:group::r-x
default:group:sales:rwx
default:mask::rwx
default:other::---
在这个目录里新建文件,新文件会套用 default ACL 作为模板。有一点要特别注意:默认 ACL 给的是模板,新文件的最终权限还要看创建进程自己申请的权限位。比如 vim 保存文件时通常会创建一个 0644 左右的文件,那默认 ACL 里的 rwx 就会被限制成 rw-,不会变成可执行文件。所以我每次设置完 default ACL,都会在目录里新建一个测试文件,用 getfacl 看一眼实际结果,不靠猜。
3.4 用真实身份验证,不要只看 getfacl
getfacl 显示的是规则,不是实际感受。我习惯用对应用户身份进行验证。
code复制su - zhangsan -c "ls -l /data/project"
su - zhangsan -c "cat /data/project/report.txt"
su - zhangsan -c "touch /data/project/test.txt" # 预期是 Permission denied
如果权限配置有误,这一步基本能立刻暴露问题,也能避免把带病配置直接交出去。
4. 踩过的坑:chmod、cp、mv、tar 都会悄悄干扰 ACL
4.1 chmod 会改写 mask,把你的授权“削掉一截”
这是 setfacl 使用中最容易翻车的地方。你给一个目录加好了多个用户的 ACL,中途有人做了一个非常常规的操作:chmod 750 /data/project。这个操作本身没有恶意,但它会同步修改 mask,把 mask 从 rwx 改成 r-x,然后所有命名用户和命名组的 w 权限全部被屏蔽。
我遇到过的情况是:同事在调试程序时顺手执行了一个 chmod,第二天项目组的反馈是“突然写不进去了”。最后定位到问题,才发现是 chmod 改了 mask。
解决办法有两个思路:
- 记住:当你设置了 ACL 之后,别再依赖 chmod 去调整权限。需要改 mask 时,用
setfacl -m m::rwx /data/project这种方式改。 - 如果已经发生了误操作,不要急着一条条重加,直接把 mask 改回你想要的值就行。
code复制setfacl -m m::rwx /data/project
然后 getfacl 确认一下所有 effective 权限是否恢复。
4.2 cp、mv、tar 对 ACL 的保留情况完全不同
文件从一个地方复制到另一个地方,ACL 不一定跟着走。这里面有几个规则,我踩过几次才彻底记住。
mv在同一个文件系统内是改名操作,inode 不变,ACL 会保留。如果跨文件系统,mv 会退化成复制加删除,ACL 可能就丢了。cp默认只复制内容和常规权限位,ACL 默认不复制。想保留 ACL,必须用cp -p或者cp --preserve=all。tar备份时,如果不加--acls,默认也不会打包 ACL 信息。恢复的时候也要用对应的参数。rsync同步目录时,用-A参数才会保留 ACL。
我维护的服务器上,备份脚本里写的就是:
code复制tar --acls -cf backup.tar /data/project
如果没有 --acls,一旦灾难恢复完成,你会发现所有 ACL 全部丢失,所有特殊授权都要重建,那个画面很痛苦。
4.3 NFS 和备份工具可能导致 ACL 静默丢失
除了单机复制,网络文件系统也容易让人踩坑。NFSv3 不支持 POSIX ACL,挂载一个 NFS 卷之后,setfacl 可能会直接报错“Operation not supported”。NFSv4 有自己的 ACL 模型,和本地文件系统的 POSIX ACL 并不是一回事,需要使用 nfs4_getfacl 和 nfs4_setfacl 这组命令来管理。
另外,很多备份恢复工具并不支持 ACL。排查这类问题时,我建议先确认文件系统类型,再看命令是否真的把 ACL 写进去了,最后用 getfacl 验证。
code复制df -T /data/project
getfacl /data/project/restored_file
遇到 ACL 丢失,不要慌,先从这几个维度排查,基本能覆盖绝大多数原因。
5. 批量场景下的 setfacl 管理技巧:备份、恢复和权限回收
5.1 用 getfacl 做 ACL 快照,迁移目录时不怕丢规则
配置好一套 ACL 不容易,尤其是一堆用户和组规则。我会定期用 getfacl 把规则导出成文本,方便回滚和迁移。
code复制getfacl -R /data/project > /backup/project-acl.txt
恢复的时候直接:
code复制setfacl --restore=/backup/project-acl.txt
这个 --restore 会读取文件里记录的路径,自动重建 ACL。注意备份文件里的路径是 /data/project,如果你把目录迁移到新位置,建议先在目标位置用 setfacl --restore 测试一次,确认路径匹配。
5.2 批量给多个用户或组加同一份权限
有时要给十几个实习生统一开只读权限,一个个敲命令太累。我习惯直接写个 for 循环:
code复制for user in alice bob carol; do
setfacl -m u:${user}:r-x /data/project
done
如果要对目录里的所有文件生效,配合 find 使用:
code复制find /data/project -type f -exec setfacl -m u:zhangsan:r-- {} \;
写脚本的时候注意变量加 ${},避免边界问题。这些命令在生产环境执行前,最好先在测试目录跑一遍。
5.3 正确回收权限,避免安全残留
权限不能只加不收。人员离职或者项目结束后,我会定期清理 ACL。
删除单个用户的规则:
code复制setfacl -x u:zhangsan /data/project
清理目录下所有扩展 ACL,恢复到纯 chmod 管理的状态:
code复制setfacl -b /data/project
递归清除目录里所有文件上的 ACL:
code复制setfacl -Rb /data/project
清理完以后,用 getfacl 再看一眼,能确认没有残留。权限这东西,宁可多验证一步,也不要在安全审计时被动发现。
6. 一些靠经验换来的 setfacl 使用建议
第一条建议是不要滥用。setfacl 确实很灵活,但如果一个目录上挂了四五十条命名用户规则,那权限审计会变成一场噩梦。比较合理的做法是:稳定的岗位权限用系统组管理,临时性的、个例化的访问需求用 setfacl 来覆盖。这样既保持灵活性,又能让组结构保持稳定。
第二条建议是把 ACL 当成配置管理的一部分。我会在项目的 README 里记录关键目录的 ACL 设计,或者把 getfacl 快照直接放在配置仓库里。这样哪天有人误操作了,可以快速用 setfacl --restore 恢复。
第三条建议是充分相信 getfacl 的输出。遇到权限问题,先看自己有哪类身份,再看 mask,最后看 effective。很多时候,问题不是出在 ACL 没配置,而是出在 mask 或者文件系统不支持。
我在生产环境里用 setfacl 最多的场景,是给运维同事开审计日志目录的只读权限。一条规则挂上去,一个季度都不用动。比起反复 chmod 和建组,这个命令确实省心很多。如果你还没有用过 setfacl,我建议从自己的测试环境开始,把一个目录的权限反复折腾几遍,很快就能掌握。
