Handling file uploads in PHP: increasing size and validating uploads

Handling file uploads is a common requirement in web applications, but it presents its own challenges. This guide explores how to handle file uploads in PHP 8.3, including checking and increasing the maximum upload size, modifying PHP configurations, restricting file types, and validating uploads to enhance the security and efficiency of your applications.
PHP version and configuration defaults
This guide has been tested with PHP 8.3—the latest stable version as of 2024. In PHP 8.3, the default file upload settings typically include:
upload_max_filesize
set to 2M.post_max_size
usually around 8M (the value may vary based on your configuration).max_file_uploads
set to 20.
Review your current settings as needed before making changes.
How to check max file upload size in PHP
In PHP 8.3, the default upload_max_filesize is 2MB and max_file_uploads is 20. Before making any
changes, it's essential to know your current upload limits. PHP controls file upload sizes with two
configuration directives: upload_max_filesize
and post_max_size
. You can check these values by
creating a simple PHP script:
<?php
try {
echo 'upload_max_filesize: ' . ini_get('upload_max_filesize') . '<br>';
echo 'post_max_size: ' . ini_get('post_max_size') . '<br>';
echo 'max_file_uploads: ' . ini_get('max_file_uploads') . '<br>';
echo 'memory_limit: ' . ini_get('memory_limit') . '<br>';
} catch (Exception $e) {
error_log('Error checking PHP configuration: ' . $e->getMessage());
echo 'Error checking configuration settings.';
}
?>
Save this script as check_upload_size.php
and run it on your server. It will display the current
maximum file upload size and the maximum post size allowed.
Alternatively, you can use the phpinfo()
function to get a full overview of your PHP
configuration:
<?php
phpinfo();
?>
Accessing this script in your browser will provide detailed information about your PHP configuration.
How to increase file upload size in PHP
If you need to allow larger file uploads, you will have to adjust the upload_max_filesize
and
post_max_size
directives in your PHP configuration. Here is how you can do it:
Using .htaccess
(for apache 2.4+)
Add the following lines to your .htaccess
file:
<IfModule mod_php.c>
php_value upload_max_filesize 20M
php_value post_max_size 25M
php_value memory_limit 256M
php_value max_execution_time 300
</IfModule>
This sets the maximum upload file size to 20 megabytes and the maximum post size to 25 megabytes. Adjust the values according to your needs.
Using php-fpm configuration
If you're using PHP-FPM, add these settings to your pool configuration file (usually in
/etc/php/8.3/fpm/pool.d/www.conf
):
php_admin_value[upload_max_filesize] = 20M
php_admin_value[post_max_size] = 25M
php_admin_value[memory_limit] = 256M
How to change max file upload size in php.ini
on Ubuntu
If you have access to your server's php.ini
file, you can make the changes there. Here is how to
do it on an Ubuntu system:
-
Locate your
php.ini
file. For PHP 8.3, it's usually at/etc/php/8.3/apache2/php.ini
or/etc/php/8.3/fpm/php.ini
. -
Open the file in a text editor with root privileges:
sudo nano /etc/php/8.3/apache2/php.ini
-
Find and modify these directives:
upload_max_filesize = 20M post_max_size = 25M memory_limit = 256M max_execution_time = 300 max_file_uploads = 50
-
Save the file and exit the editor.
-
Restart your web server and PHP-FPM (if applicable):
sudo systemctl restart apache2 sudo systemctl restart php8.3-fpm
Implementing file upload functionality in PHP
With the configuration set, you can now implement file upload functionality in your application.
HTML form
First, create an HTML form that allows users to upload files:
<form action="upload.php" method="post" enctype="multipart/form-data">
<label for="file">Choose file to upload:</label>
<input type="file" name="file" id="file" required />
<input type="submit" value="Upload" />
</form>
Make sure to set enctype="multipart/form-data"
in the form tag to handle file uploads.
PHP upload script
Here's a modern, secure implementation of upload.php
using PHP 8.3 features:
<?php
declare(strict_types=1);
class FileUploadHandler {
private string $uploadDirectory;
private array $allowedTypes;
private array $allowedExtensions;
private int $maxFileSize;
private array $uploadedFiles = [];
public function __construct(
string $uploadDirectory = 'uploads/',
array $allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'],
array $allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf'],
int $maxFileSize = 5242880 // 5MB
) {
$this->uploadDirectory = $uploadDirectory;
$this->allowedTypes = $allowedTypes;
$this->allowedExtensions = $allowedExtensions;
$this->maxFileSize = $maxFileSize;
$this->initializeUploadDirectory();
}
private function initializeUploadDirectory(): void {
if (!is_dir($this->uploadDirectory)) {
if (!mkdir($this->uploadDirectory, 0755, true)) {
throw new RuntimeException('Failed to create upload directory');
}
}
$htaccess = $this->uploadDirectory . '.htaccess';
if (!file_exists($htaccess)) {
file_put_contents($htaccess, "php_flag engine off\nOptions -Indexes\n");
}
}
public function handleUpload(): array {
try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
throw new RuntimeException('Invalid request method');
}
if (!isset($_FILES['file'])) {
throw new RuntimeException('No file uploaded');
}
$file = $_FILES['file'];
$this->validateUpload($file);
$fileInfo = $this->processUpload($file);
$this->uploadedFiles[] = $fileInfo;
return [
'success' => true,
'message' => 'File uploaded successfully',
'file' => $fileInfo
];
} catch (Exception $e) {
error_log('Upload error: ' . $e->getMessage());
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
private function validateUpload(array $file): void {
if ($file['error'] !== UPLOAD_ERR_OK) {
throw new RuntimeException($this->getUploadErrorMessage($file['error']));
}
if ($file['size'] > $this->maxFileSize) {
throw new RuntimeException('File is too large');
}
$fileType = mime_content_type($file['tmp_name']);
if (!in_array($fileType, $this->allowedTypes, true)) {
throw new RuntimeException('Invalid file type');
}
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $this->allowedExtensions, true)) {
throw new RuntimeException('Invalid file extension');
}
if (!is_uploaded_file($file['tmp_name'])) {
throw new RuntimeException('Invalid upload attempt');
}
}
private function processUpload(array $file): array {
$hash = hash_file('sha256', $file['tmp_name']);
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$newFilename = sprintf('%s_%s.%s', uniqid('', true), $hash, $extension);
$destination = $this->uploadDirectory . $newFilename;
if (!move_uploaded_file($file['tmp_name'], $destination)) {
throw new RuntimeException('Failed to move uploaded file');
}
chmod($destination, 0644);
return [
'name' => $file['name'],
'type' => mime_content_type($destination),
'size' => filesize($destination),
'hash' => $hash,
'path' => $destination
];
}
private function getUploadErrorMessage(int $error): string {
return match($error) {
UPLOAD_ERR_INI_SIZE => 'File exceeds upload_max_filesize',
UPLOAD_ERR_FORM_SIZE => 'File exceeds MAX_FILE_SIZE',
UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
UPLOAD_ERR_NO_FILE => 'No file was uploaded',
UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary folder',
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
UPLOAD_ERR_EXTENSION => 'File upload stopped by extension',
default => 'Unknown upload error'
};
}
}
// Usage
header('Content-Type: application/json');
$handler = new FileUploadHandler();
$result = $handler->handleUpload();
echo json_encode($result, JSON_THROW_ON_ERROR);
This implementation includes:
- Strict type checking
- Comprehensive error handling with try-catch
- File hash verification for integrity checks
- Secure file type validation using the Fileinfo extension
- Proper directory permissions and .htaccess protection
- Protection against common upload vulnerabilities
Best practices for secure file uploads
Directory security
-
Set proper permissions:
chmod 755 uploads/ chmod 644 uploads/*
-
Use open_basedir restriction in php.ini:
open_basedir = /var/www/uploads/:/tmp/
-
Organize uploads into randomized subdirectories to isolate files and reduce exposure to directory traversal attacks.
-
Implement rate limiting:
session_start(); if (!isset($_SESSION['upload_count'])) { $_SESSION['upload_count'] = 0; $_SESSION['upload_time'] = time(); } if (time() - $_SESSION['upload_time'] > 3600) { $_SESSION['upload_count'] = 0; $_SESSION['upload_time'] = time(); } if ($_SESSION['upload_count'] >= 10) { throw new RuntimeException('Upload limit exceeded'); } $_SESSION['upload_count']++;
Virus scanning
Implement ClamAV scanning for uploaded files:
<?php
function scanFile(string $filepath): bool {
$socket = socket_create(AF_UNIX, SOCK_STREAM, 0);
socket_connect($socket, '/var/run/clamav/clamd.ctl');
socket_send($socket, "SCAN $filepath\n", strlen("SCAN $filepath\n"), 0);
$result = socket_read($socket, 4096);
socket_close($socket);
return strpos($result, 'OK') !== false;
}
?>
Modern framework integration
For more robust handling, consider using modern PHP frameworks. For example, a Laravel implementation might look like this:
// Laravel example
use Illuminate\Http\Request;
public function upload(Request $request)
{
$request->validate([
'file' => 'required|file|mimes:pdf,jpg,png|max:5120'
]);
$path = $request->file('file')->store('uploads');
return response()->json(['success' => true, 'path' => $path]);
}
Advanced upload techniques
Beyond the basics, you can further improve the upload experience and handle more demanding scenarios:
-
Handling AJAX Uploads: Use XMLHttpRequest or the Fetch API to upload files asynchronously. This approach allows you to track upload progress and provide real-time feedback to users. For example:
<script> const fileInput = document.getElementById('file') fileInput.addEventListener('change', function () { const file = fileInput.files[0] const xhr = new XMLHttpRequest() xhr.upload.addEventListener('progress', function (e) { if (e.lengthComputable) { const percent = (e.loaded / e.total) * 100 console.log('Upload progress: ' + percent.toFixed(2) + '%') } }) xhr.open('POST', 'upload.php') const formData = new FormData() formData.append('file', file) xhr.send(formData) }) </script>
-
Chunked Uploads for Large Files: For very large files, consider breaking the file into smaller chunks and uploading them sequentially. This method can improve reliability and support resumable uploads.
-
Supporting Additional File Formats: Extend your validation logic to support other formats such as WebP images or ZIP archives. Always perform strict validation to ensure the file's content matches its extension.
Conclusion
By following these techniques, you can securely and efficiently handle file uploads in PHP. From increasing upload size limits and robust validation to implementing advanced methods like AJAX uploads and chunked transfers, these practices help improve both security and user experience. For an integrated solution using modern file uploading tools, consider exploring Transloadit's handling uploads service.