WordPressTroubleshootingConfigurationPerformanceWordPress 6.xWordPress 7.xIntermediateCurrent

How to Fix the WordPress Media Upload HTTP Error for Good

A field-tested diagnosis path for WordPress 'HTTP error' on media uploads: read the real status code, then fix PHP limits, Nginx body caps, Imagick threads, or plugin conflicts.

The error that tells you almost nothing

You drag an image into the WordPress media library, the progress bar runs, and then a red box appears: “HTTP error.” No status code, no file name, no hint about whether the problem is the file, the server, or a plugin. The message is generic by design — the uploader in the admin area posts the file to async-upload.php via AJAX, and when that request fails for any reason, the JavaScript side renders the same two words.

That vagueness is why the usual advice (“deactivate all your plugins and see what happens”) wastes so much time. Plugin conflicts do cause this error, but they are one branch of a decision tree, not the trunk. The upload pipeline has four stages — browser to web server, web server to PHP, PHP to disk, and post-processing with GD or Imagick — and the “HTTP error” can originate at any of them. The fix that works is to find out which stage failed before changing anything.

This guide follows the order we actually use on client sites: extract the real error first, then walk the pipeline from the outside in. If you are seeing broader failures — a 500 error on the whole admin area, or a white screen — the problem is not the upload pipeline, and you should start with our guides on debugging WordPress errors and fixing 500 errors instead.

Step 1: Make the error tell you something

Open your browser’s developer tools, switch to the Network tab, and reproduce the upload. Find the request to async-upload.php and read two things: the HTTP status code and the response body.

The status code narrows the problem immediately:

  • 413 Request Entity Too Large — a web server or reverse proxy rejected the body before PHP ever ran. This is a client_max_body_size (Nginx) or LimitRequestBody (Apache) problem, not a WordPress problem. Skip to the server limits section.
  • 500 or 502 — PHP crashed or timed out mid-upload. Usually memory exhaustion in image post-processing, or a PHP fatal error inside a plugin hooked into the upload.
  • 200 with "error" in the JSON body — the request completed but WordPress itself rejected the file. Read the message in the response; it is usually more specific than the red box, and often points at file type, permissions, or disk space.

While you are in the developer tools, confirm the file actually left your machine. If the request to async-upload.php never appears at all, or stalls at 0 bytes uploaded, the problem is upstream of the server — an aggressive security extension in the browser, a corporate proxy, or a flaky connection. No amount of server tuning fixes that branch.

If the status is 500 or the message is still vague, turn on debugging before going further. As the Advanced Administration Handbook’s debugging guide describes, set WP_DEBUG and WP_DEBUG_LOG in wp-config.php, reproduce the upload once, and read wp-content/debug.log. A memory exhaustion line (“Allowed memory size of … bytes exhausted”) or a PHP fatal with a plugin path in it will usually end the investigation on the spot. Turn the constants back off afterward — leaving debug logging enabled on a production site is a common mistake we see in security reviews.

Step 2: Check the PHP upload and memory limits

Three PHP settings gate every upload, and hosts set all three conservatively on shared plans:

  • upload_max_filesize — the largest single file PHP will accept.
  • post_max_size — the largest total POST body. If this is smaller than your file (or your file plus the rest of the form), the upload dies before WordPress can even report a problem.
  • memory_limit — how much memory PHP may use. Uploading is not the expensive part; generating sub-sized copies of a large image is. A 6000-pixel-wide JPEG can consume several times its file size in memory while GD or Imagick resizes it.

The trap is that these are checked at different stages. A 10 MB file under a 25 MB upload_max_filesize uploads fine, then kills PHP during thumbnail generation if memory_limit is 128 MB and the image is large in pixels. From the outside that looks identical to a plugin crash — which is exactly why you read the debug log or status code first rather than guessing.

Where you change these depends on the host: a php.ini or .user.ini file, the hosting panel’s PHP options screen, or the server’s PHP-FPM pool configuration. Alongside the PHP settings, WordPress has its own memory constants. The wp-config.php handbook documents WP_MEMORY_LIMIT and WP_MAX_MEMORY_LIMIT, which raise the ceiling WordPress requests for itself (and for admin-side image processing in particular). They cannot exceed what PHP itself allows, so they are the second knob, not a substitute for the first:

define( 'WP_MEMORY_LIMIT', '256M' );
define( 'WP_MAX_MEMORY_LIMIT', '512M' );

One more server-side check that takes ten seconds: disk space and inodes. A full volume or an exhausted inode quota on budget hosting produces intermittent upload failures that no configuration change will fix, and hosts do not always surface it in the panel.

Step 3: Fix the web server body limit

