PHP download file script
This is my favorite PHP download script. Before I’ve used a different more simple method, until a client wanted to be able to allow their site visitors to download a large file from a password protected directory. The PHP script works on Apache web servers for all kind of files. I have used this script for file downloads even bigger than 500MB. The cache control header is used to force a download for text files or other files, even if they are opened by default inside your web browser.
How to use the PHP download file script?
Create a PHP script, name it “download.php” and copy/paste the following code:
Create on your web page links for all the file which resists in a password protected directory or in a directory above the website root directory. Use for our example the following download URL:
PHP download file features:
- The file name, which is passed via the query string, is sanitized by using the PHP function preg_replace() and filter_var()
- To make the script safer, I use the PHP function pathinfo() to parse the file path, if this happens successfully, the script will continue the further file handling process.
- The file download script is also useful for bigger files (using this script I’ve downloaded files bigger than 500MB!).
Script Demo
The demo page demonstrates the PHP code examples for file upload and download and PHP directory functions to show the files in a SELECT menu.
PHP download files from a MySQL database
The PHP download code doesn’t hide the file name and in some situations it might be better to use a unique string or ID as a key for the file download. With the following example, I will use a string to receive the name of a file which is stored inside a secure MySQL database. Let say, we have a simple database table with only two columns for the ID and the filename. The code for the file download.php is almost the same and only the first part is different:
How to use that PHP/MySQL download code?
In the first example I used the file name inside the download URL. Because I’m casting the md5() encryption with $secret as the salt, I need to built my file download URL differently:
Disclaimer
I published the PHP download example code to explain how a write a PHP download file script . If you really need to protect your downloads, you need to deny the direct access of the file location using Apache directives or .htaccess rules. Use more secure slugs to receive a file name from your database. For example you can encrypt the database row key as well.
Published in: PHP Scripts
In the modern world, your server should support the “sendfile” method to offload the transfer of the file after authentication is done.
IIRC mod_sendfile in Apache, x-accel-redirect in nginx
Otherwise you’re just blocking that php engine until the client has closed the connection.
Hi Mike,
“Sendfile” looks great and I’m sure I will use this mod for the next heavy downloads server I use. The problem is that mod_xsendfile isn’t installed on most of the (shared) hosting accounts. I guess that’s one reason why there are so many people looking for a 100% PHP download solution. For Nginx (I will try that one for sure) is this article interesting:
http://kovyrin.net/2006/11/01/nginx-x-accel-redirect-php-rails/ (LOL this feature exists for more than 10 years)
Mike,
I see that mod_xsendfile is still in beta: https://tn123.org/mod_xsendfile/
I’m sure the mod is great way to process downloads, but I don’t think that a lot of server admins will install the mod on their production servers. This will make it a more developer solution, don’t you think?
I ditched Apache so many years ago… I thought it was practically shipped with nowadays. Wow. That’s sad.
Just another reason to switch to an nginx-based solution. I think lighty has supported it for a long time too.
Let’s keep us the discussion about LightHTTP, Apache and Nginx for another topic :)
Thanks for your insights!
Then they should move away from shared hosts. It’s in their interest to enable things like sendfile. They probably still run mod_php and have lame permissions crap too.
Sure they should move away, but many of them stick with shared hosting because of the higher costs for managed server hosting. This website is a VPS by DigitalOcean and I’m using ServerPilot as a kind of control panel. I can’t use mod_sendfile because it’s not enabled, but I will check the Nginx variant.
Hi, i used your code to create a download.php file, in order to protect my files from !$_SESSION users. I would like to protect also my folder from url access, if i use 700 as chmod value, the page will not return the download file, but it will stay blank (the fopen fail).. Which value should i use for access files from the code?
Hi Marcello,
Using a file permissions or CHMOD is the wrong way in some situations. It’s better to move your downloads to a level “above” the public_html directory. Or protect your directory via .htaccess and .htpasswd.
Does the download work with regular permissions?
i’ve just solved 5 mins ago via .htaccess. I deny the access to the folder.
My file’s paths are stored in a database table, so i pass only the ID via GET method. The “noob” user will never see the clear URL of the file.
The download works with regular permission. Thanks!
Hi,
Great that it works this way :) Check this article for your DB record IDs:
https://www.web-development-blog.com/creating-secure-php-websites/
(most of all the comments)
Hi Olaf,
I notice that i have some problems on file download via Internet Explorer and Safari. The script gave me an empty file. Do you know what i’m talking about?
Thanks in advance
Hi,
sounds like a problem with headers, does the same script works for you in browsers like Chrome and Firefox?
Do you use the windows versions?
Warning: Cannot modify header information – headers already sent by (output started at C:wampwwwBackupSec_Academictest.php:9) in C:wampwwwBackupSec_Academictest.php on line 374
Line 374: Is the one that has the header function ie header(“Content-type: application/octet-stream”);
This warning is displayed for all header function used after the default key word in the Switch function
How can i fix this?
Hi Bobic,
like the warning said your script started the output at row 9. Is there an “echo” or some empty row/whitespace?
Hi Olaf,
I have some problem on download xls/xlsx file using this code. The file is opened in corrupted format.
Hi Deepak,
does this happen only to these two files types or to any file? Do you checked the file size for the downloaded file?
Tip: Open the file in your text editor, most of the time you can see an error message. Solve the the reported problem. Otherwise check also your PHP error log.
Thanks for the reply. Any xlsx/xls file, on download in face that issue. But PDF, image file, txt file will download correctly for same code. I create a seperate controller for only download action. On that time it works fine(both xlsx/xls file downloaded correctly). So i find that it occur because of some other code affecting file.
Hi,
great you solved it, I guess your previous solution has problems with one of the headers and/or “early” output.
The bad thing is that headers work different for different server configurations.
Hello,
I wanted to download a file and save it to disk. How do this?
Hi Serge,
you need to use the right headers. Check the example code for the PDF file. “Content-Disposition: attachment;” this header will force the download.
How to use this with .rar files?
I added: header(“Content-type: application/x-rar-compressed, application/octet-stream”);
Hi Fernandes,
your header is not valid, just use this one:
header("Content-type: application/x-rar-compressed");
Thanks for this. I think I found three errors in your scripts re. downloading from a database.
In download_mysql.php you concatenate $file_ID and $secret, but in your sample page code you do it the other way around, ie. $secret.$file_ID.
On line 11 you have a rogue trailing underscore after $result.
Also line 11, num_rows should be ‘num_rows’ to avoid an undefined constant warning.
At least it made me understand it to make it work!! :)
Hi Jez,
Thanks for your comment. You’re right there was a bug in my example. I fixed the code snippet.
$result->num_rows
shouldn’t give you an undefined constant warning. It’s part of the MySQLi object.