文件系统与权限管理
1. 概念与原理
Linux 文件系统的核心抽象是 一切皆文件。普通文件、目录、设备、管道、socket,甚至进程信息(/proc/<pid>/)都通过文件系统接口暴露。理解这一点,你才能理解为什么 cat /proc/cpuinfo 能看 CPU 信息,为什么 /dev/null 能吞掉输出。
1.1 inode 与文件名
Linux 文件的真实身份不是文件名,是 inode(索引节点)。inode 存储元数据:所有者、权限、大小、时间戳、数据块位置。文件名只是指向 inode 的引用(硬链接)。
# 查看 inode 号
ls -i package.json
# 12345678 package.json
# 同一个 inode 多个名字(硬链接)
ln package.json package-link.json
ls -i package*.json
# 12345678 package.json
# 12345678 package-link.json
这解释了几个常见现象:
- 删除文件本质是删除文件名(dentry),inode 引用计数为 0 才真正释放空间
- 一个文件被进程打开后再删除,磁盘空间不会立即释放(直到进程关闭文件描述符)—— 这是日志文件被删但磁盘没释放的根因,必须重启进程或截断
: > /var/log/app.log df显示空间满了,但du算出来文件总和远小于磁盘容量?十有八九是有进程持有已删除文件的 fd,用lsof | grep deleted找
1.2 文件类型
ls -l /
# drwxr-xr-x bin ← d 目录
# -rw-r--r-- hosts ← - 普通文件
# lrwxrwxrwx etc ← l 符号链接
# crw-rw-rw- null ← c 字符设备
# brw-rw---- sda ← b 块设备
# srw-rw-rw- sock ← s socket
# prw-r--r-- fifo ← p 管道
前端日常只关心 -(普通文件)、d(目录)、l(软链接)。node_modules 里 npm 用大量软链接做 hoist,这是 find node_modules -type l 能找到的。
2. 权限模型
2.1 三组九位权限
-rwxr-xr-- 1 alice staff 1024 Jun 18 10:00 deploy.sh
│└┬┘└┬┘└┬┘ │ └─┬─┘ └─┬─┘
│ │ │ │ │ │ │
│ │ │ │ │ │ └─ 所属组
│ │ │ │ │ └──────── 所有者
│ │ │ │ └──────────── 硬链接数
│ │ │ └──────────────── other(其他用户)r--
│ │ └─────────────────── group(同组用户)r-x
│ └────────────────────── owner(所有者)rwx
└──────────────────────── 文件类型
权限数字算法(八进制):
r = 4、w = 2、x = 1rwx = 7、rw- = 6、r-x = 5、r-- = 4
前端必知组合:
644(rw-r--r--)— 普通配置、源码、HTML/CSS/JS,所有人可读,只所有者可写755(rwxr-xr-x)— 目录、可执行脚本、二进制,所有人可读可进入,只所有者可改600(rw-------)— 私钥(~/.ssh/id_rsa)、token 文件,只所有者可读写700(rwx------)—~/.ssh/目录、敏感目录777(rwxrwxrwx)— 永远不要在生产用,等于把权限完全放开
2.2 目录权限的特殊语义
目录的 rwx 含义和文件不同,这是新人最容易栽的坑:
| 位 | 文件 | 目录 |
|---|---|---|
| r | 读取内容 | 列出目录里的文件名(ls) |
| w | 修改内容 | 创建/删除/重命名目录内文件 |
| x | 执行 | 进入目录(cd)、访问目录内文件 |
关键推论:
- 一个目录只有
r没有x,你能ls看到文件名但cat任何文件都报 Permission denied - 一个文件你有 rwx,但所在目录只有
r-x,你能改文件内容,但不能删除它(删除是改目录) - nginx 给静态文件目录的标准权限是
755,文件644
2.3 chmod 三种写法
# 数字模式(最常用)
chmod 644 file
chmod 755 deploy.sh
# 符号模式
chmod u+x deploy.sh # 给所有者加可执行
chmod go-w file # 去掉 group 和 other 的写权限
chmod a+r file # 所有人可读(a = all)
# 递归
chmod -R 755 dist/ # 慎用,会把所有文件都改成 755(含 644 的)
# 更安全的写法:区分文件和目录
find dist -type d -exec chmod 755 {} \;
find dist -type f -exec chmod 644 {} \;
2.4 chown 改所有者
chown alice file # 改所有者
chown alice:staff file # 同时改所有者和组
chown :staff file # 只改组(等同 chgrp)
chown -R nginx:nginx /var/www # 递归
前端高频场景:把 dist/ 部署到 nginx 静态目录后,所有者必须是 nginx 进程的用户(通常是 www-data 或 nginx),否则浏览器拿到 403。
scp -r dist/ user@server:/var/www/app/
ssh user@server "sudo chown -R nginx:nginx /var/www/app && sudo chmod -R 755 /var/www/app"
3. SUID / SGID / Sticky Bit
特殊权限位,前端不常配置但要看得懂。
ls -l /usr/bin/passwd
# -rwsr-xr-x ← s 在 owner 的 x 位 = SUID
| 位 | 含义 |
|---|---|
| SUID(4xxx,s 在 owner x 位) | 执行时临时获得文件所有者权限,典型例子 passwd —— 普通用户执行它会临时获得 root 权限去改 /etc/shadow |
| SGID(2xxx,s 在 group x 位) | 用在目录上:目录内新建的文件自动继承目录的所属组 |
| Sticky Bit(1xxx,t 在 other x 位) | 用在共享目录上:只有文件所有者能删除自己的文件,/tmp 必有 |
# 设置
chmod 4755 file # SUID
chmod 2755 dir # SGID
chmod 1777 dir # Sticky
安全警示:任何 SUID 二进制都是攻击面。find / -perm -4000 2>/dev/null 列出系统所有 SUID 文件,新增 SUID 是入侵者的常见后门手法。
4. ACL(访问控制列表)
标准 rwx 只能给所有者、组、其他三类设权限。需要给特定用户单独授权时,用 ACL。
# 给特定用户读写权限
setfacl -m u:alice:rw file
setfacl -m u:bob:rx file
# 查看
getfacl file
# user::rw-
# user:alice:rw-
# user:bob:r-x
# group::r--
# mask::rw-
# other::r--
# 递归 + 默认 ACL(新文件继承)
setfacl -R -m u:nginx:rx /var/www/app
setfacl -R -d -m u:nginx:rx /var/www/app
前端实战场景:CI 部署用户和 nginx 运行用户不是同一个,CI 写文件后 nginx 没权限读。两种解法:
- 部署完
chown -R nginx:nginx - 用 ACL 给 nginx 用户加读权限,不改所有者
5. umask
新建文件/目录的默认权限由 umask 控制。
umask # 查看,通常是 0022
# 新文件权限 = 666 - 022 = 644
# 新目录权限 = 777 - 022 = 755
如果生产服务器看到新建文件是 666(rw-rw-rw-),多半是 umask 被改成了 000,是严重安全问题。Dockerfile 里有时为了图方便会写 umask 000,必须避免。
6. 生产实践
6.1 SSH 密钥与权限
~/.ssh/ 权限错了会导致 SSH 连不上,且不会有清晰报错(OpenSSH 静默拒绝):
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa.pub # 公钥也建议 600 或 644
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/config
调试 SSH 连不上:ssh -vvv user@server 2>&1 | grep -i permission,看是哪个文件权限不对。
6.2 部署目录的标准权限
前端项目部署到 /var/www/myapp/:
# 目录 755,文件 644,所有者 nginx
sudo chown -R nginx:nginx /var/www/myapp
sudo find /var/www/myapp -type d -exec chmod 755 {} \;
sudo find /var/www/myapp -type f -exec chmod 644 {} \;
如果应用要写日志/缓存(如 Next.js 的 .next/cache),那个目录单独 chmod 775 或 chown 给应用运行用户。
6.3 容器内的权限陷阱
宿主机挂载目录到容器(-v $(pwd):/app),容器内进程通常以 root 跑,写出来的文件在宿主机上属于 root,宿主机普通用户改不了。
解法:
# Dockerfile 里创建非 root 用户并切换
RUN addgroup -g 1000 node && adduser -D -u 1000 -G node node
USER node
或者 docker run 时指定 uid:
docker run -u $(id -u):$(id -g) -v $(pwd):/app myimage
Node 官方镜像自带 node 用户(uid 1000),直接 USER node 即可。
7. 故障排查
7.1 Permission denied 排查思路
# 1. 当前用户是谁
whoami
id # 看 uid、gid、所有组
# 2. 文件权限和所有者
ls -la <path>
stat <path> # 更详细,含 SELinux 标签
# 3. 整条路径上每个目录都要有 x 权限
namei -l /var/www/myapp/index.html
# f: /var/www/myapp/index.html
# drwxr-xr-x root root /
# drwxr-xr-x root root var
# drwxr-xr-x root root www
# drwx------ root root myapp ← 这里被拦
# -rw-r--r-- root root index.html
# 4. SELinux / AppArmor 也可能拦(CentOS/RHEL 默认开 SELinux)
getenforce # Enforcing 表示开启
ls -Z file # 看 SELinux 标签
# 临时关:setenforce 0
7.2 磁盘满了但 du 算不出来
df -h # 显示满了
du -sh /var # 算出来远小于实际占用
# 找已删除但被进程持有的文件
sudo lsof | grep deleted | sort -k 7 -h | tail
# 输出会有 deleted 标记和文件大小
# 杀掉持有进程或重启服务释放空间
典型场景:Node 应用日志没 rotate,文件被删但进程还在写,磁盘空间不释放。
7.3 文件能看到但 cat 报错
99% 是上级目录缺 x 权限。namei -l 一查就知道。
8. 常见反模式
chmod -R 777 /var/www:永远不要。这是把所有人写权限全开,等于邀请别人篡改你的网站。正确做法是 644/755 + chown- 以 root 跑 Node 应用:被 RCE 后直接 root 权限沦陷整台机器。Dockerfile 必须 USER 非 root
- 私钥不设 600:SSH 会直接拒绝,更严重的是同机器其他用户能读你的私钥
- umask 设成 000:所有新文件都是 666,被同机器其他用户写入是分分钟的事
chown -R误改/:经典事故,恢复极其困难。任何-R操作前先echo一遍命令确认路径- 生产文件用 vim 直接改 + 不留备份:改坏了恢复不了。先
cp file file.bak.$(date +%Y%m%d)
9. 延伸阅读
- 《鸟哥的 Linux 私房菜(基础学习篇)》第 5-7 章 — 中文 Linux 权限最系统的资料
- Linux man-pages: chmod(1)
- Filesystem Hierarchy Standard 3.0 — Linux 目录结构权威规范
- Arch Wiki: File permissions and attributes — 含 ACL、attr、capabilities 全面参考