"Specified File Failed Upload Test.": WordPress Was Handed a Temp File PHP Never Received
The temporary file WordPress was handed is not one PHP received in this request or, for downloaded files, cannot be read. Custom code using the wrong upload function, a plugin that swaps the temp file, or an upload that never reached $_FILES are the usual causes.
"Specified file failed upload test." means the temporary file WordPress was given is not a file PHP received in this request or, for files WordPress downloaded itself, is not readable. The size, type and folder checks have not run yet. The path was replaced, built by code, or missing before WordPress's own checks began.
Where the check sits
Every upload in WordPress goes through _wp_handle_upload() in wp-admin/includes/file.php. It runs its tests in a fixed order, and the position of this one narrows the cause:
| Order | Test | Message on failure |
|---|---|---|
| 1 | wp_handle_upload_prefilter returned an error |
Whatever the filter set |
| 2 | Form action matches (when test_form is on) |
Invalid form submission. |
| 3 | PHP's error code is 0 | PHP's own messages, such as Missing a temporary folder or Failed to write file to disk |
| 4 | The temporary file is a real upload | Specified file failed upload test. |
| 5 | Non-empty, allowed type, writable uploads folder | File is empty…, not allowed file type, Unable to create directory… |
| 6 | Moving the file into uploads |
The uploaded file could not be moved |
Test 4 depends on how the file arrived:
wp_handle_upload(), used by the media uploader, the plugin and theme ZIP uploader, the importer and multipart REST uploads, calls PHP'sis_uploaded_file()on the temporary file. That returns true only for a path PHP itself recorded as an upload in the current POST request. It checks PHP's list, not the disk: in a test, it still returned true after the temporary file had been deleted.wp_handle_sideload(), used for files WordPress downloads or writes itself (media from a URL, REST uploads sent as a raw request body), only checksis_readable().
In the media library the message appears as "photo.jpg" has failed to upload. followed by this sentence.
The usual causes
1. A downloaded file disappeared before the sideload. For media from a URL and similar downloads, the test is is_readable(), so anything that deletes or locks the file in the temporary folder before WordPress gets to it trips it. A host scanner watching that folder is one suspect; the host's security log will show it. For ordinary form uploads, a deleted temporary file fails later instead, at the type check or the move.
2. A plugin replaced the temporary file. Image optimisation, resizing and renaming plugins sometimes hook wp_handle_upload_prefilter, process the image, and point tmp_name at the processed copy. The prefilter runs before test 4, so the new path is not a file PHP received and is_uploaded_file() rejects it. The sign is that every upload fails, or every image, right after installing or updating such a plugin. Deactivate plugins one by one, starting with anything that touches images.
3. Custom code uses the wrong function. This is the developer case, and it is covered in the next section.
4. The file never reached $_FILES. A request larger than post_max_size arrives with no file data at all. Code that passes the missing entry on to wp_handle_upload() without checking reaches this test with nothing PHP received. Check the field name and the request size in custom forms.
The trap: wp_handle_upload() only trusts real POST uploads
A common pattern in custom code and small plugins: download an image with download_url(), or decode a base64 string to a temp file, then build a $_FILES-style array and pass it to media_handle_upload() or wp_handle_upload(). It looks correct, and it fails every time with this message, because PHP's is_uploaded_file() deliberately refuses any file that did not arrive in the current request's POST body.
Use the sideload functions for anything that did not come from a form:
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/image.php';
$tmp = download_url( $url );
if ( is_wp_error( $tmp ) ) {
return $tmp;
}
$file = array(
'name' => wp_basename( parse_url( $url, PHP_URL_PATH ) ),
'tmp_name' => $tmp,
);
$id = media_handle_sideload( $file, $post_id );
if ( is_wp_error( $id ) ) {
wp_delete_file( $tmp ); // The temp file stays behind on failure.
}
media_handle_sideload() turns off the form test and runs the remaining checks on the file, including the file type check, so it is not a way around WordPress's security rules.
Narrow it down
- Upload a small JPEG in Media › Add New. If it works, the problem is file-specific, so look at plugins that process that file type first.
- If everything fails, switch to a default theme and deactivate plugins, then upload again.
- Check the server's PHP error log and the debug log around the upload time for
open_basedirwarnings or scanner entries. - If only one feature fails, such as importing images from URLs or a form plugin's file field, it is that code's upload path.
Common mistakes
- Raising
upload_max_filesize. Size problems produce different messages, earlier in the chain. - Setting the uploads folder to 777. The folder is checked at step 5; this failure happens before WordPress looks at it.
- Blaming the host's malware scanner for a form upload. Deleting PHP's temporary file does not trip this test; look at plugins and custom code first.
- Fixing the custom code with a
wp_handle_upload_overridesfilter. Theis_uploaded_file()test cannot be switched off forwp_handle_upload(); use the sideload functions.
Verify
- The file that failed uploads in Media › Add New and appears in the media library.
- Custom import code returns an attachment ID instead of a
WP_Error, and the temporary directory does not fill with leftover files. - With the suspect plugin reactivated, a test upload still works, or you have found the plugin responsible.
Frequently asked
- No. PHP's size limit is reported before this test with its own message, "The uploaded file exceeds the upload_max_filesize directive in php.ini." A zero-byte file is caught by the next test, with a "File is empty" message whose text points at post_max_size. This test only checks where the temporary file came from.
- Look for code that treats some files differently. A plugin that processes only images, for example, and points tmp_name at its own copy fails exactly those uploads. A host scanner that deletes a suspicious temporary file during a form upload does not trigger this message: is_uploaded_file() checks PHP's list of received uploads, not the disk, so that upload fails later, at the file type check or the move into uploads.
- media_handle_upload() goes through wp_handle_upload(), which accepts only files PHP received in the current POST request. A downloaded or generated file must go through media_handle_sideload() or wp_handle_sideload(), which check that the temporary file is readable instead.