MEDIA GUIDES / Video

PHP Video Upload: A Secure, Scalable How-To

Allowing users to upload video files is a core requirement for many web applications, especially those used for content sharing or video editing. PHP simplifies this process by providing a set of functions and variables for handling file uploads. At a high-level, uploading files in PHP involves creating a client-side HTML form for selecting the file from the client machine, and a server-side PHP script to handle processing and storing this file.

In this guide, we’ll walk through the steps for creating a simple video upload application using PHP. We’ll set the required PHP configuration to allow file uploads, create the client-side HTML, and the server-side PHP script.

In this article:

Requirements and php.ini Upload Settings

To write PHP code that accepts uploading a video file, we first need to adjust some configuration in the php.ini file. This configuration will enable file uploads and set the required parameters for accepting the file.

  • file_uploads has to be set to on to allow HTTP file uploads.
  • upload_max_filesize needs to be set for the maximum allowed size for the uploaded file. If you plan to limit the video file size to 100Mb, set it here.
  • post_max_size sets the maximum size of data sent in a POST request. This has to be set higher than the upload_max_filesize.
  • memory_limit specifies the maximum amount of memory a script may consume. This needs to be set higher than the post_max_size to allow for any in-memory file processing needed.
  • max_execution_time sets the maximum time that a script can take to execute. Setting this option to a higher value prevents timeouts during long video uploads.
  • max_input_time specifies the maximum time a script can take to parse request data. It’s also needed to increase this value for video uploads to prevent parsing timeouts.
  • upload_tmp_dir is a directory path that will be used for temporarily storing the file during upload.

After setting this configuration, we’re ready to start creating our client-side and server-side script.

How to Build an HTML5 Video Upload Form

The simplest way to implement the client-side video upload is through an HTML form. Inside your index.html add the following code:

<!DOCTYPE html>
<html>
<body>

<form action="upload.php" method="post" enctype="multipart/form-data">
    <label for="videoFile">Select video to upload:</label>
    <input type="file" name="videoFile" id="videoFile" accept="video/*" required>
    <input type="submit" value="Upload Video" name="submit">
</form>

</body>
</html>

This is a simple HTML form element, there are 3 main parts here that we need to note:

  • The action attribute specifies the server-side script needed to process the data in the request. In our case, this is the PHP script that should handle the video file upload.
  • The post method specifies the type of the HTTP request that will be sent to the server.
  • The enctype attribute specifies how the form data should be encoded when it’s sent to the server. The multipart/form-data encoding is used when the form input type is file.

When you try to access the server now you should get a file upload form that looks like this:

If you click on the Choose File button, it will open a pop-up to choose a file from your machine for upload. You’ll notice that it defaults to video file types, this is because we set the accept attribute in the input element to video/*

After you choose the file, when you click on the Upload Video button, the form will send the file in a POST request to the server-side script specified in the action attribute. So, our next step is to create this PHP file for handling the file upload.

Writing the PHP Upload Handler

Create a file with name upload.php to match the file name in the action attribute, and add the following code inside it:

<?php
$filename = $_FILES['videoFile']['name'];
$location = "upload/".$filename;
if ( move_uploaded_file($_FILES['videoFile']['tmp_name'], $location) ) { 
  echo 'File uploaded successfully';
} else { 
  echo 'Error uploading the file'; 
}
?>

In the above code, we:

  • Use the $_FILES super global variable to get the original file name of the uploaded file.
  • Then, we use the move_uploaded_file function in PHP to move the file from its temporary directory location to a permanent location.
  • We defined the permanent location in the $location variable as "upload/", which means we need to create a directory with this name in the same location of the upload.php script:

You also need to ensure that the temporary directory and the permanent directory have the correct write permissions for the user which the PHP script runs as.

Now we’re ready to start testing our video file upload. First we’ll access our HTML upload form and choose the file to upload:

Next click on the Upload Video button, you should get one of the success or error messages specified in the PHP script:

We got the success message, which means that the file now should be stored under the upload/ directory:

Validating File Size, MIME Types, and Upload Errors

Now we will improve our PHP script further to do some additional validations. We need to check the file size, MIME type, and any upload errors that happened.

First let’s modify our code to validate the uploaded file size:

<?php
$filename = $_FILES['videoFile']['name'];
$location = "upload/".$filename;
if ( move_uploaded_file($_FILES['videoFile']['tmp_name'], $location) ) { 
  echo 'File uploaded successfully <br><br>'; 
} else { 
  echo 'Error uploading the file'; 
}
$file_size = ($_FILES['videoFile']['size']) / 1048576;
echo "The file size is: $file_size MB";
?>

Again, we used the $_FILES super global variable to get the size attribute of the videoFile uploaded from the HTML form. If we upload our file now we should get an output like this:

Next we need to check for upload errors. To do this, we’ll use the error attribute of the $_FILES. This attribute can have an integer value that represents one of the predefined error codes. In our example, we’ll provide a larger file size than the configuration specified in the php.ini file, which should produce an error code of 1. We’ll validate this by the following code:

<?php
$filename = $_FILES['videoFile']['name'];
$location = "upload/".$filename;
if ( move_uploaded_file($_FILES['videoFile']['tmp_name'], $location) ) { 
  echo 'File uploaded successfully <br><br>';
  $file_size = ($_FILES['videoFile']['size']) / 1048576;
  echo "The file size is: $file_size MB <br><br>";
} else { 
  echo 'Error uploading the file <br><br>'; 
  $uploadError = $_FILES['videoFile']['error'];
  switch ($uploadError) {
    case 1:
    echo "File size too large, please check the php.ini configuration";
    break;
    default:
    echo "unknown error occurred: $uploadError";
  }
}
?>

Now let’s try to upload a large video file:

When we upload the video, we should get the error message we specified in the code:

Of course, in a typical scenario you should check for all the possible error codes. In this example we only checked for the exceeded maximum file size just for illustration, because we intentionally used a larger file to verify the error validation behavior.

Now let’s apply our final validation to check the MIME type of the uploaded file. The MIME type validation also has an attribute in the $_FILES super global variable, however, it’s generally not recommended to validate the content type using this attribute because this attribute is actually sent from the HTML form and it can be spoofed by an attacker who intercepts the traffic.

A more secure alternative is to validate the MIME type from the file content itself. To do this, we’ll use the PHP Fileinfo extension.

We’ll need to enable the extension first from the php.ini file:

Then we’ll modify our code to check if the uploaded file MIME type is video:

<?php
$filename = $_FILES['videoFile']['name'];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime_type = $finfo->file($_FILES['videoFile']['tmp_name']);
if (strpos($mime_type, 'video/') === 0) {
    echo "The file type is $mime_type <br><br>";
    $location = "upload/".$filename;
    if ( move_uploaded_file($_FILES['videoFile']['tmp_name'], $location) ) { 
      echo 'File uploaded successfully <br><br>';
      $file_size = ($_FILES['videoFile']['size']) / 1048576;
      echo "The file size is: $file_size MB <br><br>";
    } else { 
      echo 'Error uploading the file <br><br>'; 
      $uploadError = $_FILES['videoFile']['error'];
      switch ($uploadError) {
        case 1:
        echo "File size too large, please check the php.ini configuration";
        break;
        default:
        echo "unknown error occurred: $uploadError";
      }
  }} else {
        echo "Uploaded file is not a valid video file";
  }
?>

Here we use the finfo class (part of the Fileinfo extension) to check the MIME type of the file. Video files MIME type starts with video/ and then the file format, so, from the temporary file upload location we first check if the MIME type of the uploaded file has this structure before accepting and moving it to its permanent location. We also print the MIME type as an informational message.

First let’s try to upload a valid video file:

We should get a successful upload message:

Now if we try to upload a different file type:

The upload should fail because of the incorrect file MIME type:

Saving Files Securely with Unique Filenames

While validating file size, MIME type, and upload errors adds some security to our application, we need to enhance this further with additional security checks. Two of the most common techniques for achieving this is to provide a unique, unguessable name for the uploaded file, and move the file to a location outside the document root of the web server.

Currently, our code is saving the uploaded video file to the upload/ directory which is in the same location as the PHP script under the web server document root. Which means that anyone can access the video file directly from the browser by providing the file path and name:

To prevent this from happening, let’s change the location where we save our uploaded file simply by changing the value of our $location variable in the code to "/projects/videos/".$filename;

Now, the file should be uploaded under the specified directory outside the document root.

The next step is to provide a unique name for the uploaded file. Currently we’re using the name attribute of the $_FILES variable, which provides the same name as the file name on the client device.

We can use the sha1_file PHP function to generate a hash value based on the file contents:

$filename = sha1_file($_FILES['videoFile']['tmp_name']).".mp4";

Now if we try to upload the file again, it should be stored with this hash result as the name:

Storing Video Metadata in MySQL

Let’s add more functionality to our application by persisting the uploaded video metadata in a database. We’ll use MySQL for our scenario but the same concepts should apply for other SQL databases supported by PHP.

Assuming you already have MySQL installed and a database ready inside it, create a table for storing the video metadata with the following SQL query:

CREATE TABLE video_metadata (
    id INT AUTO_INCREMENT PRIMARY KEY,
    original_name VARCHAR(255) NOT NULL,
    stored_name VARCHAR(255) NOT NULL,
    mime_type VARCHAR(100) NOT NULL,
    size INT UNSIGNED NOT NULL,
    file_path VARCHAR(255) NOT NULL
);

Each of the above columns in the created table represents one of the video file metadata. We’ll save the original file name, stored file name, MIME type, file size, and file location. Depending on your needs, you can store additional metadata also as long as you’re able to extract it in the PHP script.

We’ll use the MySQLi driver to connect to the database from PHP. So first we need to enable the MySQLi extension from the php.ini configuration:

Next let’s modify our PHP code to connect to the database and store the video metadata:

<?php
$filename = sha1_file($_FILES['videoFile']['tmp_name']).".mp4";
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime_type = $finfo->file($_FILES['videoFile']['tmp_name']);
$original_name = $_FILES['videoFile']['name'];
$final_path = "/projects/videos";
if (strpos($mime_type, 'video/') === 0) {
    echo "The file type is $mime_type <br><br>";
    $location = "/projects/videos/".$filename;
    if ( move_uploaded_file($_FILES['videoFile']['tmp_name'], $location) ) { 
      echo 'File uploaded successfully <br><br>';
      $file_size = ($_FILES['videoFile']['size']) / 1048576;
      echo "The file size is: $file_size MB <br><br>";
      $conn = new mysqli("localhost", "myapp", "mypassword", "uploaded_files");
      if ($conn->connect_error) {
          die("Connection failed: " . $conn->connect_error);
      }
      else {
        echo "successfully connected to the database <br><br>";
        $sql = "INSERT INTO video_metadata (original_name, stored_name, mime_type, size, file_path) VALUES (?, ?, ?, ?, ?)";
        $stmt = $conn->prepare($sql);
        $stmt->bind_param("sssis", $original_name, $filename, $mime_type, $file_size, $final_path);
        if ($stmt->execute()) {
          echo "New record created successfully <br><br>";
        } else {
          echo "Error: " . $stmt->error;
        }
      }
    } else { 
      echo 'Error uploading the file <br><br>'; 
      $uploadError = $_FILES['videoFile']['error'];
      switch ($uploadError) {
        case 1:
        echo "File size too large, please check the php.ini configuration";
        break;
        default:
        echo "unknown error occurred: $uploadError";
      }
  }} else {
        echo "Uploaded file is not a valid video file";
  }
?>

Replace the database connection details like myapp, mypassword, and uploaded_files with your user, password, and database name respectively. If we uploaded a video file now, we should see its record created in the database with the metadata information:

Adding Upload Progress Bar

To make our application more interactive and user friendly, we can add a progress bar that tells the user the percentage of the video file uploaded and remaining. We’ll need to modify our frontend code by adding JavaScript and AJAX to track the upload status and update the progress bar view.

First, we’ll modify our HTML code to include the AJAX library, our JavaScript code file (app.js), and add a progress bar with the <progress> tag:

<!DOCTYPE html>
<html>
<head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <script src="app.js"></script>
</head>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data">
    <label for="videoFile">Select video to upload:</label>
    <input type="file" name="videoFile" id="videoFile" accept="video/*" required>
    <input type="submit" value="Upload Video" name="submit" onclick="uploadFile()">
    <progress id="progressBar" value="0" max="100" style="width:300px;"></progress>
    <h3 id="status"></h3>
    <p id="loaded_n_total"></p>
</form>
</body>
</html>

Next, create the app.js file and add the following code inside it:

function _(el) {
  return document.getElementById(el);
}
function uploadFile() {
  var file = _("videoFile").files[0];
  var formdata = new FormData();
  formdata.append("videoFile", file);
  var ajax = new XMLHttpRequest();


  ajax.upload.addEventListener("progress", progressHandler, false);
  ajax.addEventListener("load", completeHandler, false);
  ajax.addEventListener("error", errorHandler, false);
  ajax.addEventListener("abort", abortHandler, false);


  ajax.open("POST", "upload.php");
  ajax.send(formdata);
}
function progressHandler(event) {
  _("loaded_n_total").innerHTML = "Uploaded " + event.loaded + " bytes of " + event.total;
  var percent = (event.loaded / event.total) * 100;
  _("progressBar").value = Math.round(percent);
  _("status").innerHTML = Math.round(percent) + "% uploaded... please wait";
}
function completeHandler(event) {
  _("status").innerHTML = event.target.responseText;
  _("progressBar").value = 0;
}
function errorHandler(event) {
  _("status").innerHTML = "Upload Failed";
}
function abortHandler(event) {
  _("status").innerHTML = "Upload Aborted";
}

Here we create a FormData and add our uploaded video file to it, then use AJAX to send the file to our upload.php script using a POST request.

Now if we upload a file, we should see the progress bar appears:

Security Best Practices and Common Upload Risks

Now that we covered how to implement video upload functionality in an application, it’s important to highlight the security risks associated with file uploads and the best practices to overcome these risks. Some of the common security risks include:

  • Remote Code Execution: An uploaded file that contains malicious script which can be executed to provide the attacker access to the server. This can happen if the uploaded file content type was not checked.
  • Denial of Service: Excessive file uploads or large file sizes that can consume the server resources impacting server performance and causing application failures.
  • Path traversal: The uploaded file name can be set to include a specific path like /dir1/dir2/file which tricks the server to save the file in this location, maybe causing an overwrite to other critical system files.
  • Cross-Site Scripting: A malicious script can be embedded in the uploaded file, like in an image metadata, when a user tries to view this file the script gets executed in the user’s browser.

And the best practices to prevent against these attacks include:

  • Validating file MIME type: Check the uploaded file MIME type from the file content itself (as we did with the finfo PHP extension) and don’t rely on the provided content-type header from the client.
  • Limit uploaded file sizes: Configure strict limits for the maximum size of the uploaded files to prevent overwhelming the server and exhausting its resources.
  • Provide unique file names: Rename the uploaded files with unique names to prevent path traversal attacks.
  • Save files to a secure directory: Avoid storing the uploaded file in the document root to prevent unwanted access to the file.
  • Authentication: Only allow authenticated users to upload files.

Wrapping Up

Video file uploads is a core feature for many web applications that allow content sharing and editing. It typically requires handling in different application components including frontend, backend, and database. Implementing such feature needs close attention to performance and security as it includes many challenges in these areas.

Frequently Asked Questions

What is PHP video upload and how does it work?

PHP video upload refers to the process of allowing users to submit video files through a web form and having PHP handle the file on the server. PHP checks the file type and size, moves the uploaded video to a server directory, and can store metadata in a database for later use.

How do you implement video upload in PHP?

To implement video upload, create an HTML form with enctype="multipart/form-data", then use PHP’s $_FILES array to validate and save the uploaded file. You should check file size, type (e.g., MP4, AVI), and handle errors before moving the file with move_uploaded_file().

What are best practices for secure PHP video uploads?

Ensure you validate file extensions and MIME types, set appropriate file size limits, and store uploads outside the web root if possible. Renaming files to unique names and implementing user authentication also helps prevent unauthorized access and conflicts.

QUICK TIPS
Tali Rosman
Cloudinary Logo Tali Rosman

In my experience, here are tips that can help you better implement PHP video upload with validation, size limits, secure storage, progress tracking, and scalable delivery:

  1. Treat the “upload” as two steps: ingest, then publish
    First accept into a quarantine bucket (not web-accessible), scan/probe it, then “promote” to durable storage/CDN only after it passes checks. This prevents half-uploaded or malicious files from ever being reachable.
  2. Don’t trust “video/*” or even MIME alone—probe the container
    finfo is better than headers, but it still isn’t enough. Run a lightweight media probe (container/streams/duration) server-side and reject files with weird stream maps (e.g., no video stream, absurd dimensions, hundreds of streams).
  3. Cap by “decoded cost,” not just megabytes
    A small file can expand to enormous decode cost (4K/8K, extreme frame rates, huge duration). Enforce limits on duration, frame size, pixel rate (width×height×fps), and audio sample rate.
  4. Use resumable/chunked uploads for anything serious
    Plain multipart uploads fall apart on mobile networks. Use a chunk protocol (Tus, S3 multipart, Cloud storage resumables) and have PHP orchestrate/authorize it rather than proxying the whole file through PHP.
  5. Make filenames boring and metadata explicit
    Never use user filenames in paths. Store: original name (for UI), internal ID (for storage), and a separate “display title.” Normalize Unicode and strip control characters to avoid log/UI injection and filesystem edge cases.
  6. Implement per-user and per-IP rate limiting with storage quotas
    Size limits help, but attackers can DoS you with many “allowed-size” uploads. Put quotas (daily bytes, concurrent uploads) and backpressure (429s) in front of the upload endpoint.
  7. Avoid writing uploads to slow shared disks
    PHP’s temp dir on networked storage can bottleneck badly. Place upload_tmp_dir on fast local SSD, then asynchronously move to object storage. Your throughput and tail latency will improve immediately.
  8. Verify integrity end-to-end with checksums
    Compute a SHA-256 (or use one supplied by the client for chunked uploads) and store it with the record. This lets you dedupe identical uploads, detect corruption, and safely retry transfers between tiers.
  9. Progress bars lie unless you design for them
    Browser upload progress only covers client→server. Users care about “when can I watch it,” which includes server-side move, probe, transcode, and CDN propagation. Show a second phase (“processing…”) backed by job status.
  10. Serve with signed URLs and tokenized playback, not direct paths
    Even if files are outside web root, teams often re-expose them later. Use signed, time-limited URLs (or signed cookies) and a separate “playback authorization” step so access control stays correct when you add CDN later.
Last updated: Feb 8, 2026