Skip to main content
Blog|
How-to guides

The uploaded file could not be moved to wp-content/uploads

|
Mar 15, 2026|10 min read
HOW-TO GUIDESThe uploaded file could not bemoved to wp-content/uploadsHOSTNEYhostney.comMarch 15, 2026

The error “The uploaded file could not be moved to wp-content/uploads” means WordPress successfully received your file upload, but could not save it to the uploads directory on disk. PHP processed the upload and stored it in a temporary location, but when WordPress tried to move it to wp-content/uploads/2026/03/ (or whatever the current year/month directory is), the move failed.

This is a server-side issue, not a browser or network problem. The file made it to the server. Something on the server prevented it from being placed in its final location.

The most common causes are directory permissions, disk quota limits, and PHP configuration issues. Each is straightforward to diagnose and fix.

How WordPress handles file uploads

Understanding the upload process helps narrow down where the failure occurs.

  1. The browser sends the file. The file data is included in a POST request to wp-admin/async-upload.php (for the media uploader) or wp-admin/upload.php (for older upload methods).
  2. Nginx receives the request. Nginx checks whether the request body exceeds client_max_body_size . If it does, Nginx returns a 413 error before the request ever reaches PHP. If you are seeing a 413 instead of the uploads error, see 413 request entity too large in Nginx: how to fix it.
  3. PHP processes the upload. PHP stores the uploaded file in its temporary directory (usually /tmp ). PHP checks the file against upload_max_filesize and post_max_size . If either limit is exceeded, PHP sets an error code in $_FILES and WordPress reports the failure. But this produces a different error message – typically “The uploaded file exceeds the upload_max_filesize directive.”
  4. WordPress moves the file. WordPress calls move_uploaded_file() to move the file from the temporary directory to wp-content/uploads/YYYY/MM/ . This is where the “could not be moved” error occurs. The file is in /tmp , PHP has processed it, and the final move to the uploads directory failed.

The failure at step 4 has a limited set of causes: the destination directory does not exist, the directory exists but PHP does not have write permission, the disk is full, or the disk quota is exceeded.

Cause 1: Directory permissions

This is the most common cause. The uploads directory (or the year/month subdirectory within it) does not have the correct ownership or permissions for the PHP process to write to it.

WordPress creates the year/month directory structure automatically ( wp-content/uploads/2026/03/ ). If the parent directory has incorrect permissions, WordPress cannot create the subdirectory, and the upload fails.

How to check

Connect via SSH or your hosting’s file manager and check the permissions:

ls -la wp-content/
ls -la wp-content/uploads/

You should see something like:

drwxr-xr-x  user user  wp-content/
drwxr-xr-x  user user  wp-content/uploads/

The owner should be your hosting account’s user (the same user that PHP runs as). The permissions should be 755 for directories (read/write/execute for owner, read/execute for group and others).

How to fix

If the ownership is wrong (showing root or a different user instead of your account user):

chown -R your_username:your_username wp-content/uploads/

If the permissions are wrong:

find wp-content/uploads/ -type d -exec chmod 755 {} \;
find wp-content/uploads/ -type f -exec chmod 644 {} \;

Replace your_username with your actual hosting account username.

Why permissions go wrong

Permissions rarely break on their own. Common causes:

  • Manual file operations via SSH or FTP as a different user than the PHP process runs as. If you upload files via SFTP as root and PHP runs as your account user, the files will be owned by root and PHP cannot modify or create files in those directories.
  • Plugin or theme installers that change file ownership as part of their setup process.
  • Server migrations where files were transferred without preserving ownership. A migration that copies files as root and does not chown afterward leaves everything owned by root.
  • Git deployments where the checkout runs as a different user than the web server process.

The fundamental rule: the user that PHP runs as must own the uploads directory and everything in it.

Cause 2: Disk quota exceeded

If your hosting account has a storage quota and you have reached or exceeded it, no new files can be written to disk. The upload reaches the temporary directory (which may be on a different filesystem), but the move to the uploads directory fails because the quota prevents new writes.

How to check

Most hosting control panels show disk usage. Via SSH:

# Check overall disk usage
df -h

# Check your account's disk usage
du -sh ~/

On hosting with kernel-level quotas, the quota system returns “Disk quota exceeded” when the limit is hit. WordPress does not always surface this specific error clearly – it may just report the generic “could not be moved” message.

How to fix

Free up disk space by:

  • Deleting unused media files from the WordPress media library (and emptying the trash afterward – WordPress keeps deleted media in trash for 30 days by default).
  • Removing old backups. Backup plugins like UpdraftPlus, All-in-One WP Migration, and Duplicator store backup files in your account’s disk space. These can be hundreds of megabytes or gigabytes each.
  • Cleaning up revisions. WordPress stores every revision of every post. A site with years of content can have tens of thousands of revision records. Use WP-CLI to clean them:  wp post delete $(wp post list --post_type='revision' --format=ids) --force
  • Checking for large log files. Debug logs ( wp-content/debug.log ), error logs, and access logs can grow to gigabytes if left unchecked.

If your account is genuinely out of space and you need all the files you have, you need a larger hosting plan.

Inode limits

Some hosting providers enforce inode limits (total number of files and directories) in addition to storage limits. A WordPress site with many plugins, themes, and cached files can hit inode limits even with storage remaining. Check with your host if storage looks fine but writes are still failing.

Cause 3: The uploads directory does not exist

If wp-content/uploads/ was deleted or never created, WordPress cannot write to it. This is less common but happens after botched migrations, manual cleanup, or version control deployments that exclude the uploads directory.

How to check and fix

# Check if the directory exists
ls -la wp-content/uploads/

# Create it if missing
mkdir -p wp-content/uploads/
chown your_username:your_username wp-content/uploads/
chmod 755 wp-content/uploads/

