PHP cookbook · Intermediate
Handle file uploads securely in PHP
Accept user image uploads safely: validate type and size, avoid executable files, and store them outside the web root.
Approach
Never trust the client's filename or MIME type. Check the upload error code and size, detect the real type with finfo, map allowed types to extensions, generate a random filename, and move the file outside the public directory (or to object storage). Serve files through a controller or signed URLs with Content-Disposition and X-Content-Type-Options: nosniff.
Solution
<?php
declare(strict_types=1);
function storeAvatar(array $file, string $storageDir): string
{
if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
throw new RuntimeException('Upload failed');
}
if ($file['size'] > 2 * 1024 * 1024) {
throw new RuntimeException('File too large (max 2 MB)');
}
$mime = (new finfo(FILEINFO_MIME_TYPE))->file($file['tmp_name']);
$ext = match ($mime) {
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/webp' => 'webp',
default => throw new RuntimeException('Only JPEG, PNG or WebP images are allowed'),
};
// Re-encode to strip metadata and any embedded payloads
$image = imagecreatefromstring(file_get_contents($file['tmp_name'])) ?: throw new RuntimeException('Invalid image');
$name = bin2hex(random_bytes(16)) . '.' . $ext;
$path = rtrim($storageDir, '/') . '/' . $name;
match ($ext) {
'jpg' => imagejpeg($image, $path, 85),
'png' => imagepng($image, $path),
'webp' => imagewebp($image, $path, 85),
};
return $name;
}
$name = storeAvatar($_FILES['avatar'] ?? [], '/var/app/storage/avatars');
Pitfalls
- Storing uploads under
public/where a.phpfile could be executed. - Trusting
$_FILES['x']['type'](client-controlled). - Forgetting server limits:
upload_max_filesize,post_max_sizeand Nginxclient_max_body_size.
Variations
- Upload directly to S3 with pre-signed POST URLs and validate after upload with a worker.
- Scan non-image uploads with ClamAV before making them available.
Further reading: OWASP File Upload Cheat Sheet
Reference: https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html
More PHP recipes
- Add CSRF protection to PHP forms
- Add health check endpoints to a PHP app
- Add structured logging with request IDs in PHP
- Build a secure password reset flow in PHP
- Cache expensive queries with Redis in PHP
- Call external APIs with timeouts and retries in PHP
- Implement rate limiting in a PHP API
- Make a PHP payment endpoint idempotent