How I Fix the WordPress HTTP Error When Uploading Images — The Five Causes I Keep Seeing

· 9 min read

The WordPress media uploader gives you two words when something goes wrong: "HTTP error." No error code, no stack trace, no hint at what failed. Just a red label and a broken upload.

I see this on client sites a few times a month. The frustrating part is that the cause is almost never the same twice. Over the years I've narrowed it down to five root causes that account for virtually every case.

Before You Debug: Check the Error Log

WordPress won't tell you what happened, but your server will. The fastest way to find the actual error is to check the PHP error log while reproducing the upload failure.

If you have SSH access:

tail -f /var/log/php-fpm/error.log

The exact path depends on your setup. On cPanel servers it's usually ~/logs/error.log or /var/log/apache2/error.log. On CloudPanel or custom stacks, check your PHP-FPM pool config for the error_log directive.

Upload the image in another browser tab while tailing the log. The error that appears will tell you exactly which of the five causes you're dealing with.

If you don't have SSH access, enable WordPress debug logging temporarily:

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );

Then try the upload and check wp-content/debug.log.

Cause 1: PHP Memory Exhaustion

What the log says:

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 65536 bytes)

WordPress doesn't just save the file you upload. It creates multiple resized versions — thumbnail, medium, large, and any custom sizes your theme registers. If the original image is large (say, a 6000x4000px photo straight from a camera), WordPress needs enough memory to hold the full uncompressed image in RAM while generating each thumbnail.

A 6000x4000 image at 24-bit colour depth takes roughly 72MB of raw memory just for the pixel data. Add the resized versions being generated simultaneously and you can easily exceed 128MB.

The fix:

Increase the PHP memory limit. The right place depends on your server:

# Find which php.ini PHP-FPM is actually using
# Important: php -i shows the CLI config, which is often different from FPM
php-fpm8.3 -i 2>/dev/null | grep "Loaded Configuration File"

# If that doesn't work, drop a temporary phpinfo file and hit it in a browser:
echo '<?php phpinfo();' > /var/www/html/phpinfo.php
# Then visit yoursite.com/phpinfo.php and look for "Loaded Configuration File"
# DELETE the file immediately after — it exposes your entire server config

Set memory_limit to at least 256M:

memory_limit = 256M

Then restart PHP-FPM:

systemctl restart php8.3-fpm

If you're on shared hosting and can't edit php.ini, add this to wp-config.php:

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

The real fix: Don't upload 6000x4000px images to a website. Resize them before uploading, or use a plugin like Imagify or ShortPixel that compresses on upload. I've had clients uploading 15MB TIFF files from their photographer. A 2000px wide JPEG at quality 80 is more than enough for any screen and uses a fraction of the memory to process.

Cause 2: Imagick Multi-Threading Crashes

What the log says:

PHP Fatal error: Uncaught ImagickException: unable to read image data

Or sometimes the PHP-FPM worker just dies silently with no error — the log shows the worker process being spawned again immediately after the upload attempt.

