Skip to content

PHP File Upload Not Working? A Checklist

Uploads fail quietly. There is often no exception, no log line, and an empty array where your file should be. Work down this list in order — it is arranged so the cheapest checks eliminate the most common causes first.

1. Turn the errors on

Debugging blind wastes more time than any other mistake here. On a development machine:

<?php
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);

var_dump($_FILES);
var_dump($_POST);
var_dump($_SERVER['CONTENT_LENGTH'] ?? null);

Those three dumps identify the failure category immediately:

Never leave display_errors on in production. Log instead — error_log() writes to the server log without showing paths and stack traces to visitors.

2. Check the form

Three attributes have to be right, and each failure looks different:

<form action="upload.php" method="post" enctype="multipart/form-data">
  <input type="file" name="document">
  <button type="submit">Upload</button>
</form>

3. Read the error code

If $_FILES has an entry, error tells you the cause precisely:

echo $_FILES['document']['error'];   // 0 = OK

1 and 2 are size limits, 3 is an interrupted transfer, 4 means nothing was selected, 6 and 7 are server-side temp directory faults, 8 is an extension blocking it. Each one, with causes and fixes, is in the error code reference. Note there is no code 5.

4. Check the size limits

If the file is bigger than post_max_size, PHP throws the entire body away before your script starts — hence two empty superglobals and no error entry.

<?php
printf("upload_max_filesize: %s\npost_max_size: %s\nmax_file_uploads: %s\nini file: %s\n",
    ini_get('upload_max_filesize'), ini_get('post_max_size'),
    ini_get('max_file_uploads'), php_ini_loaded_file());

Two traps here. post_max_size must exceed upload_max_filesize, since the file travels inside the POST body. And php_ini_loaded_file() tells you which file PHP actually read — editing the wrong php.ini is the single most common reason a change appears to do nothing. Under PHP-FPM, reload the service afterwards; the pool caches its configuration.

Full detail in PHP upload size limits explained.

5. Check whether uploads are enabled at all

var_dump(ini_get('file_uploads'));   // must not be "0" or ""

Rare, but some hardened and shared-hosting configurations ship with file_uploads = Off. Every upload then fails with nothing in $_FILES, and no amount of form debugging will help.

6. Check the temporary directory

PHP writes the incoming file to a temp directory before your code sees it. If that path is missing or unwritable you get error 6 or 7:

$dir = ini_get('upload_tmp_dir') ?: sys_get_temp_dir();
var_dump($dir, is_dir($dir), is_writable($dir), disk_free_space($dir));

A full disk is the most-missed cause on a long-running server — and it produces no other symptom until something else breaks. On Windows, check the path exists for the account the web server runs as, not for your login. Under open_basedir restrictions, the temp directory must be inside the allowed paths.

7. Check the destination

$target = __DIR__ . '/../uploads';
var_dump(is_dir($target), is_writable($target));

If move_uploaded_file() returns false while the error code is 0, the arrival was fine and the destination is the problem: the directory does not exist, the web-server user cannot write to it, the path is relative to an unexpected working directory, or SELinux/AppArmor is blocking the write. Use absolute paths built from __DIR__ rather than relative ones — the current directory is not always what you assume.

8. Check the web server, not PHP

If the request never reaches PHP, nothing in your script can report it. Look for the response code:

The browser network tab shows the status and which server produced it. If the response body is Nginx's or IIS's own error page rather than yours, the request never got to PHP.

9. Test without the browser

Taking the front-end out of the picture separates "my form is wrong" from "my server is wrong" in one command:

curl -v -F "document=@/path/to/test.jpg" https://example.com/upload.php

If curl succeeds and the browser does not, the problem is in your markup or JavaScript. If curl fails the same way, it is server-side and you can iterate quickly without clicking through a form.

10. If JavaScript is doing the upload

Two specific mistakes account for most AJAX upload failures:

Both, with working code, are in the AJAX upload article.

Quick reference

SymptomMost likely cause
$_FILES empty, file name in $_POSTMissing enctype
$_FILES and $_POST both empty, large CONTENT_LENGTHpost_max_size
error is 1upload_max_filesize
error is 3Connection dropped mid-transfer
error is 6 or 7Temp directory missing, unwritable or full
move_uploaded_file() returns false, error 0Destination permissions or path
413 or 404.13 responseWeb-server body limit
Files past the 20th missingmax_file_uploads
Works locally, fails on a slow linkmax_input_time or proxy timeout

Removing whole categories of failure

Several rows in that table exist only because the whole file rides in one POST that the user cannot see. PHP File Uploader validates in the browser before transferring, streams the data in parts, and reports a specific message per file — so a too-large file is rejected instantly instead of after a long wait and a blank page. Try the progress demo, or read the installation guide for the temp-directory setup.