WordPress 上传图片提示 "HTTP 错误":一棵真能找到原因的决策树
用一次检查就定位 WordPress 上传图片时 HTTP 错误的真正原因,然后只修对的那一支:防火墙、PHP 内存、post_max_size,还是 Imagick。
发布于
“HTTP 错误”不是原因。它是媒体上传器在承认:我把文件发给服务器了,但收回来的响应我没法解析成成功——而至少有六种毫不相干的故障会产生一模一样的这几个字。在它们之间靠猜,就是为什么有人花了一下午调大 upload_max_filesize,而站点真正的问题是一条防火墙规则。
最快的出路是:先做一次观察,把这六种原因劈成两组,然后每一支只做一次检查。
第一步:读真正的响应
打开浏览器开发者工具,切到”网络”面板,重新上传一次文件。盯住发往 async-upload.php 的那个请求。返回什么,你的诊断就是什么。
| 你看到的 | 它意味着什么 | 去哪一支 |
|---|---|---|
| 403 或 406,响应体是 HTML | 防火墙或 mod_security 拒绝了请求 | 分支 A |
| 500、空响应,或被截断的响应体 | PHP 在处理中途崩溃或内存耗尽 | 分支 B |
| 413 | 请求体超出了服务器的某个上限 | 分支 C |
| 200,但响应体不是干净的 JSON | 有插件或主题在响应之前抢先输出了内容 | 分支 D |
| 请求一直不结束 / 超时 | 执行时间超限,通常出现在大文件上 | 分支 B |
这一眼就替你排除掉了网上大部分建议。如果你没法用浏览器工具,那就先在 wp-config.php 里打开日志:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
复现一次上传,然后读 wp-content/debug.log。一行 fatal 错误就能直接点名是哪一支。如果那一行读起来很晦涩,把它粘进错误日志解读工具——它会把常见的 fatal 特征映射到真正坏掉的东西上。
分支 A —— 防火墙或 mod_security 规则
403 加上一个 HTML 页面作为响应体,说明请求根本没走到 WordPress。挡在前面的某个东西——mod_security、Cloudflare、Wordfence、Sucuri,或者主机自己的规则集——认定这个 POST 看起来有恶意。
检查办法:临时停用所有安全插件,再传一次。如果毫无变化,说明规则在服务器层面,你需要主机方按失败上传的时间戳去查 mod_security 的审计日志。他们可以把那条具体的规则 ID 加白。
对这一支能搜到的建议,你得诚实一点。那些叫你往 .htaccess 里丢这两行的片段:
SecFilterEngine Off
SecFilterScanPOST Off
针对的是 mod_security 1.x,那东西基本已经绝迹了。在现代服务器上,这两条指令要么什么都不做,要么直接抛出 500 把站点搞得更糟。现代 mod_security 用的是 SecRuleEngine,而且大多数托管主机根本不允许按目录覆盖它。去问主机,别乱粘。
分支 B —— PHP 内存或执行时间限制
这是最常见的一支,特征也最清晰:它时好时坏。同一个文件第三次就传上去了,或者一张 2MB 的照片失败,一份 6MB 的 PDF 却顺利通过。硬性的配置上限每次都以相同方式失败;内存问题不会,因为可用内存取决于服务器此刻还在忙什么。
为什么偏偏是图片:WordPress 不只是把文件存起来,它还要把图片解压成原始像素,来生成每一个中间尺寸——medium_large、所有用 add_image_size 注册的尺寸、如果你开着商城还有 woocommerce_thumbnail,另外只要图片超过 big_image_size_threshold,还要再生成一份 -scaled 副本。一张 1200 万像素的 JPEG 在磁盘上只有几 MB,在内存里却大约是 48MB,这还没算缩放用的缓冲区。128MB 的上限很快就没了。
在 wp-config.php 里、“stop editing”那一行上方调高它:
define( 'WP_MEMORY_LIMIT', '256M' );
define( 'WP_MAX_MEMORY_LIMIT', '512M' );
真正的代价是:WP_MEMORY_LIMIT 不可能超过 PHP 本身允许的值。如果你的主机把 memory_limit 锁在 128M,这个常量就只是个摆设。真实数值要去”工具 → 站点健康 → 信息 → 服务器”里看,而不是看你自己写了什么。如果天花板是 PHP 自己的限制,那只有主机方能把它抬高。
分支 C —— 文件大小与请求体上限
去”工具 → 站点健康 → 信息 → 媒体处理”里看实际数值。有两个设置有影响,而人们永远只改其中一个:
upload_max_filesize—— 单个文件的上限。post_max_size—— 整个请求体的上限。
如果整个 POST 超过了 post_max_size,PHP 会在 WordPress 执行任何代码之前就丢弃这个请求。没有任何东西可返回,上传器收到一个无法解析的响应,于是说”HTTP 错误”。post_max_size 应该始终明显大于 upload_max_filesize。
有一点值得知道:当文件只是超出了 upload_max_filesize 时,上传器通常会在浏览器里就拦下来,并提示文件超过了最大上传尺寸——这是一句更清楚的话。所以一句光秃秃的”HTTP 错误”,更常指向 post_max_size 或者分支 B,而不是大家最先去调的那个设置。
改在哪里取决于你的技术栈。在使用 mod_php 的 Apache 上,.htaccess 有效:
php_value upload_max_filesize 64M
php_value post_max_size 128M
而在 PHP-FPM、LiteSpeed 或 NGINX 上——也就是绝大多数现代主机——这两行什么都不做,还可能抛出 500。请改用主机的 PHP 设置面板,或者 .user.ini 文件。NGINX 另外还会强制自己的 client_max_body_size,那个只有主机方能调高。
分支 D —— 图像处理库(Imagick 还是 GD)
只要 PHP 扩展在,WordPress 会优先用 ImageMagick,否则回退到 GD。在虚拟主机上,ImageMagick 经常就是问题所在:它的策略配置文件会限制单次操作可用的内存、磁盘和像素面积,一旦缩放触碰到其中某条限制,进程就会死掉,还不给一句有用的提示。你通常会看到大图失败、小图正常——这看起来很像分支 B,其实不是。
强制用 GD 来验证这个猜想:
add_filter( 'wp_image_editors', function ( $editors ) {
return array( 'WP_Image_Editor_GD' );
} );
把它放进 functions.php 或者一个小的 mu-plugin 里,重试上传,如果没有变化就删掉。
要说实话的取舍是:GD 并不是绝对更好。它把完整的未压缩位图放在 PHP 的内存里,所以对超大文件来说,它可能撞上 memory_limit,而 ImageMagick——部分工作在 PHP 的内存分配之外进行——本来能成功。切换过去可能只是用一种失败换了另一种失败。先把它当诊断手段,其次才是修复手段。
分支 E —— 上传目录权限
在托管主机上很少见,但在刚做完迁移或手动搬服务器之后很常见。如果 wp-content/uploads 对 Web 服务器用户不可写,或者新建的年/月子目录继承了错误的属主,写入就会失败。
ls -ld wp-content/uploads
ls -ld wp-content/uploads/2026/07
目录权限一般应为 755,属主是 PHP 运行时所用的那个用户。不要因为论坛上有人这么说就设成 777:那确实让文件夹可写从而消除了症状,但同时也让服务器上每一个账号都能写,在虚拟主机上这是实打实的风险敞口。
分支 F —— 文件名
这是整张清单里最省事的检查,所以尽管它可能性最低,也该早点做。把文件名改成纯小写字母、数字和连字符——不要重音符号、单引号、和号、# 或非拉丁字符——再传一次。特殊字符偶尔会触发安全规则,或者在编码不一致的文件系统上出问题。十秒钟就能排除。
不要这么做
别一上来就停用所有插件。 这么做干扰很大,而且只能测到分支 A 和分支 D。网络响应五秒钟就能告诉你更多。
别往 .htaccess 里随手加一行处理器指令。 那些流传的、添加 SetHandler 或 AddHandler 指令的片段,都假设了某一家主机特定的 PHP 配置。在不匹配的技术栈上,它们会用一个 500 把整个站点带下线。
别把”重试了一次就成了”当成修好了。 时好时坏正是分支 B 的签名,等服务器一忙、或者有人上传更大的文件,它就会回来。
还是没解决?
如果响应是干净的 200、JSON 也合法,上传却依然失败,那么断点在文件落地之后——通常是某个挂在附件处理链上的插件抛出了 fatal,或者某个优化类插件向外部服务发了自己的请求然后超时了。打开 WP_DEBUG_LOG,复现一次,把得到的 fatal 丢进错误日志解读工具,看看到底是哪个文件、哪个钩子该负责。
FAQ
常见问题
上传图片到 WordPress 时提示 HTTP 错误是什么意思?
意思是浏览器里的上传器把文件发给了服务器,但收回来的东西它没法解析成一个有效的成功响应。这句提示描述的是症状,不是原因。PHP 进程崩溃、防火墙拦截、403、以及空响应,都会产生一模一样的这几个字。
为什么同一张图有时候能传上去,有时候又失败?
时好时坏几乎总是指向内存或 CPU 耗尽,而不是某个配置上限。缩放一张大图需要一次性占用较多内存,而在虚拟主机上,可用内存会随服务器负载变化。像 post_max_size 这样的硬性限制,每一次都会以完全相同的方式失败。
把 WordPress 从 Imagick 切换到 GD 能解决 HTTP 错误吗?
有时可以,而且只对其中一个分支有效。虚拟主机上的 ImageMagick 常常被策略文件或很低的资源限制卡住,在 GD 能成功的场景下它反而失败。不过 GD 处理超大文件时占用内存更多,所以切换过去可能让由内存引起的失败变得更严重,而不是更好。
怎么判断是不是主机防火墙拦了上传?
打开浏览器的网络面板,上传文件,看发往 async-upload.php 的那个请求返回的状态码。返回 403 或 406 且响应体是 HTML,说明是 Web 应用防火墙或 mod_security 规则拒绝了这个请求。返回 500 或空响应,则指向 PHP 崩溃。
调大 upload_max_filesize 能修好 HTTP 错误吗?
只有当文件确实超出了你的上限时才有用,而且 post_max_size 通常更关键。如果整个请求体大于 post_max_size,PHP 会在 WordPress 运行之前就丢弃它,上传器什么都收不到。只改其中一个而不改另一个,失败会原封不动地留在那里。
文件名会导致 WordPress 出现 HTTP 错误吗?
会,不过这是最少见的一支。带重音符号的字母、单引号、和号,以及非拉丁文字,都可能触发某条安全规则,或者在文件系统编码不一致时把路径搞坏。把文件名改成纯小写字母、数字和连字符不花任何成本,大约十秒就能排除这一支。