今天遇到了 Typecho 附件上传失败的问题,经过一番排查终于解决了。这里记录一下解决方案,希望能帮到遇到同样问题的朋友。

Ps:本文中的所有路径、用户名、数据库名均为示例,请根据您的实际服务器环境替换相应内容

常见原因

1.目录权限不足Web 服务器用户对 uploads 目录没有写入权限
2.PHP、Nginx 配置限制PHP 的 upload_max_filesize、post_max_size 或 Nginx 的 client_max_body_size 限制
3.虚拟路径映射服务器管理面板导致实际路径与 PHP 访问路径不一致
4.attachmentTypes 配置错误附件类型配置格式错误导致解析失败

本人遇到的问题

在我的案例中,问题是虚拟路径映射配置错误的组合:

  1. 虚拟路径映射问题:使用服务器管理面板后,实际文件路径与 PHP 访问路径存在映射关系,导致在 Shell 中修改权限后,PHP 仍然无法写入
  2. attachmentTypes 配置错误:数据库中的配置混合了宏定义(@image@)和具体扩展名,导致解析失败

虽然目录权限看似正确(所有者 www-data,权限 755),但由于路径映射的存在,实际上 PHP 访问的是另一个路径,该路径的权限才是关键。

排查与修复方案

1. 目录权限检查与修复

这是比较常见的上传失败原因,必须优先检查。

步骤1:查看 Web 服务器运行用户

ps aux | grep -E "php-fpm|nginx|apache" | head -2

记住运行用户,通常是 www-datanginxapache

步骤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>";
?>

希望我的案例和这份方案可以帮到你 有任何问题欢迎反馈交流

最后修改:2026 年 02 月 19 日
如果觉得我的文章对你有用,请随意赞赏