How to Use PHP Headers to Force File Download Safely

In this tutorial we are going to learn about PHP Force File Download. it protects file for directly accessing file path. when user click on file link like whether it PDF, images or ZIP – PHP HEADERS helps force downloading file without opening links. This will helps for securing file like invoices or other sensitive information. We will explain in details about PHP download script and secure file download.

We can check if file exists on server using file_exists and use header(), readfile() function to easily achieve our download file goal.

PHP Force File Download Script

Below php download script tells browser to download file without preview it.

<?php
$file = 'styled.pdf'; // File in the current directory

if (file_exists($file)) {
    // Force file download using PHP headers
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . basename($file) . '"');
    header('Expires: 0');
    header('Cache-Control: must-revalidate');
    header('Pragma: public');
    header('Content-Length: ' . filesize($file));

    // Flush and read the file
    flush();
    readfile($file);
    exit;
	
} else {
    echo "File not found.";
}
Making PHP Download script secure.

basename functions helps for removing slashes and dots. if you where passing query string on browser it prevents directory traversal attacks.

$file=$_GET['file'];
header('Content-Disposition: attachment; filename="' . basename($file) . '"');
Restrict file downloading.

allow only specific extension file. this will helps for give restriction file types download.

$allowed = ['pdf', 'zip', 'jpg'];
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));

if (!in_array($ext, $allowed)) {
    die("Invalid file type.");
}
Large file uploading using chunk reading

Below example code chunked reading 1MB file to prevent memory overload and works for slow connections.

<?php
$file = 'styled.pdf'; // File in the current directory

if (file_exists($file)) {
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . basename($file) . '"');
    header('Expires: 0');
    header('Cache-Control: must-revalidate');
    header('Pragma: public');
    header('Content-Length: ' . filesize($file));

    // Clean output buffer before reading file
    ob_clean();
    flush();

    // Read the file in chunks (1MB at a time)
    $chunkSize = 1024 * 1024; // 1MB
    $handle = fopen($file, 'rb');

    while (!feof($handle)) {
        echo fread($handle, $chunkSize);
        flush(); // Push to browser
    }

    fclose($handle);
    exit;
} else {
    http_response_code(404);
    echo "File not found.";
}