Safeguard
Security Guides•

How do you check a PHP file upload safely?

Check the upload error code, size and real MIME type with finfo, then move_uploaded_file to a generated name outside the web root. Steps from the PHP docs.

Safeguard Security Team
4 min read

Check the upload error code, enforce a size limit, detect the real MIME type with finfo instead of trusting the browser, then save with move_uploaded_file under a name you generate, in a directory the web server will not execute. Each step closes a different gap.

What the PHP manual advises

The PHP manual page on handling file uploads includes an example that begins with a warning to check the $_FILES structure and values thoroughly. It guards against an undefined or array valued error field, which it labels a corruption attack. It then checks the error code, checks the size against a limit, and says not to trust the MIME value in $_FILES, because that comes from the client. It checks the type itself with finfo.

The manual for move_uploaded_file adds that the function only moves files that were uploaded through PHP, which protects against being tricked into moving other files. It also warns that if the destination exists, it will be overwritten.

A safe sequence

if (!isset($_FILES['upfile']['error']) || is_array($_FILES['upfile']['error'])) {
    throw new RuntimeException('Invalid parameters.');
}
if ($_FILES['upfile']['error'] !== UPLOAD_ERR_OK) {
    throw new RuntimeException('Upload failed.');
}
if ($_FILES['upfile']['size'] > 1000000) {
    throw new RuntimeException('File too large.');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$type = $finfo->file($_FILES['upfile']['tmp_name']);
$allowed = ['jpg' => 'image/jpeg', 'png' => 'image/png'];
$ext = array_search($type, $allowed, true);
if ($ext === false) {
    throw new RuntimeException('Invalid file format.');
}
$name = bin2hex(random_bytes(16)) . '.' . $ext;
move_uploaded_file($_FILES['upfile']['tmp_name'], '/srv/uploads/' . $name);

The extension comes from the detected type, never from the submitted file name.

What each step prevents

StepGap it closes
Check the structureMalformed or array valued fields
Check the error codePartial or failed uploads treated as success
Check the sizeDisk and memory abuse
Detect type with finfoClient supplied type spoofing
Generate the namePath tricks, overwrites, odd characters
Store outside the web rootUploaded script being run as code

Where to store files

Put uploads outside the document root, or in a directory where the web server will not run scripts. If files must be reachable, serve them through a script that sets the content type and disposition yourself. An uploaded PHP file in a web served folder is a classic route to a web shell, which is why the name and location matter more than the check.

Steps summary

  1. Validate the shape of $_FILES and the error code.
  2. Enforce size in code and in server configuration.
  3. Detect the type with finfo, and map it to an extension from an allow list.
  4. Create the file name yourself.
  5. Save outside the web root.
  6. Serve through a controlled path, with a download disposition for non images.
  7. Keep image libraries updated if you resize uploads.

Limits of content checks

A file can have a valid image header and still carry other data. Re-encoding images through a library can strip extra content, but then the library itself is part of your attack surface. Patch it.

Server limits come first

PHP and the web server both have their own upload size limits, and a request over the limit may be rejected before your code runs. Set them to match your application limit, so users get a clear error and your code is not asked to handle huge bodies. Also make sure the temporary upload directory is not shared with other applications.

Quick check

Can I trust $_FILES type?

No. The PHP manual example says not to trust it, since the client sets it.

Why not keep the original file name?

It can contain path characters or an executable extension, and it can collide with an existing file.

Safeguard's SBOM and dependency scanning can list the libraries your upload code depends on. A free scan is at gold.safeguard.sh/scan.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.