ThinkPHP上传失败主因是PHP底层配置或权限问题:先查$_FILES'file'和phpinfo(),确认file_uploads=On、upload_max_filesize与post_max_size合理且生效,upload_tmp_dir可写,目标目录存在且PHP进程用户有权限。

ThinkPHP 文件上传失败,90% 不是框架问题,而是 PHP 底层配置或权限没对上——先别改代码,去查 $_FILES['file']['error'] 和 phpinfo()。
看 $_FILES['file']['error'] 才知道真卡在哪一步
ThinkPHP 的 $file->getError() 只封装了部分错误,很多底层失败它返回空或 misleading 信息。必须直接读原始错误码:
-
UPLOAD_ERR_OK:上传成功,可继续 move -
UPLOAD_ERR_NO_TMP_DIR:upload_tmp_dir没配或目录不可写,$file->getInfo()['tmp_name']为空就是这个征兆 -
UPLOAD_ERR_CANT_WRITE:临时目录或目标目录磁盘满、SELinux/AppArmor 拦截、或 sticky bit 缺失(/var/tmp需chmod 1733) -
UPLOAD_ERR_EXTENSION:某个扩展(如 suhosin、Suhosin)主动拦截了上传,不是 PHP 配置问题
别跳过这步——很多“move_uploaded_file 返回 false”实际是 tmp_name 根本没生成,移动操作根本没执行。
move_uploaded_file 失败时,目标目录比源文件更值得怀疑
move_uploaded_file 要求两个条件同时满足:源路径是合法上传临时文件,且目标目录存在、可写、有足够 inode 空间。但开发者常只检查后者,忽略前者。
立即学习“PHP免费学习笔记(深入)”;
- 用
is_dir($targetDir)+is_writable($targetDir)双重校验,不能只靠mkdir(..., 0755, true)就认为万事大吉 - Linux 下注意用户组:如果 PHP 进程跑在
www-data,而目录属主是root,chown -R www-data:www-data $targetDir才生效 - 避免拼接用户输入:
basename($_FILES['file']['name'])必须做,否则路径遍历漏洞可绕过所有检查 - 目标路径不要用相对路径,如
'upload/'....,应转为绝对路径:realpath(__DIR__.'/../public/upload')
Nginx + php-fpm 场景下,php.ini 可能被覆盖三次
Apache mod_php 和 Nginx + php-fpm 加载的配置文件不同,且 php-fpm pool 级配置优先级最高。改完 php.ini 却不生效,大概率是被覆盖了。
- 确认生效位置:访问
phpinfo()页面,搜索Loaded Configuration File和Scan this dir for additional .ini files - 检查 php-fpm pool 配置(如
/etc/php/8.1/fpm/pool.d/www.conf),里面可能有php_admin_value[upload_max_filesize] = 20M,它会无视php.ini -
post_max_size必须 ≥upload_max_filesize,否则 POST 数据体在进入 PHP 前就被截断,页面直接空白,$_FILES全为空 - 重启服务要完整:
systemctl restart php8.1-fpm && systemctl restart nginx,只重启 nginx 不管用
ThinkPHP 里 moveTo() 报错,先关掉 validate 再试
$file->validate(['size'=>...]) 看似安全,但它会在 moveTo() 前做一次检查,而某些验证逻辑(比如 size)依赖 tmp_name 存在。一旦前置环节失败,validate 就会误报“超限”,掩盖真实原因。
- 临时注释掉
validate(),直接调用$file->moveTo($path),看是否抛出更具体的异常 - 如果
moveTo()报Upload error: no tmp dir,说明upload_tmp_dir没生效,和 ThinkPHP 无关 - ThinkPHP 6+ 的
moveTo()默认使用move_uploaded_file,但若启用了copy模式(通过配置),则不校验上传来源,安全性下降,慎用
真正难排查的,往往不是 move_uploaded_file 本身失败,而是它根本没被执行——因为 tmp_name 是空的,而你一直在给目标目录加权限。



















