Permission denied 到底差在哪:一次 Linux 文件权限排查复盘

服务迁到新机器一启动就报 Permission denied,chmod 777 了还是不行,文件权限明明看着对。排查梳理:读懂 ls -l 九个权限位、目录的 x 才是进入的通行证、namei 看穿整条路径、chown 改对属主才是治本、umask 与 SUID/SGID/Sticky、ACL 精细授权。

2024 年我们把一个服务从一台老机器迁到新机器,部署完一启动就报 Permission denied。这种错看着简单——加个权限不就完了?可真动起手来,坑一个接一个:给文件 chmod 777 了还是不行;明明文件能读,程序却说进不去那个目录;同样一份代码,这台机器跑得好好的,那台就权限报错。那一天我对着 ls -l 输出里那一串 rwxr-xr-x,才意识到自己对 Linux 文件权限的理解一直停留在"大概知道"的层面。本文复盘这次实战,把 Linux 文件权限这套东西彻底讲清楚。

问题背景

环境:CentOS 7,一个 Java 服务从老机器迁到新机器
事故现象:
- 服务启动报 Permission denied
- 给报错的文件 chmod 777 了,换个地方又报
- 程序读不了某个目录下的配置,但那个配置文件
  本身权限看着是对的
- 老机器一切正常,新机器各种权限报错

现场排查:
# 1. 看报错具体卡在哪个文件
$ tail -50 /var/log/app/start.log
java.io.FileNotFoundException: /data/app/conf/app.yml
  (Permission denied)

# 2. 看这个文件的权限
$ ls -l /data/app/conf/app.yml
-rw-r--r-- 1 root root 2048 May 14 10:00 /data/app/conf/app.yml
# 文件本身 others 有 r 权限,看着没问题啊?

# 3. 关键:看它【所在目录】的权限
$ ls -ld /data/app/conf
drwxr-x--- 2 root root 4096 May 14 10:00 /data/app/conf
# ★ 目录对 others 没有任何权限(--- )
#   而服务是用 appuser 这个普通用户跑的!

根因(后来定位到的):
1. 服务以 appuser 身份运行,但文件/目录的属主是 root
2. 关键目录对 others 没有 x 权限 —— 没有 x,
   就进不去这个目录,里面文件权限再对也没用
3. 老机器上服务恰好是用 root 跑的,所以没暴露问题

修复 1:读懂 ls -l 那一串权限位

=== ls -l 输出的第一列,是排查权限的起点 ===

$ ls -l app.yml
-rw-r--r-- 1 root root 2048 May 14 10:00 app.yml
 │└┬┘└┬┘└┬┘  │    │
 │ │  │  │   │    └─ 属组(group)
 │ │  │  │   └────── 属主(owner / user)
 │ │  │  └────────── others:其他人的权限
 │ │  └───────────── group:属组成员的权限
 │ └──────────────── user:属主的权限
 └────────────────── 文件类型

=== 第 1 个字符:文件类型 ===
-  普通文件      d  目录       l  软链接
c  字符设备      b  块设备     s  socket   p  管道

=== 后面 9 个字符:分成 3 组,每组 rwx ===
分别是 [属主] [属组] [其他人] 的权限。
r (read)    读
w (write)   写
x (execute) 执行
-           表示没有这一项权限

=== 例:-rw-r--r-- 怎么读 ===
属主 rw-  : 可读、可写、不可执行
属组 r--  : 只可读
其他 r--  : 只可读

=== 关键:权限是按"身份"匹配的,而且只匹配一次 ===
系统判断你能不能操作一个文件时:
- 你是属主          -> 只看属主那 3 位,后面的不看了
- 你不是属主但在属组 -> 只看属组那 3 位
- 都不是            -> 只看 others 那 3 位
所以"我在属组里,但 others 权限更大"也没用 ——
匹配到属组就停了。这是个常见误解。

修复 2:目录的 x 权限——这次踩的最大的坑

# === 对【文件】和对【目录】,rwx 的含义不一样!===

