今天遇到了 Typecho 附件上传失败的问题,经过一番排查终于解决了。这里记录一下解决方案,希望能帮到遇到同样问题的朋友。
Ps:本文中的所有路径、用户名、数据库名均为示例,请根据您的实际服务器环境替换相应内容
常见原因
| 1.目录权限不足 | Web 服务器用户对 uploads 目录没有写入权限 |
| 2.PHP、Nginx 配置限制 | PHP 的 upload_max_filesize、post_max_size 或 Nginx 的 client_max_body_size 限制 |
| 3.虚拟路径映射 | 服务器管理面板导致实际路径与 PHP 访问路径不一致 |
| 4.attachmentTypes 配置错误 | 附件类型配置格式错误导致解析失败 |
本人遇到的问题
在我的案例中,问题是虚拟路径映射和配置错误的组合:
- 虚拟路径映射问题:使用服务器管理面板后,实际文件路径与 PHP 访问路径存在映射关系,导致在 Shell 中修改权限后,PHP 仍然无法写入
- attachmentTypes 配置错误:数据库中的配置混合了宏定义(
@image@)和具体扩展名,导致解析失败
虽然目录权限看似正确(所有者 www-data,权限 755),但由于路径映射的存在,实际上 PHP 访问的是另一个路径,该路径的权限才是关键。
排查与修复方案
1. 目录权限检查与修复
这是比较常见的上传失败原因,必须优先检查。
步骤1:查看 Web 服务器运行用户
ps aux | grep -E "php-fpm|nginx|apache" | head -2
记住运行用户,通常是 www-data、nginx 或 apache。
步骤2:检查上传目录权限
ls -la /your/website/path/usr/uploads/
查看所有者和权限是否正确。
步骤3:修复权限
将目录所有者改为 Web 服务器用户(将 www-data 替换为你的实际用户)
chown -R www-data:www-data /your/website/path/usr/uploads/
设置合适的权限
chmod -R 755 /your/website/path/usr/uploads/
如果上传附件仍然失败:尝试给予完全权限(不推荐,但可以排查问题)
chmod -R 777 /your/website/path/usr/uploads/
2. 虚拟路径映射诊断与修复
服务器管理面板(1Panel、宝塔等)可能导致路径映射问题。
步骤1:创建诊断脚本
在网站根目录创建 path_test.php:
<?php
echo "<h3>路径诊断</h3>";
echo "PHP 访问的上传目录: " . __DIR__ . "/usr/uploads<br>";
echo "目录是否存在: " . (is_dir(__DIR__ . "/usr/uploads") ? "是" : "否") . "<br>";
echo "目录可读: " . (is_readable(__DIR__ . "/usr/uploads") ? "是" : "否") . "<br>";
echo "目录可写: " . (is_writable(__DIR__ . "/usr/uploads") ? "是" : "否") . "<br>";
// 显示实际物理路径
echo "<br>实际物理路径: " . realpath(__DIR__ . "/usr/uploads") . "<br>";
?>步骤2:访问诊断脚本
浏览器访问 https://yourdomain.com/path_test.php,记录 PHP 显示的路径。
步骤3:对比路径
在 Shell 中查看:
pwd
假设输出: /opt/1panel/www/sites/yoursite
如果 Shell 路径与 PHP 路径不一致(例如 PHP 显示 /www/sites/yoursite),说明存在路径映射。
步骤4:修复权限
对 PHP 实际访问的路径修改权限:
chown -R www-data:www-data /实际PHP访问的路径/usr/uploads/
chmod -R 755 /实际PHP访问的路径/usr/uploads/
步骤5:清理诊断文件
rm /your/website/path/path_test.php
3. attachmentTypes 配置修复
配置错误会导致 Typecho 无法识别允许的文件类型。
步骤1:检查当前配置
mysql -u 数据库用户名 -p 数据库名 << EOF
SELECT value FROM typecho_options WHERE name = 'attachmentTypes';
EOF
步骤2:识别错误
错误配置示例(混合宏定义和扩展名):
@image@,@media@,@doc@,gif,jpg,jpeg,png...
正确配置(只包含扩展名):
gif,jpg,jpeg,png,tiff,bmp,webp,avif,mp3,mp4,mov,wmv,wma,rmvb...
步骤3:修复配置
mysql -u 数据库用户名 -p 数据库名 << EOF
UPDATE typecho_options
SET value = 'gif,jpg,jpeg,png,tiff,bmp,webp,avif,mp3,mp4,mov,wmv,wma,rmvb,rm,avi,flv,ogg,oga,ogv,txt,doc,docx,xls,xlsx,ppt,pptx,zip,rar,pdf'
WHERE name = 'attachmentTypes';
EOF步骤4:验证修复
mysql -u 数据库用户名 -p 数据库名 << EOF
SELECT value FROM typecho_options WHERE name = 'attachmentTypes';
EOF
确认没有 @image@ 等宏定义。
步骤5:重启 PHP-FPM
systemctl restart php-fpm
或根据你的 PHP 版本:php7.4-fpm、php8.1-fpm 等
4. PHP、Nginx 配置限制修复
配置限制了上传文件的大小
PHP 配置
步骤1:检查 PHP 配置
php -i | grep -E "upload_max_filesize|post_max_size"
或创建 info.php 查看:
<?php phpinfo(); ?>
步骤2:修改 PHP 配置
找到 php.ini 文件:
php --ini
编辑配置文件,修改以下参数:
upload_max_filesize = 64M
post_max_size = 64M
步骤3:重启 PHP-FPM
systemctl restart php-fpm
或根据你的 PHP 版本:php7.4-fpm、php8.1-fpm 等
步骤4:验证修改
php -i | grep -E "upload_max_filesize|post_max_size"
Nginx 配置
步骤1:检查 Nginx 配置
nginx -T | grep client_max_body_size
步骤2:修改 Nginx 配置
编辑网站配置文件(通常在 /etc/nginx/sites-available/ 或 /etc/nginx/conf.d/):
server {
# ...
client_max_body_size 64M;
# ...
}或在 http 块中全局设置:
http {
client_max_body_size 64M;
}步骤3:测试配置
nginx -t
步骤4:重载 Nginx
systemctl reload nginx
步骤5:验证修改
nginx -T | grep client_max_body_size
完整诊断脚本
如果不确定问题在哪,可以尝试使用这个脚本一次性检查所有项,将此文件保存为 diagnose.php 放到网站根目录,访问后会显示所有诊断信息
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
echo "<h2>Typecho 附件上传完整诊断</h2>";
// 1. 路径检查
echo "<h3>1. 路径检查</h3>";
$uploadDir = __DIR__ . '/usr/uploads';
echo "PHP 访问路径: $uploadDir<br>";
echo "实际物理路径: " . realpath($uploadDir) . "<br>";
echo "目录存在: " . (is_dir($uploadDir) ? "是" : "否") . "<br>";
// 2. 权限检查
echo "<h3>2. 权限检查</h3>";
echo "可读: " . (is_readable($uploadDir) ? "是" : "否") . "<br>";
echo "可写: " . (is_writable($uploadDir) ? "是" : "否") . "<br>";
// 3. 写入测试
echo "<h3>3. 写入测试</h3>";
$testFile = $uploadDir . '/test_' . time() . '.txt';
if (@file_put_contents($testFile, 'test')) {
echo "写入成功<br>";
@unlink($testFile);
} else {
echo "写入失败<br>";
$error = error_get_last();
echo "错误: " . $error['message'] . "<br>";
}
// 4. PHP 配置
echo "<h3>4. PHP 上传配置</h3>";
echo "file_uploads: " . (ini_get('file_uploads') ? "开启" : "关闭") . "<br>";
echo "upload_max_filesize: " . ini_get('upload_max_filesize') . "<br>";
echo "post_max_size: " . ini_get('post_max_size') . "<br>";
// 5. 磁盘空间
echo "<h3>5. 磁盘空间</h3>";
$total = disk_total_space('/');
$free = disk_free_space('/');
$used = $total - $free;
$percent = round(($used / $total) * 100, 2);
echo "总空间: " . round($total / 1024 / 1024 / 1024, 2) . " GB<br>";
echo "已用: " . round($used / 1024 / 1024 / 1024, 2) . " GB<br>";
echo "剩余: " . round($free / 1024 / 1024 / 1024, 2) . " GB<br>";
echo "使用率: $percent%<br>";
// 6. 环境信息
echo "<h3>6. 环境信息</h3>";
echo "PHP 版本: " . phpversion() . "<br>";
echo "运行用户: " . (function_exists('posix_getpwuid') ? posix_getpwuid(posix_geteuid())['name'] : '未知') . "<br>";
echo "临时目录: " . sys_get_temp_dir() . "<br>";
// 7. 数据库配置检查
echo "<h3>7. 数据库配置</h3>";
try {
require_once __DIR__ . '/config.inc.php';
$db = \Typecho\Db::get();
$result = $db->fetchRow($db->select('value')->from('table.options')->where('name = ?', 'attachmentTypes'));
if ($result) {
$value = $result['value'];
echo "attachmentTypes 配置: " . (strlen($value) > 100 ? substr($value, 0, 100) . '...' : $value) . "<br>";
// 检查是否包含宏定义
if (strpos($value, '@image@') !== false || strpos($value, '@media@') !== false) {
echo "<span style='color:red'>警告:配置包含宏定义,可能导致解析错误!</span><br>";
}
}
} catch (Exception $e) {
echo "无法连接数据库<br>";
}
echo "<hr>";
echo "<p>诊断完成。根据以上结果对照排查方案进行修复。</p>";
?>希望我的案例和这份方案可以帮到你 有任何问题欢迎反馈交流