把PHP项目从开发机推到服务器,最容易卡住人的往往不是数据库配置,也不是PHP扩展缺失,而是这几个关键词组合出来的问题:脚本要写日志、要写缓存,但目标目录没有写权限,于是程序要么白屏,要么疯狂报错,要么看似正常却什么都没记下来。
很多人遇到这类问题,第一反应是chmod 777。但目录权限这事,拆开看其实很有讲究。为什么日志和缓存必须让PHP进程有写权限?到底该给哪个用户、哪个组、哪些目录放权?目录权限和文件权限有什么不同?SELinux为什么会从中间插一脚?这篇文章就把“PHP脚本需写入日志、缓存 → 必须对目录有写权限”这件事像庖丁解牛一样拆开,从底层机制到实操命令,从排错思路到权限收敛,一次性讲透。不管是刚接触LNMP的小白,还是被线上问题折腾过的开发者,都能找到可落地的方案。
1. 为什么日志和缓存都绕不开“目录可写”
1.1 日志是程序的黑匣子,写不进去等于蒙眼开车
PHP应用跑起来之后,开发者不可能一直盯着浏览器看结果,绝大部分运行信息都要靠日志来记录。框架的异常栈、接口的慢请求、支付回调的通知结果、定时任务的执行状态,全都记在日志文件里。ThinkPHP有runtime目录,Laravel有storage/logs目录,Symfony有var/log目录,CodeIgniter有writable/logs目录,这些都是高频写入对象的典型位置。
问题在于,PHP代码执行时,并不是以你的登录身份在操作文件,而是以PHP进程的运行用户身份在操作。如果这个运行用户对日志目录没有写权限,调用file_put_contents写日志时就会直接报错,或者被错误处理机制吞掉,表现为页面正常但日志为空。更麻烦的是,日志写不进去往往会让后续排错进入死循环,因为连最基础的错误信息都看不到。
我处理过好几次类似的线上问题,开发环境一切正常,部署到客户服务器后接口莫名其妙返回500,排查到最后,全是日志目录权限不对。把权限修好之后,错误信息立刻浮现,真正的bug反而是一行SQL写错了。这就是日志目录写权限的第一个价值:它是所有后续排错的前提,权限不对,连报错都看不见。
1.2 缓存文件也要写目录,不是只有“日志”才需要权限
很多人能理解日志要写文件,但容易忽略缓存同样需要磁盘写入。PHP项目里的缓存,除了Redis、Memcached这类外部缓存服务,还有大量文件型缓存。比如Laravel的配置缓存、路由缓存、视图编译缓存,ThinkPHP的temp目录,Symfony的var/cache,以及很多项目自己实现的“把数据库查询结果序列化到一个PHP文件里”的方案。这些缓存文件本质上都是在磁盘上新建文件、写入内容、再读取内容。
文件缓存的设计逻辑很简单:把耗时操作的结果存成文件,下次直接require或file_get_contents读出来,省去下一次动态计算。这个逻辑成立的前提就是缓存目录可写。一旦缓存目录不可写,框架可能反复重新生成缓存导致性能锐减,也可能抛异常,用户看到的就是白屏或者一直加载中。
需要注意一个边界:PHP的Opcache不属于应用层这种“写缓存”概念。Opcache是PHP引擎把编译后的字节码放到共享内存或者磁盘,启动时自动处理,应用代码不需要去写某个目录,所以权限问题一般不落在Opcache上。真正需要关注的是业务代码主动写文件缓存。
1.3 先分清“本地写文件”和“外部缓存服务”
现在很多项目用Redis做缓存,于是有人会觉得“我用了Redis,就不需要给PHP任何缓存目录写权限了吧”。这是一个常见的混合概念。Redis确实不需要本地目录写权限,PHP通过网络协议读写Redis内存。但你仍然需要日志目录写权限,因为框架默认要记录运行日志,除非你把日志都改成stdout输出。而且很多缓存策略是分层的,比如先查本地文件缓存,没有命中再去查Redis,依然绕不开本地目录。
有些环境里,Redis本身也可能落盘,比如开启了AOF持久化,那是Redis进程写自己的目录,与应用层PHP的权限问题不直接相关。所以定位问题时,要区分清楚:到底是PHP脚本要写本地文件,还是PHP脚本要连接外部服务。标题里说的“缓存必须对目录有写权限”,指的是前者。判断标准很简单:看PHP代码里操作的是文件路径,还是连接的IP/PORT,文件路径就必然涉及目录写权限。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 写权限背后的运行机制:先搞清楚是谁在写
2.1 “你能写”不代表“PHP能写”,运行身份才是关键
Linux的权限模型里没有“程序”这个概念,一切进程都有一个真实的用户身份和组身份。PHP-FPM启动后,master进程通常以root启动,但它会创建多个worker进程来处理请求,这些worker进程会切换成配置里指定的用户和组。绝大多数发行版默认配置是www-data用户,RHEL/CentOS系列可能是apache或nginx用户,也可能有人自定义成自己的部署账号。
CLI模式下没有PHP-FPM这层转换,你在终端执行php artisan xxx,进程身份就是当前登录的Shell用户。这就是为什么同样一段PHP代码,在终端跑能写文件,通过浏览器访问就报Permission denied。终端下你用root执行,往哪儿写都畅通无阻;浏览器请求走到PHP-FPM的worker,实际身份可能是www-data,它对目录并没有写权限。
我经常用这个类比:目录权限相当于一栋楼的房门规则。你本人是物业经理,能打开任何房间,但住在里面的房客只有自己房间的钥匙。PHP脚本是房客,不是你本人。所以在排查写权限问题时,第一步永远不是急着改权限,而是先搞清楚PHP到底以什么身份在跑。
2.2 目录写权限和文件写权限,其实是两件事
Linux权限看似简单,只有读、写、执行三组九个位,但落到目录和文件上,含义有不小区别。文件上的w权限表示能修改文件内容,目录上的w权限表示能在这个目录里创建、删除、重命名条目。目录上的r权限表示能列出目录内容,x权限表示能进入目录并访问其中的文件。只有r没有x,你是看不到文件具体信息的,也读不到文件内容。
这里有一个典型误判:日志文件明明存在,ls看一眼权限是rw-r--r--,于是觉得PHP应该能写。但我们要把场景拆细一点。如果PHP要写一个已存在的日志文件,文件本身没有给运行用户写权限,直接追加内容会失败;如果PHP想创建一个新的日志文件,目录的w位就起作用了。框架日志通常按天生成文件,比如laravel-2025-03-20.log,每天第一次写的时候需要新建文件,这时候光看旧文件权限没用,目录不对头就会报错。
还有一层容易忽略的是目录的x权限。即使目录有w权限,没有x权限,进程也无法进入目录完成写入。我在新服务器上遇过一次非常奇怪的问题,目录权限是drw-rw-rw-,看起来很宽松,但PHP一直提示打不开日志文件。后来用ls -ld和打头的d一看结构,才发现少了一个x,导致任何人都进不了这个目录。所以权限排查时不要只看w位,要整体看rwx组合。
2.3 运行环境不同,权限排查的方向完全不同
PHP的运行环境大致分三类。第一类是Apache的mod_php方式,PHP作为Apache模块运行,进程身份一般是Apache进程的用户,比如daemon或www-data。第二类是Nginx配PHP-FPM,也是目前最常见的部署方式,PHP请求由PHP-FPM的worker处理,身份由pool配置里的user和group决定。第三类是纯CLI,比如计划任务、队列消费、部署脚本,身份就是执行命令的登录用户。
这三类环境的症状可能完全一样,但排查入口不一样。遇到浏览器访问报权限错误,不要急着一通chmod,先确认是哪种环境。如果你用Nginx+PHP-FPM,系统里可能装了多个PHP版本,比如php8.1-fpm和php8.2-fpm并存,请求实际走的是哪个pool,那个pool配置的user才是关键。有时候修改了某个pool的user,但忘了重启php-fpm,旧进程仍然沿用旧身份在跑,也会造成“明明改对了却还报错”的困惑。
还有一个实操细节,php-fpm.conf里的user和group不一定跟文件名一样。不同项目可以在/etc/php/8.1/fpm/pool.d/下配置多个pool文件,每个pool有自己的user,千万别拿A项目的pools配置去看B项目的权限问题。
3. 实操:定位运行用户,把权限精确放给最需要的目录
3.1 第一步:确认PHP进程的真实运行用户
别猜,直接看进程。
bash复制# 列出所有PHP相关进程,看worker进程的启动用户
ps aux | grep php-fpm | grep -v grep
如果服务器是systemd管理的,可以用更精准的方式:
bash复制# 查看php-fpm主进程的工作状态
systemctl status php8.1-fpm
另外php-fpm的pool配置文件里会明确写出user和group:
bash复制# Debian/Ubuntu常见路径
grep -E "^(user|group)" /etc/php/8.1/fpm/pool.d/www.conf
# CentOS/RHEL常见路径
grep -E "^(user|group)" /etc/php-fpm.d/www.conf
还有一种办法是写一个临时PHP文件,直接调用函数输出当前进程身份:
php复制<?php
var_dump(posix_geteuid()); // 输出实际用户ID
var_dump(posix_getpwuid(posix_geteuid())); // 输出用户详情
线上环境我不太建议随便放这种脚本,跑完要记得删除,否则等于给外部暴露了系统信息。不过在你自己的开发机或测试环境,这个方式非常直观,用来确认身份百发百中。看到输出里的username后,再去对应用户名下排查目录权限,问题往往就清晰了。
3.2 第二步:用最小权限完成放权,而不是粗暴777
确定PHP运行用户之后,就可以针对性地放权。假设运行用户是www-data,项目目录在/var/www/html/myapp,只有runtime目录需要写入,典型操作是这样:
bash复制# 调整项目目录属主,代码文件可以归属你的部署用户或root
sudo chown -R deploy:www-data /var/www/html/myapp
sudo chmod -R 755 /var/www/html/myapp
# 调整runtime目录,属主改为php-fpm运行用户
sudo chown -R www-data:www-data /var/www/html/myapp/runtime
sudo chmod -R ug+rwX /var/www/html/myapp/runtime
# 设置setgid位,让runtime目录下新建的子目录自动继承组
sudo chmod g+s /var/www/html/myapp/runtime
为什么加setgid位?因为很多框架会在runtime下继续按日期或模块创建子目录,如果子目录创建时没有继承正确的组身份,创建出来的新目录组归属可能变成部署用户或其他用户,后续PHP进程再去写就会撞墙。设置了setgid,runtime下所有新目录组都会强制继承为www-data组,省去很多麻烦。
有基础的同学也可以考虑POSIX ACL方案,用setfacl给指定用户或组单独授权,不必改目录属主:
bash复制# 给www-data用户单独放行runtime目录
sudo setfacl -R -m u:www-data:rwx /var/www/html/myapp/runtime
# 设置默认ACL,让新文件自动继承
sudo setfacl -R -d -m u:www-data:rwx /var/www/html/myapp/runtime
ACL比chmod灵活,但部分老项目或备份迁移时容易弄丢ACL配置。如果团队里没人熟悉ACL,我建议先用属主加组的传统方式,出了问题好排查,后续团队能力跟上再谈精细化。
3.3 第三步:应用启动时主动创建并检查目录
有些项目运行在自动扩容的容器环境里,或者代码包压缩的时候没有保留runtime目录,如果用代码能在启动阶段自己建目录并检测权限,能少一层运维心智负担。
在PHP脚本入口或者引导文件里,可以这样做:
php复制$runtimeDir = __DIR__ . '/runtime/log/';
if (!is_dir($runtimeDir)) {
// 0755表示目录所有者可写,其他用户只读,具体按需调整
if (!mkdir($runtimeDir, 0755, true) && !is_dir($runtimeDir)) {
// 主动抛出异常,避免后续静默失败
throw new RuntimeException('无法创建日志目录: ' . $runtimeDir);
}
}
if (!is_writable($runtimeDir)) {
throw new RuntimeException('日志目录不可写: ' . $runtimeDir);
}
这种方式不是所有场景都可取。比如PHP-FPM运行用户如果没有目录所在父级的写权限,mkdir必然失败,抛出的异常反而能帮助运维快速定位到“该目录没有在部署时创建”这个问题。相对于让日志模块默默吞掉错误,我宁愿框架在启动时明确报出来,毕竟权限问题早期发现比上线后薅头发强。
3.4 开发机、测试机、正式机的权限策略要分层
开发环境用777很省心,这个我不反对,因为开发机上通常只有你自己在跑,风险面极小。但测试环境尽量模拟生产环境,按正式标准收紧权限。正式环境绝不能随意777。这样做的原因很现实:测试环境如果和正式环境的权限规则差异太大,很多权限相关问题到上线才会暴露,测试等于白测。
正式环境的推荐做法是项目代码目录保持755和root或deploy用户所有,只有runtime、storage、更明确一点就是日志和缓存目录才放权给PHP运行用户。如果某个目录既要让部署用户更新,又要让php-fpm执行用户写入,可以考虑把两者放进同一个用户组,目录属组设为该组,权限写成775或2775。
注意:不要因为图方便把整个项目目录chown成www-data。否则你每次用部署用户更新代码,都可能因为写不进文件而被迫再改一次权限,来回拉扯,越搞越乱。
4. 常见报错、踩坑与排查速查
4.1 权限不足在不同框架中的典型表现
权限问题在不同框架里报错措辞不一样,但底层原因高度一致。我整理了最常见的几种现场和报错信息,方便大家对照定位。
| 场景 | 典型报错 | 排查方向 |
|---|---|---|
| ThinkPHP运行时写runtime目录 | [ error ] mkdir(): Permission denied |
runtime目录属主与php-fpm用户不一致 |
| Laravel写storage/logs目录 | The stream or file could not be opened in append mode: failed to open stream: Permission denied |
storage/logs目录不可写 |
| 通用file_put_contents写文件 | file_put_contents(...): failed to open stream: Permission denied |
日志或缓存目录的w/x权限 |
| 无法打开已存在文件 | fopen(...): failed to open stream: Permission denied |
文件本身是否可写,目录是否有x权限 |
| 代码无报错但日志为空 | 日志文件在错误日志里不出现 | 日志模块静默吞错,权限问题被掩盖 |
看到这些报错后,先别急着搜“chmod 777”,按下面一套流程走,通常十分钟内能定位。
4.2 验证写权限的三步走,亲测有效
第一步看目录:
bash复制# 查看目录详细权限和属主属组
ls -ld /var/www/html/myapp/runtime
# 查看当前是否存在权限继承层面的隐藏设置
ls -ldZ /var/www/html/myapp/runtime
第二步用PHP真实运行用户做一次写入测试:
bash复制# 切换成www-data用户,在目标目录创建测试文件
sudo -u www-data touch /var/www/html/myapp/runtime/test.txt
# 删除测试文件,确认无残留
sudo -u www-data rm /var/www/html/myapp/runtime/test.txt
# 如果touch和rm都成功,说明目录本身写权限没问题
这一步为什么重要?因为以root身份测试写权限永远测不出来。root在Linux里几乎不受权限位限制,你在root下touch一百次都成功,换www-data可能就是Permission denied。想要模拟真实运行状态,就必须切换到真实运行用户来验证。
第三步再回到业务本身,触发一次日志写入或者缓存重建操作。修改配置后别忘了重启php-fpm或nginx,看最终结果是否正常。这样一层层排查,权限问题基本不会漏。
4.3 权限看起来没问题却还是失败?注意隐形限制
有一种很让人抓狂的情况:目录权限明明已经正确设置,用sudo -u www-data touch也成功,php脚本依然报Permission denied。遇到这种,要检查三个容易被忽略的隐形限制。
第一个是open_basedir。php.ini里如果配置了open_basedir,PHP脚本就只能访问指定目录及子目录,哪怕Linux文件权限允许,PHP自身也会拒绝访问。这个限制是PHP层面的,ps或ls都看不出来,只有进了PHP环境才能感知。可以把日志目录加进open_basedir,或者在测试环境临时注释后看是否恢复。
第二个是SELinux或AppArmor。CentOS等RHEL系默认开启SELinux时,即使普通权限全对,SELinux策略仍然可以阻止httpd或php-fpm写某个目录。用ls -Z查看目录的SELinux上下文,再看SELinux布尔值是否允许httpd对public_content_rw_t之类的目录写入。如果不想深入SELinux,在某些非关键业务服务器上可以临时setenforce 0验证;如果确实是SELinux拦路,再认真放行相应目录,不要长期关闭安全模块。
第三个是文件系统层面的问题。目录所在分区满了,或者inode耗尽,即使权限正确也无法创建新文件。用df -h和df -i检查一下磁盘和inode,这一项排查起来最快,也最不容易想到。另外还有只读挂载的盘,比如mount参数带了ro,也会表现成写权限错误。
5. 权限收敛:别让“临时放权”变成“长期裸奔”
5.1 为什么我不建议随手chmod -R 777
chmod -R 777解决权限问题的速度肉眼可见,但它关掉了Linux权限模型对你所有文件和目录的保护。777意味着任何达到这台机器文件系统访问层的用户或进程都能创建、修改、删除目录里的一切内容。一旦Web服务出现代码执行漏洞,比如文件上传处理不当或命令注入,攻击者能轻易篡改现有PHP文件,挂上恶意代码。
很多大型项目被挂马,回头排查时发现最早的问题就是某个目录被粗暴地设置成777。安全不是要求你把权限弄得极度复杂,而是把权限面缩到刚好够用的范围。日志和缓存目录需要写权限,那就只给日志和缓存目录;PHP进程只需要写这两个目录,就不要顺手把整个应用根目录也开放出去。
5.2 PHP-FPM多池是做项目隔离的利器
一台服务器部署多个PHP项目时,更规范的做法是给每个项目配置独立的PHP-FPM pool。每个pool用不同的user、group和监听Socket,项目A的PHP进程不能直接修改项目B的文件。这样权限边界就不再是“整个php-fpm看服务器上所有目录”,而是“这个项目池只能碰它自己的文件”。
配置一个独立pool大概长这样:
ini复制[myapp]
user = myapp
group = myapp
listen = /run/php/php8.1-fpm-myapp.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
目录结构上做配套设计:
bash复制/var/www/myapp
├── code/ # 代码只读
└── runtime/ # myapp:myapp,可写日志和缓存
这样项目权限被隔离在各自池子内部,互相不影响。尤其SaaS或外包项目,同一台服务器承载好几个客户站点,强烈建议按这个方案走。
5.3 日志轮转和缓存更新的日常治理
权限设置好之后,后面还有两种日常操作要盯住。第一个是日志文件轮转。Linux服务器通常用logrotate按天或按大小切割日志。切割日志相当于把当前日志文件改名并新建一个文件,如果目录没有给logrotate所属进程写权限,或者PHP-FPM还在往旧文件句柄里写,切割后可能出现日志丢失或日志不更新。
我一般会在logrotate配置里考虑copytruncate参数,它会在复制日志后直接清空原文件,而不是删除并新建,这样PHP进程持有的文件句柄不会变成指向已删除文件的无效句柄。延迟压缩可以留到切割后下一个周期,减少IO波动。
第二个是缓存文件更新要避免出现“半个文件”的读取问题。PHP-FPM是多进程并发模型,一个进程正在写缓存文件时,另一个进程可能已经读了这个文件。如果写入不是原子的,某些进程会读到一个不完整的缓存文件。传统的规避方案是把内容先写到同目录下的临时文件,写完之后rename覆盖正式缓存文件,这个机制对目录写权限的要求会多一层:必须保证临时文件能创建成功。所以这类写缓存的目录,权限要保证PHP-FPM用户能写能删。
6. 从零到一:一套可直接套用的权限配置模板
6.1 常规LNMP单站点部署示例
假设是一个使用ThinkPHP或Laravel的单站点,Web服务器是Nginx,PHP版本是8.1,站点代码放在/var/www/myapp,部署用户是deploy,PHP-FPM运行用户是www-data。完整的权限分配清单可以这样设计:
| 路径 | 属主 | 权限 | 说明 |
|---|---|---|---|
| /var/www/myapp | deploy:www-data | 755 | 代码主目录不开放写 |
| /var/www/myapp/public | deploy:www-data | 755 | Web可读可执行 |
| /var/www/myapp/runtime或storage | www-data:www-data | 2750 | PHP-FPM运行用户可写 |
| /var/www/myapp/runtime/log | www-data:www-data | 2750 | 日志目录重点保障 |
| /var/www/myapp/runtime/cache | www-data:www-data | 2750 | 缓存目录重点保障 |
| /var/www/myapp/uploads | www-data:www-data | 2750 | 如果有用户上传功能则放开 |
命令序列如下:
bash复制sudo mkdir -p /var/www/myapp
sudo chown -R deploy:www-data /var/www/myapp
sudo chmod -R 755 /var/www/myapp
# 框架运行时目录
sudo mv /var/www/myapp/runtime /var/www/myapp/runtime.bak
sudo mkdir -p /var/www/myapp/runtime/log
sudo mkdir -p /var/www/myapp/runtime/cache
sudo chown -R www-data:www-data /var/www/myapp/runtime
sudo chmod -R 2750 /var/www/myapp/runtime
注意mv和mkdir的顺序,不要直接对一个已有大量文件的目录执行chown -R后还嫌慢。更稳妥的方法是代码发布阶段就在构建机处理好目录结构,部署脚本里用install命令自动创建目录并设置属主:
bash复制# install -d可以同时完成创建目录和设置权限
sudo install -d -o www-data -g www-data -m 0750 /var/www/myapp/runtime/log
sudo install -d -o www-data -g www-data -m 0750 /var/www/myapp/runtime/cache
6.2 容器化部署中的权限问题要单独处理
用Docker跑PHP项目时,写权限问题会换一种形式出现。容器内部有用户概念,宿主机也有用户概念,如果两者UID不一致,就会出现“容器里看到目录是可写的,但容器重启后宿主机文件属主被改成了一串奇怪的UID”这种情况。
最省心的做法是让容器内PHP进程的UID与宿主机写目录的UID一致。假设宿主机要用UID 1000的用户挂载代码,Dockerfile里就把应用用户创建成UID 1000:
dockerfile复制FROM php:8.1-fpm
RUN useradd -u 1000 -m app
WORKDIR /var/www/html
USER app
这样容器内进程以app用户运行,UID是1000,跟宿主机上拥有runtime目录的用户一致。目录写权限问题基本不会因为容器内外用户不一致而爆发。
如果你用docker-compose管理,可以在服务定义里指定user参数:
yaml复制services:
php:
image: myapp-php:latest
user: "1000:1000"
volumes:
- ./code:/var/www/html
有一点要提醒,Dockerfile里如果使用php:8.1-fpm官方镜像,默认用户是www-data,UID在Debian系里通常是33。宿主机上你想让某个目录给UID 33写入,就必须先把目录属主改成33或者其他匹配方式。不要想当然地认为容器里的www-data和宿主机里的www-data是同一个人,双方只有名字相同,跨过内核边界后真正认的是UID。
6.3 CI/CD发布时如何维护目录权限
走自动化发布之后,权限问题必须在构建和发布脚本里处理,不能靠运维上线后手工修手。比如用rsync同步代码之后,要立刻执行目录权限修正命令:
bash复制#!/bin/bash
# 同步代码
rsync -avz --delete ./code/ deploy@server:/var/www/myapp/
# 远程执行权限调整
ssh deploy@server "chown -R deploy:www-data /var/www/myapp && \
chmod -R 755 /var/www/myapp && \
chown -R www-data:www-data /var/www/myapp/runtime && \
chmod -R 2750 /var/www/myapp/runtime"
发布后还需要清一次缓存和重启php-fpm,因为代码更新可能导致框架缓存键失效。常见做法是:
bash复制ssh deploy@server "cd /var/www/myapp && php artisan config:clear && \
php artisan cache:clear && \
php artisan view:clear"
需要特别说明的是,不要每次发布都执行chown -R www-data:www-data /var/www/myapp。如果你把整个项目属主改成www-data,下次部署用户deploy更新代码时反而会因为目录deploy不可写而出问题。每次都重置权限,只会让权限管理永远停留在“动态修复”的状态,无法收敛成稳定配置。
正确做法是把chown的对象限缩在未来真正需要写入的目录上,代码静态文件保持部署用户所有,发布流程每次只保证runtime或storage这类动态目录的属主和权限不被破坏。这样可以省掉大量无意义的权限变更操作,也让发布脚本更稳定。
如果你还不太确定每个框架的默认写目录到底叫什么,可以在本地先跑一遍,观察应用启动后哪些目录被创建了文件,再用find命令去确认:
bash复制find /var/www/myapp -type d -newer /tmp/code_marker | head -50
代码上线后看哪些目录有新文件生成,基本就能锁定运行时写入范围。标记文件法是运维排查文件权限时很土但很有效的手段,实际操作中可以多试试。
最后再分享一个我自己的小习惯。每次解决完一个权限问题,我都会顺手把当时用的命令和最后生效的权限配置存进项目的deploy文档里。权限问题看着小,但每换一家服务器、每换一次环境,都可能复现一遍。有一份准确的权限基线,新同事接手或者环境重建时,能省下大把抓瞎时间。如果你被类似问题折磨过,不妨也试试这个习惯。