# === 对文件 ===
# r:能读文件内容       w:能修改文件内容
# x:能把文件当程序执行

# === 对目录(这里最反直觉)===
# r:能【列出】目录里有哪些文件(ls 能看到名字)
# w:能在目录里【创建/删除/改名】文件
# x:能【进入】这个目录、能【访问】里面的文件
#    —— x 是目录的"通行证"

# === 这次的坑就在这:目录少了 x ===
$ ls -ld /data/app/conf
drwxr-x--- 2 root root 4096 ... /data/app/conf
# others 是 --- ,连 x 都没有。
# appuser 属于 others,没有 x -> 【根本进不去这个目录】
# 进不去目录,里面的 app.yml 权限就算是 777 也读不到!

# === 一个关键认知:访问一个文件,要它【路径上每一级
#     目录】都有 x 权限 ===
# 要读 /data/app/conf/app.yml,你必须对
#   /  、/data  、/data/app  、/data/app/conf
# 每一级目录都有 x 权限,缺一级都进不去。
# 这就是为什么"文件权限看着对、却还是 Permission denied"。

# === 用 namei 一键看整条路径的权限 ===
$ namei -l /data/app/conf/app.yml
f: /data/app/conf/app.yml
 dr-xr-xr-x root root /
 drwxr-xr-x root root data
 drwxr-xr-x root root app
 drwxr-x--- root root conf      <-- 问题这一级!
 -rw-r--r-- root root app.yml
# namei -l 把路径上每一级的权限全列出来,
# 排查"到底是哪一级目录挡住了"神器。

# === 修复:给目录补上 x ===
$ chmod o+x /data/app/conf
# 或者更根本地,把属主改对(见修复 4)

修复 3:chmod——数字法和符号法

# === chmod 改权限,两种写法 ===

# === 写法 A:数字法(rwx 当二进制)===
# r=4  w=2  x=1   一组权限 = 三者相加
# rwx = 4+2+1 = 7      rw- = 4+2 = 6
# r-x = 4+1 = 5        r-- = 4
$ chmod 644 app.yml
# 6=rw- (属主)  4=r-- (属组)  4=r-- (其他) -> -rw-r--r--
$ chmod 755 start.sh
# 7=rwx  5=r-x  5=r-x -> 属主全权,其他人可读可执行
$ chmod 700 secret.key
# 7=rwx  0=---  0=--- -> 只有属主能动,别人完全无权

# === 常用数字组合记忆 ===
# 644  普通文件:属主可读写,其他人只读       (配置、文档)
# 755  可执行文件/目录:属主全权,其他人可读可进 (脚本、目录)
# 600  私密文件:只有属主可读写               (私钥、密码文件)
# 700  私密目录:只有属主能进                 (.ssh 目录)
# 777  所有人全权 —— ★ 几乎永远不该用,见下文

# === 写法 B:符号法(更直观,适合"微调")===
$ chmod o+x conf/          # others 增加 x
$ chmod g-w file           # group 去掉 w
$ chmod u+x deploy.sh      # user 增加 x
$ chmod a+r file           # a=all,所有人增加 r
$ chmod u=rw,go=r file     # 直接精确赋值

# === 递归改:-R(对目录及里面所有内容)===
$ chmod -R 755 /data/app/conf
# ★ 但递归有个坑:它会把目录和文件【一视同仁】。
# 755 对目录合适(要 x 才能进),
# 但普通配置文件其实不需要 x。更精细的做法:
$ find /data/app -type d -exec chmod 755 {} \;   # 目录给 755
$ find /data/app -type f -exec chmod 644 {} \;   # 文件给 644

# === 为什么不要 chmod 777 ===
# 777 = 任何人都能读、写、执行。这是巨大的安全漏洞:
# 任何用户(包括被攻破的其它服务)都能篡改你的文件。
# 它还会掩盖真正的问题 —— 你以为"加 777 好了",
# 其实根因(属主不对、目录缺 x)一直都在。
# 正确做法永远是:把【属主】和【最小必要权限】配对,
# 而不是用 777 一把梭。

