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:

  1. 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.

  2. Open the file in a text editor with root privileges:

    sudo nano /etc/php/8.3/apache2/php.ini
    
  3. Find and modify these directives:

    upload_max_filesize = 20M
    post_max_size = 25M
    memory_limit = 256M
    max_execution_time = 300
    max_file_uploads = 50
    
  4. Save the file and exit the editor.

  5. 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

  1. Set proper permissions:

    chmod 755 uploads/
    chmod 644 uploads/*
    
  2. Use open_basedir restriction in php.ini:

    open_basedir = /var/www/uploads/:/tmp/
    
  3. Organize uploads into randomized subdirectories to isolate files and reduce exposure to directory traversal attacks.

  4. 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.