FrankenPHP独立二进制上传失败主因是未显式配置且验证upload_tmp_dir的可写性;需在构建时通过--php-ini指定含upload_tmp_dir=/tmp/frankenphp-uploads的php.ini,运行前手动创建并赋权,同时确保目标保存路径绝对、存在、可写,并调大memory_limit、post_max_size等限制。

FrankenPHP 独立应用(即单文件二进制打包版)保存上传文件时,不会自动继承系统默认的 /tmp 权限或环境变量,且其内部 PHP 运行时对 upload_tmp_dir 和磁盘写入路径的控制比传统 PHP-FPM 更敏感——稍不注意就会出现 $_FILES 为空、move_uploaded_file() failed to open stream: No such file or directory 或 Failed to write to temporary directory 等错误。
为什么独立二进制里上传文件总失败
独立二进制运行时没有 systemd 服务上下文,不读取系统 /etc/php/*/cli/php.ini,也不自动设置 upload_tmp_dir;它只认自己打包时嵌入的 php.ini(或通过 --php-ini 指定的配置)。更关键的是:即使你代码里写了 move_uploaded_file($_FILES['file']['tmp_name'], '/var/www/uploads/...'),若目标目录在二进制运行用户(如 root 或当前 shell 用户)下不可写,或路径根本不存在(因为二进制是“隔离”的,不自动挂载宿主机目录),就会直接失败。
-
$_FILES['file']['tmp_name']指向的是内存或临时目录中的路径,该路径由upload_tmp_dir决定,不是你本地开发时的/tmp - 独立二进制默认使用极小的内存上传阈值(约 2MB),超限才落盘;但若
upload_tmp_dir不可写,连这“落盘”一步都会崩 - 打包时未显式配置
upload_tmp_dir,运行时 fallback 到系统/tmp,而容器外运行的二进制往往以普通用户启动,对/tmp有写权限,但对/var/www/uploads这类自定义路径完全没权限
必须显式配置 upload_tmp_dir 并确保可写
这是最常被跳过的一步,也是 90% 上传失败的根源。不能依赖“应该能写”,必须主动指定、验证、隔离。
- 在项目根目录新建一个
php.ini(仅用于构建),添加:upload_tmp_dir = "/tmp/frankenphp-uploads"
- 构建时用
--php-ini php.ini参数注入:frankenphp build --output myapp --php-ini php.ini - 运行前手动创建并赋权(二进制无法自己建目录):
sudo mkdir -p /tmp/frankenphp-uploads && sudo chmod 1777 /tmp/frankenphp-uploads - 验证是否生效:在
index.php中加一行var_dump(ini_get('upload_tmp_dir'));,访问看输出是否为你设的路径
保存到自定义目标路径的实操要点
独立二进制没有 Web 服务器自动帮你映射 public/uploads 到可写位置,所有路径都得你代码里硬编码或环境驱动,且必须提前存在、有权限。
立即学习“PHP免费学习笔记(深入)”;
- 不要用相对路径如
./uploads/—— 二进制工作目录不确定,很可能指向/或临时解压路径 - 推荐用绝对路径 + 环境变量控制:
$targetDir = $_ENV['UPLOAD_DIR'] ?? '/var/tmp/myapp-uploads';,然后在运行前export UPLOAD_DIR=/home/deployer/myapp/uploads - 务必在保存前检查并创建目录:
if (!is_dir($targetDir)) { mkdir($targetDir, 0755, true); } - 注意 SELinux/AppArmor:即使权限 755,某些发行版会阻止二进制进程写入非标准路径,临时测试可用
sudo setsebool -P allow_ypbind 1(SELinux)或检查aa-status(AppArmor)
大文件上传要调大 memory_limit 和 post_max_size
独立二进制默认沿用 PHP 的保守限制,max_memory_upload 隐式影响 post_max_size 行为,不显式调大会卡在解析阶段。
- 在构建用的
php.ini中至少加三行:memory_limit = 256M post_max_size = 128M upload_max_filesize = 128M
- 注意:FrankenPHP 的
max_memory_upload是内部参数,不暴露为 ini 设置,但增大memory_limit可间接放宽它 - 如果仍报
413 Request Entity Too Large,说明 Caddy 层拦截了——需在构建时通过--server-name或自定义 Caddyfile 注入request_body max_size 134217728(128MB)
真正麻烦的不是配置本身,而是独立二进制把“路径所有权”从运维移交给了开发者:你得同时当 PHP 配置员、Linux 权限管理员和 Caddy 规则编写者。漏掉任意一环,上传就静默失败——连错误日志都不打,因为日志本身可能也写不到指定位置。



















