
前段时间帮朋友看一个部署问题静态站点搬到新服务器nginx 配置一模一样域名也解析对了但浏览器上永远是 403 Forbidden。他改了三天重装过 nginx、换过配置文件、怀疑过 SELinux最后我上去敲了一句namei-l/var/www/site/index.html问题出来了/var/www的权限是750而 nginx 的 worker 进程跑在www-data用户下既不是属主也不在组里。它连目录都进不去更别提读文件了。chmod ox /var/www之后页面立刻就出来了。说实话Linux 权限这东西我也栽过好几回——SSH 私钥权限太开被拒连、脚本 clone 下来跑不动、chmod -R 777之后整个目录被人塞了挖矿程序。这篇就把权限位这件事从头到尾讲清楚。问题根源权限是三组 × 三位缺一个就全盘不通很多人对 chmod 的理解停留在777 是全部权限644 是普通文件靠背数字过活。但真正出问题的地方往往不在最后一位而在整条访问路径上。 几个高频踩坑点脚本没有执行位git clone下来的.sh是644./deploy.sh直接Permission denied路径上任何一级目录缺 xnginx/php-fpm 读不到文件报 403但文件本身权限是对的SSH 私钥权限过开644的私钥会被 OpenSSH 直接拒绝使用Web 目录给了 777任何本地用户都能改你的代码被挂马只是时间问题Windows 与 Linux 的文件系统差异NTFS 挂载、WSL、Docker volume 挂载进来的文件权限可能全是 777 或者全 0递归 chmod 覆盖了不该改的chmod -R 755会把所有普通文件也变成可执行根子上是一个认知问题权限不是一个文件一个数字而是属主、属组、其他人三组每组 rwx 三位一共九个位而且目录和文件的语义完全不同。ls -l 那十个字符到底在说什么先看一行最常见的输出-rw-r----- 1 admin wheel 6 Sep 30 17:28 demo.txt │└┬┘└┬┘└┬┘ │ │ │ │ └─ 文件名 │ │ │ │ │ │ │ └─ 大小 │ │ │ │ │ │ └─ 属组 (group) │ │ │ │ │ └─ 属主 (owner) │ │ │ │ └─ 硬链接数 │ │ │ └─ others 的权限 │ │ └─ group 的权限 │ └─ owner 的权限 └─ 文件类型 macOS 上末尾的 表示带扩展属性Linux 上是 . 表示带 SELinux 上下文 表示有 ACL第一位是文件类型字符类型说明-普通文件绝大多数情况d目录本质是一张文件名到 inode 的表l符号链接权限位对它本身几乎无意义c字符设备如/dev/null、/dev/ttyb块设备如/dev/sdap命名管道mkfifo创建s套接字Unix domain socket后面九位就是权限本体每三位一组顺序固定为owner → group → others。技术原理rwx 对文件和目录的语义完全不同这是全文最该记住的部分。同样是x在文件和目录上是两回事。文件位含义r(4)能读内容cat、less、编辑器打开w(2)能改内容写入、截断。注意不能改名或删除那取决于所在目录的wx(1)能作为程序执行./a.out、bash a.sh后者其实只要r 一个反直觉点删除文件看的是目录的w不是文件的w。所以一个444的只读文件只要你对它所在目录有写权限照样能rm掉rm会提示确认加-f直接删。这也是/tmp要加 sticky 位的原因后面讲。目录位含义r能列出目录里的文件名ls但拿不到任何元数据w能在目录里创建、删除、重命名文件——配合x才生效x能穿越这个目录cd进去、访问里面的文件、把它当路径的一部分实测验证macOSLinux 行为一致# 目录只给 r不给 x400chmod400dtlsdt# a.txt ← 能列出名字catdt/a.txt# Permission denied ← 但读不到内容cddt# permission denied ← 也进不去statdt/a.txt# Permission denied ← 连元数据都拿不到# 目录只给 x不给 r100chmod100dtlsdt# Permission denied ← 列不出名字catdt/a.txt# (读取成功) ← 但知道文件名就能访问cddt# /tmp/perm/dt ← 也能进去所以r无x只能看到这个目录里有些什么东西什么都干不了x无r可以正常访问已知路径的文件只是不能列目录——这其实是一种隐藏但可用的配置w无x完全无效写操作必须先穿越目录⚠️ 这就是 nginx 403 的根源从/到目标文件的每一级目录都必须有 x只给最后那个文件 644 是没用的。排查命令namei-l/var/www/site/index.html# 或者逐级看ls-ld/ /var /var/www /var/www/site /var/www/site/index.html八进制怎么算每三位二进制压成一个八进制数字r 4 (100) w 2 (010) x 1 (001) - 0 (000) rwx 421 7 r-x 401 5 r-- 400 4 rw- 420 6644就是rw-/r--/r--rw-r--r--。心算容易错的地方是顺序rw-r--r--从左往右是 owner、group、others写反了就变成给别人开了写权限。我一般不心算直接查表或者用工具反查。特殊权限位第四位数字八进制其实可以有四位最高位是特殊权限位值名称作用4setuids在 owner 的 x 位上执行程序时进程的有效用户变成文件属主而不是调用者2setgids在 group 的 x 位上对可执行文件有效组变成文件属组。对目录目录内新建的文件自动继承目录的属组1stickyt在 others 的 x 位上对目录即使所有人都有w也只能删除自己拥有的文件符号表示有个细节容易看漏大写表示设了特殊位但没有对应的 x。4755 → rwsr-xr-x 小写 s有 setuid 且有 x 4644 → rwSr--r-- 大写 S有 setuid 但没 x实际不生效 1777 → rwxrwxrwt 小写 t/tmp 就是它 1766 → rwxrw-rwT 大写 T有 sticky 但没 x 三个真实用例/usr/bin/passwd是4755root 属主——普通用户改密码时必须能写/etc/shadow靠的就是 setuid/tmp是1777——所有人可写但 sticky 位让你删不掉别人的临时文件共享代码目录设2775——组内成员新建的文件自动归属同一个组不会各写各的⚠️Linux 内核会忽略脚本文件上的 setuid 位#!/bin/bash那种这是有意为之的安全设计。想给脚本提权得包一层 setuid 的 C 程序或者用sudo配精确规则别指望chmod 4755 deploy.sh能生效。umask新文件的权限从哪来touch出来的文件为什么默认是644而不是777因为 umask。规则是按位与非文件0666 ~umask 目录0777 ~umask实测umask新建文件新建目录022644755002664775027640750077600700000666777注意最后一行umask 000 时新文件是666不是777。因为创建文件的系统调用申请的基准权限就是0666umask 只能减不能加。想给文件加执行位必须显式chmod x或者用install -m 755。 团队协作目录的标准配方umask 002 目录2775。前者让组内成员新建的文件组可写后者让文件自动继承目录属组两个人改同一个目录就不会互相踩权限。实操四种做法按场景选方法一数字模式精确覆盖chmod644config.yaml# 显式指定九个位chmod755deploy.shchmod4755/usr/local/bin/mytool# 带 setuidchmod1777/var/tmp/scratch# 带 sticky数字模式是绝对赋值写644就是把九个位全部重置成rw-r--r--不是在原基础上加。想保留原状态就别用它。方法二符号模式增量修改更安全chmodux deploy.sh# 只给属主加执行位chmodg-w config.yaml# 去掉组的写权限chmodosecret.key# 清空 others 的全部权限chmodar README.md# 所有人加读权限chmod-RarX docs/# 递归但只给目录和本来就可执行的文件加 x最后那条的大写X是批量操作里最有用的一个特性。实测改前: 700 xr (目录) 600 xr/plain.txt (普通文件无 x) 700 xr/script.sh (脚本已有 x) 700 xr/sub (目录) chmod -R arX xr 之后: 755 xr ← 目录加了 x 644 xr/plain.txt ← 普通文件X 不生效只加了 r 755 xr/script.sh ← 本来就有 xX 生效 755 xr/sub ← 目录加了 x对比一下chmod -R arx小写它会把plain.txt、README.md全部变成可执行文件一整个目录绿绿的看着就很不对劲。批量改权限永远优先用大写X。符号模式还支持相对写法chmod gu file让组的权限等于属主chmod x file不写谁等价于chmod ax但会受 umask 影响。方法三find chmod批量与审计# 经典配方目录 755文件 644find.-typed-execchmod755{}find.-typef-execchmod644{}# 只给脚本加执行位find.-typef-name*.sh-execchmodux{}# 审计找出所有 others 可写的文件find.-typef-perm/ow# GNU findLinuxfind.-typef-permow# BSD findmacOS# 找出所有 777find/-xdev-typef-perm777⚠️-perm的语法在两个平台上不一样GNU find 用/mode任意一位匹配和-mode全部位匹配BSD find 用mode旧写法和-mode。把 Linux 脚本直接搬到 macOS 上跑会报illegal mode string。-exec ... {} 比\;快很多前者会把参数批量传给 chmod后者每个文件起一个进程。方法四在线计算器临时换算与教学有些时候只是想快速确认一下我想要的效果对应哪个数字——比如要给运维同学说明白某个目录该设成什么或者写文档时需要贴符号表示。我用的是这个Chmod 权限计算器。界面是 Owner / Group / Others 三行每行勾rwx三个框勾完右边实时出八进制值和rwx符号串下面还会生成一条chmod NNN filename命令可以直接复制。反过来也行直接输入755点应用勾选状态会自动填好。https://leowh.com/tools/chmod/index.html它的输入范围是 0–777也就是不覆盖 setuid / setgid / sticky 那第四位——这部分只能自己记或者用stat -c %a从现成的文件上反查。适合的场景是临时换算、写文档、给别人解释权限。真要在服务器上批量改还是走方法三的find命令能进版本控制、能 code review、出问题能复现。四种方法对比方法适合场景批量能力可复现 / 进 CI风险点数字模式单个文件精确设置弱✅绝对覆盖容易清掉原有位符号模式增量加减权限中✅小写x会把普通文件也变成可执行find chmod目录树批量修复、审计强✅-exec配\;很慢-perm语法跨平台不一致在线计算器临时换算、教学、写文档无否不覆盖四位特殊权限实战场景场景一SSH 私钥权限OpenSSH 对私钥权限有硬性检查太开就直接拒绝使用。报错原文从ssh二进制里的字符串表提取 WARNING: UNPROTECTED PRIVATE KEY FILE! Permissions 0644 for /home/deploy/.ssh/id_rsa are too open. It is required that your private key files are NOT accessible by others. This private key will be ignored.标准配置chmod700~/.sshchmod600~/.ssh/id_ed25519# 私钥chmod644~/.ssh/id_ed25519.pub# 公钥可以给 644chmod600~/.ssh/authorized_keyschmod600~/.ssh/configchmod644~/.ssh/known_hosts# 644 也可以⚠️ 注意~/.ssh目录本身也要700。目录权限过开时即使私钥是600某些配置下 sshd 也会拒绝。场景二Web 目录 403前面开头那个问题的通用排查流程# 1. 看整条路径逐级检查namei-l/var/www/site/index.html# 2. 确认 nginx worker 的实际身份psaux|grepnginxgrep-E^\s*(user|User)/etc/nginx/nginx.conf# 3. 从 nginx 的角度试读sudo-uwww-datacat/var/www/site/index.htmlsudo-uwww-datals/var/www/site常见解法按优先级# 方案 A把目录属组改成 web 服务用户所在组chgrp-Rwww-data /var/www/sitechmod750/var/www/sitechmod640/var/www/site/*.html# 方案 B路径上缺 x 的父目录补上chmodox /var/www# 方案 C用 ACL 精确授权不动原有权限setfacl-R-mu:www-data:rX /var/www/site setfacl-R-d-mu:www-data:rX /var/www/site# 默认 ACL新建文件自动继承❌不要用chmod -R 777 /var/www。它确实能解决 403但代价是任何本地用户都能改你的代码。共享主机上一个被入侵的账号就能给你植入后门。setfacl里的X和 chmod 一样是大写语义只给目录和已有执行位的文件加x。macOS 上没有setfacl对应的是chmod a user:www-data allow list。场景三脚本 clone 下来跑不动$ ./deploy.sh zsh: permission denied: ./deploy.sh $ls-ldeploy.sh -rw-r--r--1dev dev412Sep3017:30 deploy.sh根因是Git 只记录执行位这一个权限信息实测本地 644 → git 索引里是 100644 本地 755 → git 索引里是 100755 本地 664 → git 索引里是 100644 本地 600 → git 索引里是 100644也就是说读权限、setuid、setgid、sticky 这些 Git 全都不管它只关心这个文件可不可执行。所以如果提交的时候脚本没有x别人 clone 下来一定跑不了。修复chmodx deploy.shgitadddeploy.shgitcommit-mchore: make deploy.sh executablegitls-files-sdeploy.sh# 确认变成 100755如果项目里已经有一堆脚本要批量修gitls-files*.sh|xargschmodxgitls-files-s|awk$1100644 $4 ~ /\.sh$/ {print $4}|xargs-rgitupdate-index--chmodx另外注意core.fileMode在 Windows/WSL 或者 FAT/NTFS 挂载的仓库上Git 可能识别不到权限变化需要git config core.fileMode true。场景四Docker 容器里权限不对# 推荐构建期就把权限定死不依赖宿主机 COPY --chmod755 entrypoint.sh /app/entrypoint.sh COPY --chmod644 config.yaml /app/config.yaml--chmod需要 BuildKitDocker 18.09 默认开启或设DOCKER_BUILDKIT1。运行时挂 volume 的坑# 宿主机上的目录权限会被原样带进容器dockerrun-v$PWD/data:/datamyappls-ld/data容器内进程如果以非 root 用户跑比如USER 1000:1000而宿主机目录是750 root:root就会写不进去。解法chown-R1000:1000 ./data# 宿主机上改# 或者容器内用固定 UID 启动dockerrun--user1000:1000-v$PWD/data:/datamyapp常用权限速查表数字符号典型用途777rwxrwxrwx所有权限全开安全上几乎从不需要775rwxrwxr-x团队协作目录组可写755rwxr-xr-x目录与可执行文件标配750rwxr-x---组内可进外部完全无权限700rwx------仅属主可进如~/.ssh644rw-r--r--普通文件标配640rw-r-----组可读外部无如日志600rw-------仅属主读写SSH 私钥400r--------只读证书、只读配置1777rwxrwxrwt/tmp人人可写但只能删自己的4755rwsr-xr-xsetuid以文件属主身份执行2755rwxr-sr-xsetgid新文件继承目录属组反向查已有文件的权限stat-c%a %A %U %G %nfile# LinuxGNUstat-f%A %Sp %Su %Sg %Nfile# macOSBSDls-lddir# 两个平台通用工程落地把权限写进部署流程别靠手工 chmod手工改权限的问题是不可复现——下次部署又得重来一遍。#!/usr/bin/env bashset-euopipefailDEPLOY_DIR/var/www/app# 用 install 一次性搞定复制 权限比 cp 之后再 chmod 可靠install-d-m755-owww-data-gwww-data$DEPLOY_DIRinstall-m640-gwww-data config.yaml$DEPLOY_DIR/install-m755bin/myapp$DEPLOY_DIR/bin/# 批量修正find$DEPLOY_DIR-typed-execchmod755{}find$DEPLOY_DIR-typef-execchmod644{}find$DEPLOY_DIR-typef-name*.sh-execchmod750{}实测各工具对权限的处理工具行为install -m 644 src dst复制并强制设置成 644一步到位cp src dst按 umask 生成新权限不继承源文件cp -p src dst保留源文件权限、属主、时间戳tar -xf默认保留归档里的权限root 解包会连属主一起恢复rsync -a归档模式保留权限rsync不带-a权限会按 umask 重算git只记录执行位100644 / 100755mv权限跟着 inode 走完全不变 最省心的做法部署脚本里用install -m替代cpchmod两步权限意图直接写在命令里review 的时候一眼能看出来。安全审计定期扫一遍有没有 777# 全盘找 others 可写的文件排除 /proc /sys /tmpfind/-xdev-typef-perm/ow\!-path/tmp/*!-path/var/tmp/*-printf%m %u:%g %p\n2/dev/null# 找所有带 setuid / setgid 的可执行文件提权风险清单find/-xdev-typef\(-perm-4000-o-perm-2000\)-execls-l{}# 找 nobody 可写的 web 目录find/var/www-typed-perm-ow-xdev很重要它会阻止 find 跨越文件系统边界避免扫到/proc、网络挂载和容器层。常见问题Qchmod 777之后还是 Permission deniedA往上看目录。文件权限对了但父目录缺x一样进不去。用namei -l 完整路径逐级检查。另外确认一下是不是 SELinux / AppArmor 在拦getenforce、sudo ausearch -m avc -ts recent或者文件系统是只读挂载mount | grep / 。Q为什么chmod -R 755之后所有文件都能执行了A数字模式是绝对赋值-R会把目录和文件一视同仁。正确做法是分开处理find . -type d -exec chmod 755 {} 和find . -type f -exec chmod 644 {} 或者直接用符号模式的chmod -R arX大写 X 只给目录和本来就可执行的文件加 x。Qchmod -R会不会顺着符号链接改到树外面的文件A不会。GNU chmod 的-R默认是-P不跟随符号链接实测递归改一个包含外部软链接的目录链接指向的原文件权限保持不变。要跟随得显式加-L。想只改链接自身用chmod -h。Q删除一个 444 的只读文件为什么还能成功A删除操作检查的是所在目录的wx跟文件本身的权限无关。想防止误删要么去掉目录的写权限要么给目录加 sticky 位chmod t要么用chattr i fileLinux 的 immutable 标志root 也删不掉。Qsetuid 加在 shell 脚本上为什么没效果ALinux 内核出于安全考虑会忽略解释型脚本上的 setuid 位。需要提权的话用sudo配精确到命令的规则或者写一个 setuid 的 C wrapper。Qumask设成 000新建的文件会是 777 吗A不会是666。创建文件时内核申请的基准权限就是0666umask 只能做减法。目录的基准是0777所以 umask 000 时新建目录才是777。QWindows 上编辑的文件传到 Linux 权限全乱了ANTFS/FAT 没有 Unix 权限模型。通过 WSL、SMB、或者 Docker Desktop 的 volume 挂载进来的文件权限通常由挂载参数metadata、drvfs的umask选项决定改不动也留不住。解法是在容器或部署脚本里显式chmod别指望文件系统帮你记。总结Linux 文件权限看起来就是九个位实际上✅rwx 在文件和目录上语义不同——目录的x是可穿越缺了它整条路径都断✅ 排查 403 / Permission denied 要从/开始逐级看用namei -l最快✅ 数字模式是绝对覆盖符号模式是增量修改批量场景优先符号模式✅ 批量改权限永远用大写Xchmod -R arX别用小写x✅ setuid / setgid / sticky 是第四位符号表示里大写S/T意味着设了但没有 x✅ 新建文件的权限 0666 ~umaskumask 不能给你加执行位✅ Git 只记录执行位脚本要git add之前先chmod x✅ 部署流程里用install -m把权限意图写进命令别手工 chmod相关链接Chmod 权限计算器https://leowh.com/tools/chmod/index.htmlGNU coreutils chmod 手册https://www.gnu.org/software/coreutils/manual/html_node/chmod-invocation.html参考资料man 1 chmod、man 2 chmod、man 2 umaskPOSIX 权限模型man 1 find-perm的/、-、三种匹配语义GNU coreutils 手册 §15.20 install、§12.1 cpOpenSSHssh.c/readconf.c中的私钥权限检查逻辑Linux man-pagescredentials(7)setuid / setgid 的有效用户语义Dockerfile 参考文档COPY --chmod本文验证说明目录r无x、x无r的行为差异均为本机实测macOS 14BSD 用户态Linux 行为一致常用权限对照表、特殊位符号渲染4755 → rwsr-xr-x、4644 → rwSr--r--、1777 → rwxrwxrwt、1766 → rwxrw-rwT由脚本生成并经ls -l核对umask 表格为实测值022 → 644/755、027 → 640/750、077 → 600/700、000 → 666/777chmod -R arX的大写 X 行为为实测目录与已有 x 的文件加执行位普通文件不加Git 索引模式100644/100755为git ls-files -s实测输出install -m、cp -p、tar的权限行为均为实测OpenSSH 报错文本提取自本机/usr/bin/ssh二进制的字符串表setgid 目录2775在本机因系统策略限制未能设置成功其新建文件继承目录属组的行为按 POSIX / Linux 标准语义描述未做本机实测-perm /owGNU与-perm owBSD的差异在本机 BSD find 上复现