WordPress uses Imagick (ImageMagick's PHP extension) as its default image editor. Imagick tries to use multiple CPU threads via OpenMP when processing images. On many hosting environments — especially shared hosting or containers with restricted CPU access — this causes crashes or hangs because the threading model conflicts with PHP-FPM's process model.

This is a well-documented WordPress issue going back to WordPress 4.5.

The fix:

Limit Imagick to a single thread. The simplest method is adding this to your .htaccess file (Apache) or your server's environment:

SetEnv MAGICK_THREAD_LIMIT 1

For nginx, set the environment variable in your PHP-FPM pool config:

env[MAGICK_THREAD_LIMIT] = 1

If that doesn't resolve it, you can force WordPress to use GD instead of Imagick entirely. GD is simpler and more stable, though it produces slightly lower quality output:

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

This tells WordPress to prefer GD and only fall back to Imagick if GD isn't available.

Cause 3: Upload Size Limits

What the log says:

PHP Warning: POST Content-Length of 15728640 exceeds the limit of 8388608

Or sometimes nothing at all — nginx returns a 413 before PHP even sees the request.

Three separate limits control how large an upload PHP will accept, and all three need to be high enough:

upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 300

But if you're running nginx, there's a fourth limit that catches people:

client_max_body_size 64m;

This goes in your server or location block. Without it, nginx rejects the upload with a 413 status before it even reaches PHP — and WordPress shows "HTTP error" with nothing useful in the PHP log.

How to check current limits:

# Use php-fpm -i (not php -i, which shows CLI values that may differ)
php-fpm8.3 -i 2>/dev/null | grep -E "upload_max_filesize|post_max_size|memory_limit"

# Or check from within WordPress — add this temporarily to a must-use plugin:
# <?php error_log('upload_max_filesize=' . ini_get('upload_max_filesize'));

For nginx:

nginx -T 2>/dev/null | grep client_max_body_size

After changing any of these, restart both PHP-FPM and nginx:

systemctl restart php8.3-fpm nginx

Cause 4: mod_security or WAF Blocking the Upload

What the log says:

Your PHP logs will be clean. The block happens at the web server or firewall level before the request reaches PHP.

Check the ModSecurity audit log:

grep "upload" /var/log/modsec_audit.log | tail -20

Or on cPanel servers:

grep "upload" /usr/local/apache/logs/modsec_audit.log | tail -20

You'll see entries like ModSecurity: Access denied with code 403 with a rule ID.

ModSecurity and other WAFs (Web Application Firewalls) inspect the body of POST requests. Image uploads — especially large ones or those with EXIF metadata containing unusual byte sequences — can trigger false positives on rules designed to catch file upload attacks.

The fix:

The correct approach is to whitelist the specific rule that's firing, not to disable ModSecurity entirely. Find the rule ID from the audit log (it looks like id "950005") and add an exclusion:


    SecRuleRemoveById 950005

If you're on managed hosting and can't edit ModSecurity rules, contact your host. Most will whitelist the rule for you once you give them the rule ID.

As a temporary diagnostic step, you can test whether mod_security is the cause by adding this to .htaccess:


    SecFilterEngine Off
    SecFilterScanPOST Off

Remove this immediately after testing — it disables all ModSecurity protection on your site.

Cause 5: File Permission Problems

What the log says:

PHP Warning: move_uploaded_file(): Unable to move '/tmp/phpXXXXXX' to '/var/www/html/wp-content/uploads/2026/04/image.jpg'

WordPress uploads go through two stages: PHP first writes the file to its temporary directory (usually /tmp), then WordPress moves it to wp-content/uploads/. If either directory has wrong permissions, the upload fails.

Check permissions:

ls -la /tmp/ | head -5
ls -la /var/www/html/wp-content/uploads/

The uploads directory should be owned by the web server user (usually www-data, apache, or nobody) and have permissions of 755 for directories and 644 for files:

chown -R www-data:www-data /var/www/html/wp-content/uploads/
find /var/www/html/wp-content/uploads/ -type d -exec chmod 755 {} \;
find /var/www/html/wp-content/uploads/ -type f -exec chmod 644 {} \;

Also check that PHP's upload temp directory is writable:

php-fpm8.3 -i 2>/dev/null | grep upload_tmp_dir

If it's set to a custom path, make sure that path exists and is writable by the PHP-FPM user.

I've seen this happen most often after server migrations where files were transferred as root, or after a hosting provider changed the PHP-FPM pool user without updating directory ownership.

A Quick Diagnostic Checklist

When a client reports "HTTP error" on upload, I run through these checks in order:

  1. Tail the PHP error log and reproduce the upload
  2. If "memory size exhausted" — increase memory_limit, resize the source image
  3. If Imagick exception or silent worker death — limit MAGICK_THREAD_LIMIT to 1
  4. If "POST Content-Length exceeds" or 413 in nginx — increase upload_max_filesize, post_max_size, and client_max_body_size
  5. If PHP logs are clean — check ModSecurity/WAF audit logs
  6. If "Unable to move" — fix directory ownership and permissions

The error log always has the answer. The problem is that WordPress buries it behind two of the least helpful words in its vocabulary.

If you're tired of chasing vague WordPress errors on your own, my maintenance plans include proactive server monitoring and fast-response debugging when things break.

Stop Firefighting. Start Maintaining.

I manage 70+ WordPress sites for UK agencies and businesses. Whether you need ongoing maintenance, emergency support, or a one-off performance fix — I can help.

View Maintenance Plans Get in Touch

Get in Touch to Discuss Your Needs