Skip to main content

Secure File Uploads in PHP: Type Checks and Defense

PHP · 29.09.2026

Why Checking the File Extension Does Not Stop Someone Uploading Code

A check like if (strpos($_FILES['file']['name'], '.php') !== false) solves nothing: an attacker renames shell.php to shell.php.jpg, and on some Apache configurations with mod_mime the server still executes it as PHP because of the compound extension. The file extension is metadata fully controlled by whoever submits the form, not a sign of the actual content.

The same applies to the Content-Type header from $_FILES['file']['type']: the browser sets it based on the extension on the client side, and curl or Postman let you set any value manually. This field cannot be trusted in security checks at all.

Checking the Real File Type with finfo

The only reliable way is to read the file's signature and determine the MIME type from its content, not from the name or the request header.

$finfo = new finfo(FILEINFO_MIME_TYPE);
$realType = $finfo->file($_FILES['avatar']['tmp_name']);

$allowed = ['image/jpeg', 'image/png', 'image/webp'];
if (!in_array($realType, $allowed, true)) {
    throw new RuntimeException('Invalid file type: ' . $realType);
}

The check runs against the temporary file $_FILES['avatar']['tmp_name'], which PHP has already saved to disk after the upload — it is exactly this content that needs analyzing, not the original file name. The RuntimeException is worth logging instead of showing straight to the user — the approach is covered in the article on PHP logs and diagnosing fatal errors.

Limiting Size: php.ini and Code

The size of an uploaded file is limited at two levels. First, php.ini, before the script even gets control.

upload_max_filesize = 10M
post_max_size = 12M

The post_max_size value must be larger than upload_max_filesize, otherwise PHP truncates the whole request before it parses the files, and $_FILES ends up empty with no explicit error. A detailed breakdown of these directives and common mistakes is in the article on memory_limit and upload_max_filesize. The second level is checking $_FILES['avatar']['size'] in code before calling move_uploaded_file, if the business logic needs a limit smaller than the one in php.ini.

Where to Store Uploaded Files

RuleBadGood
Locationinside the web root /publicoutside the web root, served by a script
File namethe original name from the usera random name via random_bytes
Permissions0777 on the upload directory0755 on the directory, 0644 on files
ExecutionPHP is allowed in the upload directoryphp_admin_flag engine off in the config

If the upload directory physically sits inside the web root, add php_admin_flag engine off for that exact path in the configuration — then even an uploaded file with a .php extension gets served as plain text instead of running as code. General principles for hardening php.ini are collected in the article on PHP security through configuration.

Common Mistakes When Accepting File Uploads

  • Trusting the extension or the Content-Type from the request instead of checking the content with finfo.
  • Saving the file under its original name — a path like ../../config.php in the file name allows writing outside the upload directory.
  • No limit on the number of files per request — this opens the door to DoS through thousands of tiny uploads.
  • move_uploaded_file is called without checking is_uploaded_file first, which lets an attacker fake the source path.
  • Uploaded images are not re-encoded through GD or Imagick — malicious code can hide inside a JPEG that looks valid.

Summary: Checklist for Secure File Uploads

  • The real file type is checked with finfo based on content, not on the extension or Content-Type.
  • upload_max_filesize and post_max_size are set in php.ini, and any extra size limit is checked in code.
  • Files are stored under a random name outside the web root, or with PHP execution disabled.
  • The upload directory has 0755/0644 permissions, not 0777.
  • Images go through re-encoding with GD or Imagick before being saved.
← Back to Knowledge Base Ask Support