public/uploads目录不可写导致上传失败,因FrankenPHP进程用户(如www-data)无写权限,move_uploaded_file()静默失败;需确认目录存在、执行sudo chown -R www-data:www-data public/uploads && chmod -R 755 public/uploads,并同步检查upload_tmp_dir权限。

public/uploads 目录不可写导致上传失败
FrankenPHP 进程用户(如 www-data)必须对目标上传目录有写权限,否则 move_uploaded_file() 会静默失败或抛出警告“Unable to move”。这不是 PHP 配置问题,而是操作系统级权限拦截。
- 确认上传目标路径是否已创建:比如 Laravel 的
public/uploads、Symfony 的public/media,若目录不存在,move_uploaded_file必然失败 - 用进程用户身份测试写入:
sudo -u www-data touch public/uploads/test.write,失败则说明属主或权限不对 - 标准赋权命令(假设使用
www-data):sudo chown -R www-data:www-data public/uploads&&sudo chmod -R 755 public/uploads - 避免递归
chmod 777:它会让组/其他用户可写,违反最小权限原则;755 对目录足够,文件可设 644
runtime/、storage/ 等运行时目录权限不足
很多框架(Laravel、Symfony、ThinkPHP)会在 runtime/ 或 storage/ 下生成缓存、日志、session 文件。FrankenPHP 启动后若发现这些目录不可写,会报错如 “Failed to write cache file” 或直接 500,但错误日志可能被屏蔽。
- 检查目录是否存在且路径硬编码正确:例如
__DIR__.'/../runtime'在容器或 systemd 服务中可能指向错误位置 - 在应用启动前主动初始化(推荐加到
index.php开头):if (!is_writable('runtime')) { @mkdir('runtime', 0755, true); @chmod('runtime', 0755); } - 确保父目录可执行(
x权限):Linux 中进入目录需x,所以/var/www/myapp/runtime的/var/www/myapp至少要有dr-xr-xr-x - 若用 systemd 托管,注意
ProtectSystem=strict或ReadOnlyDirectories=可能封锁整个runtime/路径,需显式放开
upload_tmp_dir 临时目录权限不生效
即使 public/uploads 可写,上传仍卡在“临时文件无法写入”,说明 PHP 层的 upload_tmp_dir 没配对 —— 它和最终保存路径是两层权限,缺一不可。
- FrankenPHP 默认用系统
/tmp,但ps aux | grep frankenphp查到的运行用户(如nobody)可能无权写/tmp(尤其启用了PrivateTmp=true的 systemd 服务) - 不要依赖
sys_get_temp_dir()返回值做判断:它只反映配置,不验证实际可写性;务必用is_writable(ini_get('upload_tmp_dir'))实测 - 显式配置更可靠:在
php.ini中设upload_tmp_dir = "/var/tmp/frankenphp-upload",然后sudo mkdir -p /var/tmp/frankenphp-upload && sudo chown www-data:www-data /var/tmp/frankenphp-upload && sudo chmod 755 /var/tmp/frankenphp-upload - SELinux/AppArmor 场景下,即使目录权限全开,策略也会拦截;查
sudo ausearch -m avc -ts recent | grep php或sudo dmesg -t | tail -20看拒绝记录
容器或 systemd 中的隐藏权限限制
本地跑通、服务器部署就报权限错,大概率是运行环境隔离机制在起作用,而非代码或配置本身有问题。
立即学习“PHP免费学习笔记(深入)”;
- Docker 场景:挂载卷时默认以 root 创建目录,容器内
www-data用户无法写入;启动时加--user www-data或在 entrypoint 里chown -R www-data:www-data /app/storage - systemd 服务:检查
/etc/systemd/system/frankenphp.service是否含NoNewPrivileges=yes、RestrictSUIDSGID=yes或ProtectHome=read-only—— 这些都会干扰临时目录行为 - macOS 上通过 Homebrew 安装的 FrankenPHP,若项目目录在 iCloud 或 Dropbox 同步区,文件系统锁可能导致
fopen失败,换到/Users/xxx/Sites等本地路径即可
upload_tmp_dir 和最终保存目录的权限要分别验证,且二者都受 SELinux/AppArmor 影响**。别只盯着 public/uploads 改权限,临时目录那一环断了,上传根本走不到落地阶段。



















