「Permission denied」到底是谁不给权限:读透 Linux 文件权限的完整排查手册

「Permission denied」到底是谁不给权限:读透 Linux 文件权限的完整排查手册

服务器运维里最高频、也最容易让人抓狂的一句话就是 Permission denied。Nginx 报 403、PHP 写文件失败、cron 脚本跑不动、SSH 密钥登不进去 —— 表面都是权限问题,但根因可能藏在权限位的三个数字里、藏在 umask 里、藏在 POSIX ACL 里,甚至藏在一个看不见的 chattr +i 属性里。

这篇文章给你一套完整的排查思路:从最基础的 ls -l 权限位读法,到 umask 的隐式影响,再到 ACL 和 chattr 这两个「隐藏关卡」,最后给一份从现象到根因的排查清单。全部用真实命令,可以直接在服务器上跟着敲。

一、先读懂 ls -l 那一长串

执行 ls -l,你会看到类似这样一行:

-rwxr-x---  2 www-data www-data  4096 Oct  4 21:00 uploads
drwxr-xr-x  5 root     root      4096 Sep 30 10:00 /var/www

拆开看第一列的 10 个字符:

-  rwx  r-x  ---
|   |    |    |
|   |    |    └── other(其他人)的权限
|   |    └─────── group(所属组)的权限
|   └──────────── owner(属主)的权限
└──────────────── 文件类型(- 普通文件,d 目录,l 符号链接)

权限位含义:

  • r (read, 4):读。对文件是读取内容;对目录是「能列出目录里的文件名」(ls)。
  • w (write, 2):写。对文件是修改内容;对目录是「能在里面创建/删除文件」。
  • x (execute, 1):执行。对文件是运行它;对目录是「能进入(cd)并在里面访问已知名字的文件」。

八进制表示就是三者相加:rwx=7,r-x=5,---=0。上面那个 uploads 目录是 750,属主全权,组内可读可进,其他人无权。

二、目录的权限比文件更容易踩坑

新手最常见的误解:只看文件的权限,忘了目录的权限。记住这个规则:

要访问 /a/b/c.txt,你需要对路径上每一个目录都有 x(执行)权限。

如果 /var/www/uploads 的权限是 drw-------(属主可读写但没有 x),那么即使文件本身是 777,你也进不去这个目录,读不到文件。这是「文件权限明明对了却还是 Permission denied」的头号原因。

另一个反直觉的点:删除一个文件,取决于目录的 w 权限,而不是文件的 w 权限。 只要你对目录有写权限,就能删除里面任何文件,哪怕那个文件是 444 只读的。这也解释了为什么共享目录里别人能删你的文件。

快速诊断路径上的目录权限:

namei -l /var/www/uploads/2026/file.txt

namei -l 会把路径上每一级的权限都列出来,一眼看出是哪一级缺了 x:

f: /var/www/uploads/2026/file.txt
drwxr-xr-x root     root     /
drwxr-xr-x root     root     var
drwxr-xr-x root     root     www
drwxr-x--- www-data www-data uploads
drwx------ www-data www-data 2026
-rw-r--r-- www-data www-data file.txt

这里 2026 目录是 700 属主 www-data,如果当前用户不是 www-data,就在这一级被挡住。诊断完成。

三、umask:新文件权限为什么永远不是你想的那样

你 touch newfile,然后 ls -l,发现权限是 644 而不是 666。这不是 bug,是 umask 在起作用。

umask 是「掩码」,作用是从默认权限里减掉某些位。默认:普通文件 666,目录 777。umask 值里的位会被屏蔽掉:

# 查看当前 umask
umask
# 输出 0022

# 文件:666 去掉 022 → 644
# 目录:777 去掉 022 → 755

常见 umask 值:

  • 022:默认,属主可读写,其他人只读不写。适合大多数场景。
  • 002:同组可写。多人协作目录常用。
  • 077:只有属主有权限,其他所有人无权。安全敏感场景(如 ~/.ssh)用。

如果你的应用创建的文件权限不对(比如 web 程序生成的文件其他进程读不了),先查 umask。对 PHP-FPM,可以在 pool 配置里强制:

; /etc/php/*/fpm/pool.d/www.conf
; 不直接提供 umask 指令,但可以通过 systemd 设置
[Service]
UMask=0022

或者对 Nginx/PHP 进程所属的 systemd service 加 UMask=0022 后 daemon-reload 重启。

四、ACL:当三个数字不够用的时候

传统的 owner/group/other 模型有个死结:一个文件只能有一个属主和一个属组。如果我想让 alice 能读写、bob 只读、web 组能读写,三个数字根本表达不了。

POSIX ACL(访问控制列表)解决这个问题,能给任意用户/组单独授权。查看文件的 ACL:

getfacl /var/www/shared/config.php

输出会显示基本权限 + 扩展的 ACL 条目:

# file: var/www/shared/config.php
# owner: root
# group: root
user::rw-
user:alice:rw-
user:bob:r--
group::r--
mask::rw-
other::---

注意这里的 mask 行 —— 它是 ACL 的「有效权限上限」,会和你常见的 ls -l 里显示的权限位混淆。当你给文件设了 ACL,ls -l 里 group 位置显示的不再是真实的组权限,而是 mask(后面会有一个 + 号提示有 ACL):

-rw-rw----+ 1 root root 1234 config.php
#          ↑ 这个加号表示文件有 ACL

添加 ACL 条目:

# 给 alice 读写权限
setfacl -m u:alice:rw /var/www/shared/config.php

# 给 web 组只读
setfacl -m g:web:r /var/www/shared/config.php

# 给目录设默认 ACL(新建文件自动继承)
setfacl -d -m u:alice:rw /var/www/shared/

# 删除某条 ACL
setfacl -x u:alice /var/www/shared/config.php

最容易踩的坑:mask 会限制 ACL 条目的实际生效权限。 你设了 u:alice:rw,但 mask 是 r--,那 alice 实际只有读权限。解决方法是把 mask 也放开:

setfacl -m mask::rw /var/www/shared/config.php

排查 ACL 问题时,永远先 getfacl 看 mask。

五、chattr:藏在权限位下面的「隐藏关卡」

有时候权限明明是 777,root 也改不了、删不掉,报 Operation not permitted(注意不是 Permission denied)。这时候要怀疑文件被设了不可变属性。

查看扩展属性:

lsattr /etc/important.conf

输出里如果第一个字母是 i,说明文件被设了 immutable(不可变):

----i---------e---- /etc/important.conf

加了 +i 的文件:不能修改、不能删除、不能改名、不能创建硬链接,连 root 都不行(除非先去掉这个属性)。这是防篡改的终极手段,但如果你忘了自己设过,排查时会很奇怪。

chattr -i /etc/important.conf   # 去掉不可变属性
chattr +i /etc/important.conf   # 重新加上

另一个常见属性是 +a(append only,只能追加不能覆盖),日志文件常用。同理,用 chattr -a 解除。

lsattr 报 Permission denied 看不到属性时,说明你连读属性都没权限,先解决基础的读权限。

六、从现象到根因的排查清单

遇到 Permission denied,按这个顺序走:

  1. 确认是谁在操作:ps aux | grep 进程名,看是哪个用户/组。Nginx 是 www-data,PHP-FPM 也是。别拿 root 的视角去推测 www-data 能不能访问。
  2. 用 namei 看整条路径:namei -l /完整/路径,找出哪一级目录缺 x。
  3. 看目标文件权限:ls -l,注意那个 + 号,有就是有 ACL。
  4. 有 ACL 就 getfacl:重点看 mask 是不是限制住了。
  5. 权限看着正常却还是不行:lsattr 查 immutable/append-only。
  6. 新创建的文件权限不对:查 umask,以及父目录有没有默认 ACL。
  7. SELinux/AppArmor:Debian/Ubuntu 上如果有 AppArmor,dmesg | grep -i denied 能看到被策略拦下的记录 —— 这种报错和文件权限长得一样,但根因完全不同。

七、实战:修一个 Nginx 403

假设访问 https://site.com/uploads/a.jpg 返回 403,而文件确实存在。按清单走:

# 1. Nginx 跑在 www-data
ps aux | grep nginx

# 2. 看路径每一级
namei -l /var/www/site/uploads/a.jpg
# 发现 uploads 是 drwx------ root root —— 问题在这

# 3. 修复:让 www-data 能进入该目录
chown -R www-data:www-data /var/www/site/uploads
chmod 755 /var/www/site/uploads

# 4. 验证
curl -sI https://site.com/uploads/a.jpg | head -1
# HTTP/2 200  ✅

整个过程不到一分钟。关键在于用 namei -l 定位到具体哪一级目录,而不是盲目地把权限改成 777。

顺便说一句:永远不要用 chmod -R 777 来「解决」权限问题。它只是掩盖症状,还会引入严重的安全风险(任何用户都能改你的网站文件)。真正理解了 owner/group/other、目录的 x 语义、umask、ACL、chattr 这五层,你就能精确地授予「刚好够用」的权限 —— 既让服务跑起来,也不给攻击者留门。

Last modification:October 4th, 2026 at 08:27 pm

Leave a Comment