修复 4:chown——属主才是问题的根

# === 这次故障的真正根因:属主不对 ===
# 服务以 appuser 身份跑,可文件全是 root 的。
# appuser 对这些文件来说只是个 "others",
# 权限处处受限。光 chmod 是治标,改对属主才是治本。

# === chown:改文件的属主 / 属组 ===
$ chown appuser app.yml              # 只改属主
$ chown appuser:appgroup app.yml     # 同时改属主和属组
$ chown :appgroup app.yml            # 只改属组(等同 chgrp)

# === 递归:把整个目录树的属主都改对 ===
$ chown -R appuser:appgroup /data/app
# 这次真正的修复就是这一条 ——
# 让运行服务的用户,成为它要访问的文件的属主。
# 改完之后,appuser 走的是"属主"那 3 位权限,
# 一切豁然开朗。

# === 先搞清楚服务到底是用哪个用户跑的 ===
$ ps -eo user,pid,cmd | grep '[j]ava'
appuser  12345 java -jar /data/app/app.jar
# 确认是 appuser,文件属主就该配它。

# === 看一个用户的身份信息 ===
$ id appuser
uid=1001(appuser) gid=1001(appgroup) groups=1001(appgroup)
# uid/gid、属于哪些组,一目了然

# === 迁移机器时,属主"对不上"的真相 ===
# 文件系统里存的其实是【数字 uid/gid】,不是用户名。
# 老机器 appuser 的 uid 可能是 1001,
# 新机器上 appuser 的 uid 却可能是 1005。
# 用 tar/scp 把文件搬过去,属主按数字 uid 保留,
# 于是新机器上 ls -l 可能显示一个莫名其妙的数字、
# 或张冠李戴的用户名。迁移后务必重新 chown 一遍。

修复 5:umask 与特殊权限

# === umask:新建文件的"默认权限"是怎么来的 ===
$ umask
0022
# 新建【文件】默认 666 减去 umask -> 666-022 = 644
# 新建【目录】默认 777 减去 umask -> 777-022 = 755
# (文件默认不带 x,所以基数是 666 不是 777)

# 如果某进程的 umask 是 0077,它创建的文件就是 600,
# 别的用户全都读不到 —— 服务之间共享文件出问题时,
# 检查一下创建方进程的 umask。
$ umask 0027     # 临时改;永久改写进 /etc/profile 或服务配置

# === 特殊权限位:SUID / SGID / Sticky ===

# 【SUID】(在属主 x 位上显示为 s)
$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root ... /usr/bin/passwd
#    ↑ 这个 s 就是 SUID
# 含义:任何人执行这个程序时,进程会临时以
#       【文件属主】的身份运行。passwd 要改 /etc/shadow,
#       所以它靠 SUID 临时获得 root 权限。
# 安全提示:可写的、自己写的程序绝不要随便加 SUID。

# 【SGID】(在属组 x 位上显示为 s)
# 加在【目录】上特别有用:在该目录里新建的文件,
# 属组会自动【继承这个目录的属组】,而不是创建者的组。
$ chmod g+s /data/share
# 多人协作的共享目录常用它,保证组内都能访问。

# 【Sticky bit】(在 others x 位上显示为 t)
$ ls -ld /tmp
drwxrwxrwt ... /tmp
#          ↑ 这个 t 就是 sticky
# 含义:目录里人人可写,但每个文件【只有属主能删】。
# /tmp 就靠它,防止你删掉别人在 /tmp 里的文件。

# === 设置特殊权限 ===
$ chmod u+s file      # 加 SUID
$ chmod g+s dir       # 加 SGID
$ chmod +t dir        # 加 sticky
$ chmod 2755 dir      # 数字法:最前面那位,2=SGID 4=SUID 1=sticky

修复 6:ACL——超越"属主/组/其他"的精细授权