If your status code was 413, this is your section. Nginx’s default client_max_body_size is 1 MB — a deliberate default that quietly breaks media uploads on any hand-configured VPS running Nginx in front of PHP-FPM. Raise it inside the relevant server or http block:

client_max_body_size 64m;

Then validate and reload: nginx -t && systemctl reload nginx. On Apache, the equivalent is LimitRequestBody, though its default is effectively unlimited and this cause is rare there. If the site sits behind a CDN, a load balancer, or a managed WAF, remember that every hop has its own body limit, and the strictest one wins. The probe is the same at every layer: upload a file just over the limit and watch for the 413 in the Network tab.

Step 4: Test whether Imagick is the culprit

WordPress ships two image engines: GD (bundled with PHP) and Imagick (the ImageMagick extension). Per the hook reference for wp_image_editors, WordPress tries Imagick first and falls back to GD only if Imagick is unavailable. Imagick generally produces better output and handles more formats, but on shared hosting it is also the classic source of upload failures: hosts commonly cap its threads or memory, and the process dies mid-resize in a way that surfaces as the generic HTTP error.

Two fixes exist, and the right one depends on your situation.

If you control the environment, limit Imagick to a single thread instead of switching engines. In Apache .htaccess:

SetEnv MAGICK_THREAD_LIMIT 1

This keeps Imagick’s quality while removing the multi-thread behaviour that trips resource limits. On Nginx you would set the same variable in the FastCGI params.

If you cannot change server environment variables, or you want to confirm the diagnosis in thirty seconds, force WordPress to use GD via the filter in a small must-use plugin or your theme’s functions.php:

add_filter( 'wp_image_editors', function ( $editors ) {
    return [ 'WP_Image_Editor_GD' ];
} );

Upload the same file again. If it now succeeds, Imagick was the problem — and you have learned something diagnostic, not just applied a workaround: the same capped-resource condition may affect any image-heavy plugin, and it is worth raising with the host.

Step 5: Isolate plugin and theme conflicts

Only after the layers above check out should you suspect code you control. The usual offenders, in rough order of frequency: image optimization plugins (they hook directly into the upload pipeline), security and firewall plugins (some inspect POST bodies aggressively), and offload plugins that copy uploads to S3-compatible storage and fail on the remote step after the local one succeeded — which looks, from the media library, exactly like a failed upload.

Deactivate all plugins at once and upload the same file. If it works, re-enable in halves rather than one by one; binary search finds the culprit in a handful of uploads instead of twenty. Switching to a default theme for the duration of the test is cheap insurance, though themes rarely cause upload failures on their own.

When you find the culprit, check whether the failure is a configuration problem before replacing the plugin — an offload plugin with the wrong region setting, or an optimizer whose compression engine is missing on the server, is a settings fix, not a migration project. And if the conflict only appeared after an update, review how you sequence updates: the safer update workflow we describe for WP-CLI includes taking a checkpoint you can compare against.

Step 6: Verify the uploads directory itself

The last stage of the pipeline is writing files to wp-content/uploads/, organized in year/month subdirectories as configured under Settings → Media (the Media Library documentation covers how these paths are used). WordPress needs to create the current month’s directory on demand. If permissions or ownership are wrong — common after a server migration, a manual restore, or a switch in PHP-FPM user — the write fails and the error surfaces, again, as the generic HTTP error.

Directories should be 755 and files 644, owned by the user PHP runs as. The tell-tale variant: uploads worked last month but fail this month, because the previous month’s directory was created under an old owner and the new one cannot be created. Fix ownership and permissions recursively on wp-content/uploads, and test by uploading any small image.

The bypass that keeps you publishing: WP-CLI

When a client needs photos live today and the fix is a conversation with their host, wp media import sidesteps the browser pipeline entirely. It creates the same attachments — from a local file or a URL — with titles, alt text, and featured image assignment all supported:

wp media import ~/photos/header.jpg \
  --title="Autumn workshop header" \
  --alt="Workshop attendees at long tables" \
  --featured_image

The command still runs image post-processing through PHP, so it inherits memory and Imagick constraints — but it skips the AJAX layer, the web server body cap, and any plugin JavaScript in the uploader. That makes it both a workaround and a diagnostic: if wp media import succeeds where the browser fails, the problem lives in the request path, not in image processing.

A closing note on recurring errors

If the HTTP error comes back every few weeks despite everything above, stop treating it as a series of one-off incidents. Intermittent upload failures on a properly configured site almost always mean the hosting environment is under-provisioned — memory limits shared with a noisy neighbour, disk quotas creeping toward full, or Imagick caps applied host-wide. At that point the decision is not which constant to tweak but whether the hosting still fits the site, which is a question worth answering with numbers rather than with error messages.

Related guides

Continue with the surrounding work.

More WordPress guides
Related insights

Broader context from the YCTheme blog.