File Uploads in PHP: Complete Guide with Security Best Practices
File upload functionality is essential for many web applications, but it also introduces security risks if not implemented properly. This guide covers secure file uploads in PHP with practical examples.
1. Basic File Upload Form
HTML Form Setup
<form action="upload.php" method="post" enctype="multipart/form-data">
<label>Select file to upload:</label>
<input type="file" name="fileToUpload" id="fileToUpload">
<input type="submit" value="Upload File" name="submit">
</form>
-
enctype="multipart/form-data"
is required for file uploads method="post"
must be used-
name="fileToUpload"
will be used in PHP to access the file
2. Handling the Upload in PHP
Basic Upload Script (upload.php)
<?php
$targetDir = "uploads/";
$targetFile = $targetDir . basename($_FILES["fileToUpload"]["name"]);
$uploadOk = 1;
// Check if file already exists
if (file_exists($targetFile)) {
echo "Sorry, file already exists.";
$uploadOk = 0;
}
// Check file size (5MB limit)
if ($_FILES["fileToUpload"]["size"] > 5000000) {
echo "Sorry, your file is too large.";
$uploadOk = 0;
}
// Allow certain file formats
$allowedTypes = ["jpg", "png", "jpeg", "gif", "pdf"];
$fileType = strtolower(pathinfo($targetFile, PATHINFO_EXTENSION));
if (!in_array($fileType, $allowedTypes)) {
echo "Sorry, only JPG, JPEG, PNG, GIF & PDF files are allowed.";
$uploadOk = 0;
}
// Check if $uploadOk is set to 0 by an error
if ($uploadOk == 0) {
echo "Sorry, your file was not uploaded.";
} else {
if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $targetFile)) {
echo "The file ". htmlspecialchars(basename($_FILES["fileToUpload"]["name"])). " has been uploaded.";
} else {
echo "Sorry, there was an error uploading your file.";
}
}
?>
3. Security Best Practices for File Uploads
1. Validate File Type Properly
Never trust the file extension! Check the actual MIME type:
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES["fileToUpload"]["tmp_name"]);
$allowedMimes = ["image/jpeg", "image/png", "application/pdf"];
if (!in_array($mime, $allowedMimes)) {
die("Invalid file type");
}
2. Rename Uploaded Files
$newFileName = uniqid() . '.' . $fileType;
$targetFile = $targetDir . $newFileName;
- Prevents overwriting existing files
- Avoids directory traversal attacks
- Makes it harder to guess file locations
3. Set Proper Permissions
chmod($targetFile, 0644); // Readable by all, writable by owner
4. Store Files Outside Web Root
$targetDir = "/var/www/private_uploads/";
- Prevents direct URL access
- Use PHP to serve files when needed
5. Limit File Size
// In php.ini
upload_max_filesize = 5M
post_max_size = 6M
// In your script
if ($_FILES["fileToUpload"]["size"] > 5000000) {
die("File too large");
}
6. Prevent Image-Based Attacks
For image uploads, reprocess them:
if (getimagesize($_FILES["fileToUpload"]["tmp_name"]) === false) {
die("File is not an image");
}
4. Complete Secure File Upload Class
class FileUploader {
private $targetDir;
private $allowedTypes;
private $maxSize;
public function __construct($dir = "uploads/", $types = ["jpg", "png"], $size = 5000000) {
$this->targetDir = $dir;
$this->allowedTypes = $types;
$this->maxSize = $size;
if (!file_exists($this->targetDir)) {
mkdir($this->targetDir, 0755, true);
}
}
public function upload($fileInputName) {
$targetFile = $this->targetDir . uniqid() . '.' .
strtolower(pathinfo($_FILES[$fileInputName]["name"], PATHINFO_EXTENSION));
// Check file size
if ($_FILES[$fileInputName]["size"] > $this->maxSize) {
return ["error" => "File too large"];
}
// Check file type
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES[$fileInputName]["tmp_name"]);
if (!in_array($mime, $this->getMimeTypes())) {
return ["error" => "Invalid file type"];
}
// Move the file
if (move_uploaded_file($_FILES[$fileInputName]["tmp_name"], $targetFile)) {
chmod($targetFile, 0644);
return ["success" => true, "path" => $targetFile];
} else {
return ["error" => "Upload failed"];
}
}
private function getMimeTypes() {
$mimes = [];
foreach ($this->allowedTypes as $type) {
switch ($type) {
case 'jpg':
case 'jpeg':
$mimes[] = 'image/jpeg';
break;
case 'png':
$mimes[] = 'image/png';
break;
case 'pdf':
$mimes[] = 'application/pdf';
break;
}
}
return $mimes;
}
}
// Usage:
$uploader = new FileUploader("uploads/", ["jpg", "png"]);
$result = $uploader->upload("fileToUpload");
if (isset($result["error"])) {
echo "Error: " . $result["error"];
} else {
echo "File uploaded to: " . $result["path"];
}
5. Common Errors & Solutions
- "File exceeds upload_max_filesize" - Increase upload_max_filesize and post_max_size in php.ini
- "Temporary folder not found" - Check upload_tmp_dir in php.ini exists and is writable
-
"Permission denied" errors - Run:
chown -R www-data:www-data /var/www/uploads chmod -R 755 /var/www/uploads
- "File not uploaded" - Verify enctype="multipart/form-data" in form and check PHP's file_uploads = On in php.ini
6. Advanced Techniques
Multiple File Uploads
<input type="file" name="filesToUpload[]" multiple>
foreach ($_FILES["filesToUpload"]["tmp_name"] as $key => $tmpName) {
// Process each file
}
File Upload Progress
// Enable in php.ini
session.upload_progress.enabled = On
// Check progress via AJAX
$_SESSION["upload_progress_" . basename($_POST["name"])]
Cloud Storage Integration
// Example with AWS S3
$s3 = new Aws\S3\S3Client([...]);
$result = $s3->putObject([
'Bucket' => 'my-bucket',
'Key' => 'uploads/' . $fileName,
'Body' => fopen($_FILES["fileToUpload"]["tmp_name"], 'rb')
]);