# === 传统权限的局限 ===
# rwx 只能分"属主 / 属组 / 其他人"这三档。
# 如果你想让"appuser 能读,backupuser 也能读,
# 但其他人都不行" —— 三档根本不够分。
# 这时用 ACL(访问控制列表)。

# === setfacl:给【某个具体用户/组】单独授权 ===
$ setfacl -m u:backupuser:r-x /data/app/conf
# -m 修改  u:backupuser:r-x  给 backupuser 单独 r-x 权限
$ setfacl -m g:devteam:rwx /data/app/logs
# 给 devteam 这个组单独授权

# === getfacl:查看一个文件/目录的完整 ACL ===
$ getfacl /data/app/conf
# user::rwx
# user:backupuser:r-x        <-- ACL 额外加的那条
# group::r-x
# mask::r-x
# other::---

# === 注意:有 ACL 的文件,ls -l 会多一个 + 号 ===
$ ls -ld /data/app/conf
drwxr-x---+ 2 root root ... /data/app/conf
#         ↑ 这个 + 表示"这个文件还有 ACL 规则"。
# 排查权限时,如果 ls -l 看着对、却还是有问题,
# 看到这个 + 就要 getfacl 看看 ACL 里有没有别的规则。

# === 给目录设默认 ACL:里面新建的文件自动继承 ===
$ setfacl -d -m u:backupuser:r-x /data/app/conf
# -d 设的是 default ACL,之后在该目录新建的文件,
#    都会自动带上这条规则。

# === 删除 ACL ===
$ setfacl -x u:backupuser /data/app/conf   # 删某一条
$ setfacl -b /data/app/conf                # 清空所有 ACL

命令速查

需求                        命令
=============================================================
看文件权限/属主              ls -l file
看目录本身的权限             ls -ld dir
看整条路径每级权限           namei -l /a/b/c/file
改权限(数字法)             chmod 644 file  /  chmod 755 dir
改权限(符号法)             chmod o+x dir  /  chmod g-w file
递归改权限                   chmod -R / find -type d/f -exec chmod
改属主                       chown user:group file
递归改属主                   chown -R user:group dir
看用户的 uid/组              id 用户名
看进程用哪个用户跑           ps -eo user,pid,cmd | grep xxx
看默认权限掩码               umask
加 SGID(共享目录)          chmod g+s dir
加 sticky(防误删)          chmod +t dir
给指定用户单独授权           setfacl -m u:用户:rwx file
查看 ACL                     getfacl file

口诀:先确认运行用户 -> namei 看整条路径
      -> 目录缺 x 补 x,属主不对 chown -> 拒绝 777

避坑清单

  1. ls -l 的 9 个权限位分属主/属组/其他三组,权限按身份匹配且只匹配一次
  2. rwx 对文件和对目录含义不同,目录的 x 是"进入"的通行证,不是"执行"
  3. 访问一个文件要求路径上每一级目录都有 x 权限,缺一级就进不去
  4. 文件权限看着对却仍报 Permission denied,十有八九是某级父目录缺 x
  5. 用 namei -l 看整条路径每一级的权限,快速定位是哪一级挡住了
  6. chmod 是治标,属主不对才是根,要用 chown 把运行用户配成文件属主
  7. 几乎永远不要 chmod 777,它是安全漏洞,还会掩盖属主或目录 x 的真问题
  8. 递归 chmod 会把目录和文件一视同仁,应分开:目录 755、文件 644
  9. 迁移机器后属主按数字 uid 保留,容易对不上,迁移后务必重新 chown
  10. ls -l 末尾有 + 号说明文件还有 ACL 规则,要用 getfacl 才能看全

总结