WordPress creates the year/month subdirectories automatically when they are needed, but the uploads/ directory itself must exist.

Custom upload path

WordPress allows changing the upload directory via the UPLOADS constant in wp-config.php or through the upload_path option in the database (Settings > Media in older WordPress versions). If this has been customized, check that the custom path exists and has correct permissions:

// Check wp-config.php for custom upload constants
define('UPLOADS', 'custom-uploads');
-- Check the database for custom upload path
SELECT option_value FROM wp_options WHERE option_name = 'upload_path';

If a custom path is set but the directory does not exist, uploads will fail.

Cause 4: PHP temporary directory issues

Before WordPress moves the file to uploads, PHP stores it in a temporary directory. If the temporary directory is full, not writable, or does not exist, the upload fails before WordPress even gets involved.

How to check

Create a PHP info file to check the temporary directory:

<?php phpinfo(); ?>

Look for upload_tmp_dir in the output. If it is empty, PHP uses the system default (usually /tmp ). Check that this directory exists and is writable:

ls -la /tmp/

The /tmp directory should be writable by all users. If it is not, or if the partition that /tmp is on is full, uploads will fail.

open_basedir restrictions

PHP’s open_basedir directive restricts which directories PHP can access. If the temporary directory is not in the open_basedir list, PHP cannot write the uploaded file to the temporary location, and the upload fails.

Check the open_basedir value in phpinfo output. It should include /tmp (or whatever upload_tmp_dir is set to) in its list of allowed paths.

Cause 5: PHP configuration limits

Even though PHP configuration limits typically produce their own specific error messages, some combinations can result in the generic “could not be moved” error.

upload_max_filesize and post_max_size

upload_max_filesize = 64M    // Maximum size of a single uploaded file
post_max_size = 128M         // Maximum size of the entire POST request

post_max_size must be larger than upload_max_filesize . If post_max_size is too small, PHP silently discards the POST data and WordPress receives an empty upload, which can produce confusing error messages.

max_execution_time

Large files on slow connections can take longer to upload than PHP’s execution time limit allows. If the script times out during the upload, the temporary file is cleaned up and the move fails.

max_execution_time = 300    // 5 minutes, usually sufficient

max_input_time

Separate from max_execution_time , max_input_time controls how long PHP spends receiving input data (including file uploads). If this is set too low, large uploads may time out during the receive phase.

Cause 6: SELinux or security modules

On servers running SELinux in enforcing mode, the PHP process may not have the SELinux context required to write to the uploads directory. This is more common on self-managed VPS or dedicated servers than on managed hosting, but it produces the same error.

How to check

# Check if SELinux is enforcing
getenforce

# Check the SELinux context of the uploads directory
ls -laZ wp-content/uploads/

The directory should have an SELinux context that allows the web server to write to it. The specific context varies by distribution, but httpd_sys_rw_content_t is common for web-writable directories.

How to fix

# Set the correct SELinux context
chcon -R -t httpd_sys_rw_content_t wp-content/uploads/

# Make the change persistent across relabels
semanage fcontext -a -t httpd_sys_rw_content_t "/path/to/wp-content/uploads(/.*)?"
restorecon -Rv wp-content/uploads/

Debugging workflow

If you are not sure which cause applies, work through this diagnostic sequence:

  1. Check disk space and quota. Run df -h and check your hosting control panel for disk usage. If the disk is full or quota is exceeded, that is your answer.
  2. Check directory existence. Verify wp-content/uploads/ exists. Create it if missing.
  3. Check ownership. Run ls -la wp-content/ and verify the uploads directory is owned by the user PHP runs as. If you are not sure which user PHP runs as, check your PHP-FPM pool configuration or create a phpinfo file and look for the USER value.
  4. Check permissions. Directories should be 755 , files should be 644 . Fix with find / chmod if wrong.
  5. Check PHP limits. Create a phpinfo file and verify upload_max_filesize , post_max_size , upload_tmp_dir , and open_basedir are set correctly.
  6. Check error logs. The PHP error log and Nginx error log will usually contain the specific system error (permission denied, disk quota exceeded, no such file or directory) that WordPress does not surface in its user-facing error message.

How managed hosting prevents this

On managed hosting platforms, the infrastructure is configured to prevent these issues from occurring in the first place.

On Hostney, each WordPress site runs in a container where PHP-FPM executes as the account owner. The uploads directory, the home directory, and all subdirectories are owned by the same user that PHP runs as, so there are no ownership mismatches. Directory permissions are set to 755 and file permissions to 644 during deployment, and a background process monitors for permission drift and corrects it automatically.

Disk quotas are enforced at the kernel level through the hosting plan, and the control panel shows current usage. PHP upload limits ( upload_max_filesize , post_max_size ) and Nginx’s client_max_body_size are synchronized so that all three layers accept the same maximum file size – eliminating the class of errors where one limit is lower than the others.

The PHP temporary directory ( /tmp ) is always writable and included in the open_basedir path list. SELinux contexts are set correctly by the container orchestration system. These are configuration details that a site owner on managed hosting should never need to think about, but they are exactly the details that cause upload failures on misconfigured servers.

Summary

“The uploaded file could not be moved to wp-content/uploads” means the file reached your server but could not be saved to its final location. The most common causes are incorrect directory ownership or permissions (PHP cannot write to the uploads directory), disk quota exceeded (no space left for new files), the uploads directory missing entirely, or PHP configuration issues with the temporary directory or open_basedir restrictions.

Diagnose by checking disk space first (the quickest), then directory ownership and permissions, then PHP configuration. The server error logs contain the specific system-level error that WordPress does not display, so check those if the cause is not obvious from the filesystem checks.