upload_tmp_dir必须指向PHP进程用户(如www-data)可写且归属正确的目录,否则上传会静默失败或报UPLOAD_ERR_CANT_WRITE;需创建专用目录、设权属、配php.ini、重启服务,并排查AppArmor/SELinux拦截及磁盘空间。

upload_tmp_dir 配置必须指向可写且归属正确的目录
PHP 8.1 的文件上传依赖 upload_tmp_dir 指定的路径来暂存上传中的文件。如果该目录不存在、权限不对,或归属用户不是 PHP 进程实际运行者(如 www-data),上传会静默失败或报 UPLOAD_ERR_CANT_WRITE 错误。
常见错误现象包括:move_uploaded_file(): Unable to move、is_writable() returns false、phpinfo() 中 upload_tmp_dir 显示为 No value(说明配置未生效或被忽略)。
- 确认 PHP 实际运行用户:执行
ps aux | grep php-fpm或ps aux | grep apache2,看user =字段(如www-data) - 创建专用临时目录(不推荐直接用
/tmp):sudo mkdir -p /var/tmp/php-upload-8.1 - 设所有权:
sudo chown www-data:www-data /var/tmp/php-upload-8.1 - 设最小必要权限:
sudo chmod 750 /var/tmp/php-upload-8.1(避免 777) - 在对应 php.ini 中设置:
upload_tmp_dir = "/var/tmp/php-upload-8.1"(注意路径带引号)
虚拟目录下上传目标目录的权限不能只靠 chmod(0644) 解决
即使临时目录没问题,上传后保存到虚拟目录(如 /var/www/site1/uploads/)的文件仍可能无法被 Web 服务读取——因为 PHP 进程和 Web 服务器(如 Nginx)若用户不一致,或目录属组缺失,chmod(0644) 就只是“表面功夫”。
典型表现是:文件成功上传并移动了,但浏览器访问图片返回 403 或空白;ls -l 显示文件属主是 www-data,但目录属组却是 root 或空。
立即学习“PHP免费学习笔记(深入)”;
PHP 8.5.12 RC 1 候选版修复 BZ2、DOM、PDO、Opcache/JIT 和扩展内存安全问题,改进 Unicode 字素处理、URL 解析、TLS 流状态、信号分发及会话错误处理。
- 上传目录必须可写且可执行(
755或775):chmod 775 /var/www/site1/uploads - 确保目录属组与 Web 用户一致:
sudo chgrp www-data /var/www/site1/uploads - PHP 脚本中调用
chmod()必须作用于移动后的目标路径,例如:move_uploaded_file($_FILES['f']['tmp_name'], $dest); chmod($dest, 0644); - 避免 umask 干扰:在上传逻辑开头加
umask(0022),防止系统默认 umask(如 0077)导致新建文件权限过严
Apache/Nginx 虚拟主机配置会覆盖 PHP 权限行为
在虚拟主机(vhost)中启用 open_basedir 或使用 php_admin_value 设置 upload_tmp_dir,会优先于全局 php.ini 生效。若配置冲突,phpinfo() 显示的值可能和你改的文件不一致。
例如 Apache vhost 中写了:php_admin_value upload_tmp_dir "/var/www/site1/tmp",那无论全局 php.ini 怎么配,都以这个为准——而且该路径也必须满足前面说的所有权限条件。
- 检查生效配置路径:
php --ini和php -r "echo php_ini_loaded_file();" - 查当前生效的
upload_tmp_dir:php -r "echo ini_get('upload_tmp_dir');" - Apache vhost 中若用
php_admin_value,该设置不可被脚本 runtime 修改(ini_set()失效) - Nginx + PHP-FPM 场景下,需确认 pool 配置(如
/etc/php/8.1/fpm/pool.d/www.conf)中php_admin_value[upload_tmp_dir]是否被覆盖
AppArmor 或 SELinux 可能静默拦截写操作
Ubuntu 默认启用 AppArmor,它可能阻止 www-data 写入自定义路径(尤其是非标准路径如 /var/tmp/php-upload-8.1),而不抛出明显错误,只在内核日志里记一条 denied。
现象是:所有权限、归属、配置都对,is_writable() 返回 true,但上传仍失败;dmesg | tail -20 可看到类似 apparmor="DENIED" operation="mkdir" profile="/usr/sbin/php-fpm8.1" 的记录。
- 临时验证是否是策略问题:
sudo aa-disable /usr/sbin/php-fpm8.1(仅测试,勿上线) - 生产环境应更新 AppArmor profile:
sudo nano /etc/apparmor.d/usr.sbin.php-fpm8.1,添加类似/var/tmp/php-upload-8.1/** rwk, - 重载策略:
sudo systemctl reload apparmor - SELinux 环境(如 CentOS)需用
setsebool -P httpd_can_network_connect 1等,但 Ubuntu 默认不用 SELinux
实际部署中最容易被跳过的点是:确认 upload_tmp_dir 所在分区有足够空间(df -h /var/tmp),以及修改 php.ini 后忘记重启对应服务(sudo systemctl restart php8.1-fpm 或 apache2)。这两项不出错时一切正常,一出错就难定位。


















