Tutorials Logic, IN info@tutorialslogic.com

Secure PHP File Uploads: Validation, MIME Detection, Limits, and Storage

Upload Boundary

An uploaded file is untrusted binary input plus client-supplied metadata. A safe handler checks the transport result, enforces its own size limit, detects content on the server, generates the stored name, and keeps the file away from executable public directories.

After this lesson, you can build the required multipart form, distinguish upload transport failures from validation failures, and move an accepted temporary file into controlled storage.

Multipart Form

The browser sends file bytes only when the form uses POST and multipart/form-data. The input name becomes the key in $_FILES.

Upload One Document

Upload One Document
<form method="post" enctype="multipart/form-data">
  <label for="document">PDF document</label>
  <input id="document" name="document" type="file" accept="application/pdf" required>
  <button type="submit">Upload</button>
</form>

The accept attribute helps the chooser but is not a security check; the server must verify the received file.

Transport Validation

Check that the expected entry exists, that error is a scalar integer, and that it equals UPLOAD_ERR_OK before reading tmp_name. Oversized requests can be rejected before normal form fields reach PHP.

Reject Upload Errors

Reject Upload Errors
<?php
$upload = $_FILES['document'] ?? null;

if (!is_array($upload) || !isset($upload['error']) || is_array($upload['error'])) {
    throw new RuntimeException('Invalid upload structure.');
}

if ($upload['error'] !== UPLOAD_ERR_OK) {
    throw new RuntimeException('Upload transport failed: ' . $upload['error']);
}

The numeric upload error describes transport handling; field rules such as permitted MIME type come afterward.

Content and Size

Do not trust $_FILES type or the original extension. Enforce an application size limit and use Fileinfo to detect the temporary file content. Keep a small allowlist that maps accepted MIME types to server-chosen extensions.

Accept a Bounded PDF

Accept a Bounded PDF
<?php
if (($upload['size'] ?? 0) > 5 * 1024 * 1024) {
    throw new RuntimeException('File exceeds 5 MiB.');
}

$mime = (new finfo(FILEINFO_MIME_TYPE))->file($upload['tmp_name']);
if ($mime !== 'application/pdf') {
    throw new RuntimeException('Only PDF files are accepted.');
}

Application validation remains necessary even when php.ini also defines upload_max_filesize and post_max_size.

Controlled Storage

Generate a random filename, store outside the public document root when possible, and serve downloads through an authorization check. Never reuse the client filename as a path.

Move with a Generated Name

Move with a Generated Name
<?php
$name = bin2hex(random_bytes(16)) . '.pdf';
$destination = __DIR__ . '/../storage/uploads/' . $name;

if (!move_uploaded_file($upload['tmp_name'], $destination)) {
    throw new RuntimeException('Could not store the uploaded file.');
}

move_uploaded_file() verifies that the source came through PHP HTTP upload handling.

Upload Limits

  • Review file_uploads, upload_max_filesize, post_max_size, upload_tmp_dir, and max_file_uploads.
  • Keep web-server request limits consistent with PHP and application limits.
  • Log a generated upload identifier and failure category, not the file contents or private client path.

Review the Upload Gate

0 of 2 checked

Q1. Can $_FILES["document"]["type"] prove file content?

Q2. Why generate the stored filename?

Try this next

Harden an Upload

0 of 2 completed

  1. Define separate user-safe outcomes for missing input, transport error, oversized file, wrong MIME type, and storage failure.
  2. Store a generated identifier in the database and require ownership before streaming the file.
Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.