这次因为服务迁移而引发的权限排查,把我对 Linux 文件权限那种"大概知道"的模糊认知,彻底敲成了清晰的体系。出事之前,如果你问我 Permission denied 怎么办,我大概会不假思索地回答"chmod 加个权限呗",甚至更糟糕地脱口而出"chmod 777 准没错"。这一天之后我才真正明白,这种回答错得有多离谱。Linux 的文件权限,从来不是一个孤立的"这个文件够不够开放"的问题,而是一个由"身份"和"路径"共同决定的系统。先说身份。系统判断你能不能动一个文件时,会先看你是谁:你是这个文件的属主,就只看属主那三位权限,后面属组和其他人的权限它看都不看;你不是属主但属于它的属组,就只看属组那三位;两者都不是,才看其他人那三位。而且这个匹配一旦命中就停止——这意味着"我在属组里、但其他人的权限更大"是占不到便宜的,因为匹配到属组就结束了。理解了这一点,这次故障的根因也就浮出水面了:我们的服务是以 appuser 这个普通用户身份运行的,可要访问的那些文件,属主全都是 root,appuser 对这些文件而言,不过是个毫不相干的"其他人",自然处处碰壁。所以这次真正治本的修复,根本不是去 chmod 什么,而是一条 chown -R,把运行服务的那个用户,变成它要访问的文件的属主——身份对上了,它走的就是属主那条最宽的权限通道,一切豁然开朗。这就引出我想记住的第一条铁律:chmod 往往只是治标,属主不对才是病根,排查权限的第一步永远是先搞清楚"这个进程到底是用哪个用户在跑",再回头看文件的属主跟它对不对得上。再说路径。这次我踩的最深、也最反直觉的一个坑,是目录的 x 权限。我一直以为 x 就是"可执行",是给脚本、给程序用的,跟一个放配置文件的目录有什么关系?可事实是,x 这个权限位,对文件和对目录的含义截然不同:对文件,x 才是"能不能把它当程序执行";而对目录,x 是"能不能进入这个目录"的通行证。一个目录如果对你没有 x 权限,你就根本进不去它,那么不管它里面的文件权限设得多宽、哪怕是 777,你也一个都碰不到。更要命的是,这个规则是逐级生效的——你要访问 /data/app/conf/app.yml 这个文件,你必须对路径上的每一级目录,从根目录一直到 conf,统统都有 x 权限,中间缺了任何一级,整条路就断了。这彻底解释了那个最折磨人的现象:文件本身的权限明明看着完全正确,程序却死活报 Permission denied——因为挡住你的根本不是这个文件,而是它头顶上某一级你没留意的父目录。从那以后,namei -l 这条命令成了我排查权限问题的第一把武器,它会把一条路径上每一级目录的权限齐刷刷地列出来,到底是哪一级挡住了你,一目了然,再也不用一级一级地猜。最后我想说说 chmod 777 这件事。它之所以危险,不只是因为它把文件对所有人敞开、是个实打实的安全漏洞,更阴险的一点在于:它会"看起来"解决了问题,从而掩盖真正的病根。你 chmod 777 之后服务跑起来了,你以为搞定了,可属主不对这个根本问题、目录缺 x 这个结构问题,其实一个都没解决,它们只是被一层过度开放的权限暂时盖住了,像定时炸弹一样留在那里。正确的做法永远是反过来的——先想清楚"谁该访问它",把属主和属组配对,再给出"恰好够用的最小权限",目录给到能进能读的 755、文件给到能读能写的 644,私密的东西收紧到 600。权限管理的本质,是精确地回答"谁、能对什么、做什么"这三个问题,而不是用一个 777 把所有问题一推了之。这次排查从最初的手忙脚乱、四处 chmod,到最后理清"先认运行用户、再用 namei 看穿整条路径、目录缺 x 就补 x、属主不对就 chown、坚决不用 777"这样一条清晰的链路,我最大的收获,就是终于把文件权限从一个让我头疼的"玄学",变成了一套我能讲清楚、也能稳稳排查的逻辑。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
Linux教程

定时任务明明配了却不执行:一次 Linux crontab 排查复盘

2026-5-20 17:28:59

Linux教程

SSH 免密配了却还要密码:一次 Linux SSH 排查与加固复盘

2026-5-20 17:34:19

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索