Protecting a download using a unique URL

Update 6/25/09: I’ve updated the script to include a number of suggestions made in the comments. The new script supports multiple files, up to 20 URLs can be created at a time, and a brief note can be attached to each key. If these features sound useful, please check out the new post at:

Protecting multiple downloads using unique URLs.

A client asked me to develop a simple method for protecting a download (or digital product) by generating a unique URL that can be distributed to authorized users via email. The URL would contain a key that would be valid for a certain amount of time and number of downloads. The key will become invalid once the first of those conditions is exceeded. The idea is that distributing the unique URL will limit unauthorized downloads resulting from the sharing of legitimate download links.

In addition, once the key has been validated, the download starts immediately, preventing the visitor from seeing the actual location of the download file. What’s more, the file name of the download in the “Save as” dialogue box isn’t necessarily the same as the file name of the file on the server, making the file itself pretty much undiscoverable.

How it works

There are five main components to this system:

  1. the MySQL database that holds each key, the key creation time, and the number of times the key has been used
  2. the downloadkey.php page that generates the unique keys and corresponding URLs
  3. the download.php page that accepts the key, verifies its validity, and either initiates the download or rejects the key as invalid
  4. a dbconnect.php file that contains the link to the database and which is included into both of the other PHP files
  5. the download .zip file that is to be protected

Place all three PHP scripts and the .zip file into the same directory on your server.

The MySQL database

Using whatever method you’re comfortable with, create a new MySQL database named “download” and add the following table:

CREATE TABLE `downloadkey` (
  `uniqueid` varchar(255) NOT NULL default '',
  `timestamp` varchar(255) NOT NULL default '',
  `downloads` varchar(255) NOT NULL default '0',
  PRIMARY KEY (uniqueid)

The downloadkey.php page

This page generates the key, creates a URL containing the key, and writes the key to the database. Never give out the location of this page – this is for only you to access.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
<html xmlns="" xml:lang="en" lang="en">
<title>Download Key Generator</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="author" content="//" />
<style type="text/css">
#wrapper {
	font: 15px Verdana, Arial, Helvetica, sans-serif;
	margin: 40px 100px 0 100px;
.box {
	border: 1px solid #e5e5e5;
	padding: 6px;
	background: #f5f5f5;

<div id="wrapper">

<h2>Download Key Generator</h2>

// A script to generate unique download keys for the purpose of protecting downloadable goods

require ('dbconnect.php');

	if(empty($_SERVER['REQUEST_URI'])) {

	// Strip off query string so dirname() doesn't get confused
	$url = preg_replace('/\?.*$/', '', $_SERVER['REQUEST_URI']);
	$folderpath = 'http://'.$_SERVER['HTTP_HOST'].'/'.ltrim(dirname($url), '/').'/';

// Generate the unique download key
	$key = uniqid(md5(rand()));
//	echo "key: " . $key . "<br />";
// Get the activation time
	$time = date('U');
//	echo "time: " . $time . "<br />";
// Generate the link
	echo "<p>Here's a new download link:</p>";
	echo "<p><span class=\"box\">" . $folderpath . "download.php?id=" . $key . "</span></p>";

// Write the key and activation time to the database as a new row
	$registerid = mysql_query("INSERT INTO downloadkey (uniqueid,timestamp) VALUES(\"$key\",\"$time\")") or die(mysql_error());

<p>Each time you refresh this page, a unique download key is generated and saved to a database.  Copy and paste the download link into an email to allow the recipient access to the download.</p>
<p>This key will be valid for a certain amount of time and number of downloads, which can be set in the download.php script.  The key will expire and no longer be usable when the first of these conditions is exceeded.</p>
<p>The download page has been written to force the browser to begin the download immediately.  This will  prevent the recipient of the email from discovering the location of the actual download file.</p>


The download.php page

The URL generated by downloadkey.php points to this page. It contains the key validation script and then forces the browser to begin the download if it finds the key is valid.

// Set the maximum number of downloads (actually, the number of page loads)
$maxdownloads = "2";
// Set the key's viable duration in seconds (86400 seconds = 24 hours)
$maxtime = "86400";

require ('dbconnect.php');

	if(get_magic_quotes_gpc()) {
        $id = stripslashes($_GET['id']);
		$id = $_GET['id'];

	// Get the key, timestamp, and number of downloads from the database
	$query = sprintf("SELECT * FROM downloadkey WHERE uniqueid= '%s'",
	mysql_real_escape_string($id, $link));
	$result = mysql_query($query) or die(mysql_error());
	$row = mysql_fetch_array($result);
	if (!$row) { 
		echo "The download key you are using is invalid.";
		$timecheck = date('U') - $row['timestamp'];
		if ($timecheck >= $maxtime) {
			echo "This key has expired (exceeded time allotted).<br />";
			$downloads = $row['downloads'];
			$downloads += 1;
			if ($downloads > $maxdownloads) {
				echo "This key has expired (exceeded allowed downloads).<br />";
				$sql = sprintf("UPDATE downloadkey SET downloads = '".$downloads."' WHERE uniqueid= '%s'",
	mysql_real_escape_string($id, $link));
				$incrementdownloads = mysql_query($sql) or die(mysql_error());
// Debug		echo "Key validated.";

// Force the browser to start the download automatically

		$file = real name of actual download file on the server
		$filename = new name of local download file - this is what the visitor's file will actually be called when he/she saves it

   $file = "";
   $filename = "";
   header("Cache-Control: public, must-revalidate");
   header("Pragma: no-cache");
   header("Content-Type: " . $mm_type);
   header("Content-Length: " .(string)(filesize($file)) );
   header('Content-Disposition: attachment; filename="'.$filename.'"');
   header("Content-Transfer-Encoding: binary\n");


The dbconnect.php script (database connection)

This is the PHP include referenced by both scripts that contains the database link.

// Connect to database "download" using: dbname , username , password 
    $link = mysql_connect('localhost', 'root', '') or die("Could not connect: " . mysql_error());
    mysql_select_db("download") or die(mysql_error());

This file will almost certainly require some editing. You will need to specify a host name for your MySQL server and a MySQL username and password in that file at mysql_connect('localhost', 'root', '') so that you can connect to the database you’ve set up. It’s extremely unlikely that your production MySQL database will be installed on localhost with a user “root” and no password.

That’s all there is to it. Whenever you want to give someone access to the download, visit the downloadkey.php page. It will generate a unique key code, save it to a database, and print out a URL that you can copy and paste into an email or whatever. The page at that URL checks to see if the key code is legit, then checks to see if the code is less than X hours old, then checks to see if it has been used less than X times. The visitor will get a descriptive message for the first unmet condition and the script will terminate. If all three conditions are met, the download starts automatically.

75 thoughts on “Protecting a download using a unique URL

  1. Mike

    How would I allow resume through a download manager? I use a similar system and allow my tickets to be used for up to a week but I cannot get it to resume.

  2. Steve

    This is really useful – was looking for something like that for ages. Thanks a lot for sharing! Much appreciated.

  3. Hunter

    You are THE MAN! This is the best script I have have ever found relating to digital content. If you combine this script with a solid chunk of .htaccess and .htpassword security you got yourself a top notch setup!

    Thank you so much!

  4. Joe Black


    I have tried the above code but I get the error message below:-
    Warning: mysql_connect() [function.mysql-connect]: Access denied for user ‘root’@’localhost’ (using password: NO) in /home/alanpotts93/public_html/hiddenfiles/dbconnect.php on line 3
    Could not connect: Access denied for user ‘root’@’localhost’ (using password: NO)
    I have already created a mysql table as was instructed with the default value for the third field being a zero.

    I am a novice at website building and only know html, but any help would be welcome.

    Thanks in advance

  5. Val

    Great tutorial. I have a question if you can help: I have an ebook I am selling through clickbank. How can I make it so that each time a visitor buys the ebook, when they are returned to the THANK YOU Page, the link for download is already there, unique, so they can only download the file after payment and not be able to pass it forward to others or guess the download link?

  6. ardamis Post author

    I wish I knew how to do that. I wanted to do something similar with Paypal, where a completed transaction would send the buyer to a download page, but I never figured it out.

    If you figure it out, I’d be interested in hearing about how you did it.

  7. John Polhemus

    Many thanks!

    I have just enough html knowledge to maintain my site and even less php.

    I was able to create the five main components and apply them to 3 zip files I need to protect. A little help with the database settings from the support folks at Host Rocket and I was up and running.

    Thanks again, JP

  8. Cliffordx

    To Val:

    You can integrate your thankyou.php page with the downloadkey.php so that each successful payment will redirected them to your thank you page where the unique id is already created.

    I tested this on my test server running lighttpd and it works perfectly. Now I will find ways how to make it an expiring URL with embedded details to be use on my email marketing campaigns…

  9. manu

    i am also facing the same problem as joe

    Warning: mysql_connect() [function.mysql-connect]: Access denied for user 'root'@'localhost' (using password: NO) in /www/ on line 3
    Could not connect: Access denied for user 'root'@'localhost' (using password: NO)

    please do help me
    please some reply

  10. ardamis Post author

    I suspect that you need to edit the “dbconnect.php” file so that it uses YOUR database information.

    You will need to specify a host name, a username and a password in that file so that you can connect to the database you’ve set up. It’s extremely unlikely that your production MySQL database will be installed on localhost with a user root and no password.

  11. manu


    i have already done this

    i have write my dbname,my login or pass in the position of * but nothing happen

    now what reply

  12. Phil

    Nice work mate works nicely… I would like to be able to work with multiple zip file with a unique URL. So I can have different directories of zip files then when the user click on the link it will point to a particular zip file… how can I associate a link to a particular zip file. Would this be easy to achieve…?

  13. RLBO

    This is a LIFESAVER. I have been trying to figure something like this out for months if not years now and you present it in a simple php script that I easily edited to suit my needs.


  14. Callum

    Just what i needed but i need the ability to have one code generator with the ability for multiple files. IE enter the URL to be scrambled and it creates one for that URL only. If you get my drift. How can i edit it to do that? Can someone help? email me at webdev (at) chemical-productions (dot) co (dot) uk if anyone knows how to do that.

  15. manbros

    I have a mobile download site where user can download without login or register but i wan to protect all downloadable files that only registered users can download. Anyone please help with thanks.

  16. Rick Billings

    This is a fantastic solution for unique links. I do have a question. I got this error
    Access denied for user ‘XXXXX’@’XXXXX’ to database ‘download’. I have the dbconnect.php file with “localhost” still. Should that be changed?

  17. Darren

    Are there issues with signed files? I’m trying this with a couple of downloads–one is a zip file and one is a Windows .MSI. In both cases, the resulting download file shows as corrupt. Not sure what is happening here.

  18. Jarad

    I had the same problem as Manu and Joe Black until I realized that I needed to change:
    the download part in the dbconnect. I changed it to the name of my database and it now works. Seems obvious in hindsight. Maybe others are making the same mistake I did. Great Script!

  19. Sean

    Very cool! One question – how do I protect the file directory where the download resides so that no one can access it unless they have the download code? Obviously legitimate downloads should be possible, but were someone to figure out the directory where the files live they could just get them directly from there, no?

  20. ardamis Post author

    I’m glad you like it.

    The script allows you to use anything as the file name on the server, and then show something different to the user in the download dialogue box. This prevents even legitimate users from knowing the location of the file on the server.

    For example, the file on the server may be named while the file name presented to the user may be

    If you use a suitably long string of random alpha-numeric characters (I use as the file name, the chances of someone successfully guessing it are astronomical.

    I would strongly recommend renaming downloadkey.php, though, as knowing the location of that page allows unlimited access to the file.

    If you want to prevent people from viewing the folder contents, this is most easily done by just dropping an index file into the folder. It’s possible to restrict the listing of folder contents via a server setting, too, and this is done by default by many commercial hosting companies.

    If you’re on Apache, you could also use htpassword to limit access to a folder, but this level of security probably isn’t necessary.

    An example folder structure that should work for most people:


  21. RLBO

    Hey, I absolutely love your code. I have a question though, and I have a feeling other people might be able to use this.

    What I need is a way to generate a long list of codes, that way I can enter them all into an excel file and hand them out individually. So, without reloading the page every time I need a new code, I wonder if you could help me modify the download key generator so that it repeats its process X number of times.

    That way, I could get a big block of download keys to work with.

  22. scraps232

    I have this script functioning perfectly, it is very easy to install and use. Thank you!

    I would love for their to be a way to mark each key created with a simple text note, so when I create a key for a special download and email it to ten different people, I can mark the key with the person’s name or email address for tracking it easily.

    I am using this script to keep track of a new album release for a band, and we are emailing to specific industry contacts. We need to track who has downloaded it and how many times. Currently, the database does show how many times it has been downloaded.

    However, in order to keep track of whose link is whose, I am maintaining a google spreadsheet where I have three columns: unique id, person’s name, email address. I am updating this google doc manually but I’m sure there’s a more elegant way to do this with the database, but I am fairly new to SQL commands, and still messing around on how to write the SQL code to do this with your script.

    I was thinking about adding another column to the downloadkey database called ‘note’ and manually insert the information from my phpAdmin page provided by my hosting provider. Then I could export the table to PDF and see my notes next to each key.

    Is there an easier way to do this or am I pretty close?

    Thanks again for anyone’s feedback, I think that others may find this answer useful as well as the only thing this snappy scipt seems to be missing is any sort of tracking/reporting.

  23. Vinyl Candy

    I am getting a FATAL ERROR only at larger sized downloads.
    If I replace the file with a small zip with the same name, than it works perfect.

    Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 41400320 bytes) in /hermes/web08/b2683/pow.leberj/htdocs/store/Land/Land_p1/download.php on line 60

    What am I doing wrong?
    Can I edit the code to reflect a larger download?
    Best Regards,

  24. Pingback: Protecting multiple downloads using unique URLs | Ardamis

  25. Nate

    I found the following code to be able to limit the download speed:

    How can I incorporate this code into download.php? I’m not very PHP savvy but I’ve tried several ways to include it. Two ways I did it didn’t cause any script errors but when when trying to access the random link, it just tries and tries. Browser says “waiting” on the bottom left corner. Eventually, it gives a download prompt but it just downloads a blank file. Any ideas?

    Thanks in advance.

  26. Nate

    Actually, it eventually does download the file, not a blank file. It takes probably 2 minutes though after pressing “Go” for the download link. Also, please ignore the download rate comment in the code above; I meant to change the 8.5kb/s.

  27. Gerard

    Thank you very much for your code.

    However, I have the following problem: When I put the URL in the address bar to get the file, it is open as text instead of appearing the menu to save it. I’m using Firefox on Debian Lenny and I have verified that the file is a standard zip file.

    I would appreciate any suggestion.

  28. Ann

    Great script! Thank you!

    I have an issue with the download file. Everything works perfectly until I open up the downloaded file. The zip file attached is empty.

    Does it support other file formats (e.g. PDFs)?

    Thanks for your help.

  29. Michael Stollaire

    This is an incredible piece of code, and thanks so much, as a client was looking for just this very thing.

    Once the download key is generated, instead of posting this, can we redirect the output from a form to an email, so the user gets an email with the unique download link?


    ~ Michael

  30. Muthuraman

    hello ardamis

    its really Good script for beginner

    $url = preg_replace(‘/?.*$/’, ”, $_SERVER[‘REQUEST_URI’]);
    $folderpath = ‘http://’.$_SERVER[‘HTTP_HOST’].’/’.ltrim(dirname($url), ‘/’).’/’;

    can you tell me how to give the path url to file

  31. Tim Roijers

    Cool script, but when i open the link de browser opens the zipfile in de browser instead of presenting a save as window. Any idea how to fix this?

  32. Vincent

    RLBO, i found this to work for your purpose. i am using it to create download cards for my music and other stuff.

    it can generate a code with however many you need

    // A script to generate unique download keys for the purpose of protecting downloadable goods

    require ('dbconnect.php');

    if(empty($_SERVER['REQUEST_URI'])) {

    // Strip off query string so dirname() doesn't get confused
    $url = preg_replace('/?.*$/', '', $_SERVER['REQUEST_URI']);
    $folderpath = 'http://&#039;.$_SERVER['HTTP_HOST'].'/'.ltrim(dirname($url), '/').'/';

    // Function to generate a 10 digit alpha-numeric code

    function randomPrefix($length)
    $random= "";


    $data = "AbcDE123IJKLMN67QRSTUVWXYZ";
    $data .= "aBCdefghijklmn123opq45rs67tuv89wxyz";
    $data .= "0FGH45OP89";

    for($i = 0; $i < $length; $i++)
    $random .= substr($data, (rand()%(strlen($data))), 1);

    return $random;

    // Generate the link
    echo "Here are the download keys”;

    $i = 1;
    while ($i <= 10) {

    // $random = randomPrefix(MUMBER OF CHARACTERS DESIRED);
    $random = randomPrefix(10);
    $time = date('U');

    //print the codes to page
    echo "”. $random. “”;

    //sends codes to Database
    $registerid = mysql_query(“INSERT INTO downloadkey (uniqueid,timestamp) VALUES(“$random”,”$time”)”) or die(mysql_error());



  33. Vincent

    i also forgot.. after you run that gen.php file

    you can go to your database manager and export it for excel. then use that for mail merge in Microsoft office publisher on say a business card. 🙂

  34. DTS

    im having trouble making the downloadkey table and having it work right, it says

    Unknown column ‘uniqueid’ in ‘field list’

  35. mpeter

    I’m getting the same error as DTS.
    I have followed your instructions and everything worked except for the message:

    Unknown column ‘uniqueid’ in ‘field list’

    when trying to generate a downloadkey.

    The download key is generated, but when I test the link I get the same error and nothing downloads.
    It must be something with the mysql database.
    Where have I gone wrong?

  36. Vincent

    If it says ” Unknown column ‘uniqueid’ in ‘field list’ ”

    i assume that means it cant find your database or the column named “uniqeid” in your database.

    did you change the names when running your MySQL query to create the tables? if you did you have to change “uniqueid” to whichever column your keys are in.

  37. alex


    I’m pretty sure i set it up correctly but am getting the following error:

    Download Key Generator
    Access denied for user ‘MYCPANELLOGINNAME_USERNAME’@’localhost’ to database ‘download’.

    I set all priviledges for the MYCPANELLOGINNAME_USERNAME to MYCPANELLOGINNAME_database? What am i doing wrong?

  38. Kris


    In the dbconnect.php, you need to edit the last line and insert “MYCPANELLOGINNAME_database” where it reads “download”. This line is calling the specific name of the database which I assume you didn’t name “download”.

  39. vadim

    I’m unable to force save as file. In my case I can see only source code of zip file in a different browsers, but there is no save as dialog.

  40. Ken

    I know this is old now but thought i’d update as I struggled to get this to work with multiple rar files being corrupted.

    My only solution was to make an iso image of all the files then use it like this:

    $file = “download.iso”;
    $filename = “bogus_download_name.iso”;

    header(“Cache-Control: public, must-revalidate”);
    header(“Pragma: no-cache”);
    header(“Content-Type: ” . $mm_type);
    header(“Content-Length: ” .(string)(filesize($file)) );
    header(‘Content-Disposition: attachment; filename=”‘.$filename.'”‘);
    header(“Content-Transfer-Encoding: binaryn”);


    Very usefull script indeed. thanksKe

  41. Jason

    Great script. Is there an easy way to have the link open a page then have the user click a download button instead of automatically starting the download? I want to present information then have the user input their email and agree to TOS before they can proceed with the download.


  42. num

    Ann (comment #38), you need to change the values for $file and $filename. $file is the actual file name, and $filename is the filename of $file that the PHP script will deliver to the user.

    For those who are getting the mysql errors (#45, #46, & #48), try removing the single quote marks around each field being created.

    This is an eye-opener. Thank you so much, ardamis!

  43. num

    I wrote: “and $filename is the filename of $file that the PHP script will deliver to the user.”

    What I mean is that using this hides the actual file by using an alias name, if you will, to deliver to the end user. On my implementation:

       $file = "";
       $filename = "";

    They’ll never guess the real filename, you know? And they get a file called”.

  44. Mohamed alam

    In your script only $key is fetched in download.php. I want song name to be fetched. like this the code header(‘Content-Type: application/octet-stream’);
    $file= “music/”.$fname;
    header(“Content-Disposition: attachment; filename=$file”);
    header(“Content-Length: ” . filesize(“$file”));
    header(“Content-Description: Download”);
    $fp = fopen(“$file”, “r”);

    where can i put $fname=$_GET[‘f’];
    $file= “music/”.$fname;

  45. brownsmart

    Hi, is it possible to have this version re done with “CURL” instead of “readfile” as most of us our on shared servers where “readfile” and “get_file_contents” are disabled or throw an error about file sizes being unable to allocate etc. as this script loads the file into the RAM instead of just letting the user download it.


  46. smartcoms

    This was exactly I was searching for. But I need another script that needs an e-mail address to be subscribed and then the generated link will be sent automatically via email.

    Reply ASAP

  47. Olli


    Your script is great, thanks for the share. I have a quick query though!

    I have an include script for serving my pages, such as index.php?page=download

    When i use your script in my include script the video is always corrupt when it finishes download, The file name is correct, file size is correct i cant work out why its corrupt when accessed through my index.php?page=download include script.

    If i access the download key directly through the browser it works absolutely fine.

    Any ideas?

    My include script is as follows:

    		if (!(strpos($pageWeb, "http") === false)){
    		 echo "not allowed";
    	} elseif(isSet($p_page)){
    if (!(strpos($p_page, "http")===false)){
    			echo "not allowed";
    	} else {
  48. piscator

    The key generation works nice for me. How would a code snippet look like that takes the generated code and sends it to a visitor who puts his email-address into a form?

    Thanks in advance, your script is great.

  49. Harry

    it sends me following error:
    Parse error: syntax error, unexpected T_STRING, expecting ‘,’ or ‘;’ in /homepages/11/d336918148/htdocs/einmal/downloadkey.php on line 50

    does anybody know what might be wrong?

  50. raj

    Thanks for this great article. It works great if I am not using any download manager (like – Internet Download Manager). But when I am using any download manager software, it is unable to download the file with a message saying –
    “The web site sent a web page instead of a file when IDM requested this file second time. Probably this site uses temporary links and does not allow requesting the same address twice.
    Try to delete this download, then start it again from your browser and, when IDM captures it, download it without a stop/pause.”
    Please advise if anything can be done to make this work with download managers too.

  51. jayism

    Great Stuff. Though is there any way to make a few changes to the script so instead of downloading a file you can enter URL’s to webpages that are also disquised?

    I’ve been searching for a script like this for a while! In fact a competant PHP Coder, as our New-ish Community-Based Dev Collective has lost our only PHP/mySQL Coder to an overseas UNI Semester & have some urgent work (great for Portfolio’s & we offer great incentives provided by our growing Sponsor list).


    @Code_Collective (Open Community Dev Collaborative Project)

  52. volek

    fantastic job

    how can i submit the values $maxdownloads, $maxtime, $file, $filename, from form page
    other then editing the php file directly

  53. Peter

    Great code! Does the job. There is a new startup that is tackling this problem with a SaaS product. If you don’t use PHP or just don’t want to install any code, you may prefer a solution like theirs. Their site is

  54. Joseph

    Is there a script or a modification to this one that will prevent a specific IP address from accessing the file after having done so once? Thanks.

  55. Dennis Sheperd

    Thank you so, so much for this script! Absolutely brilliant. Have been searching for something like this for such a long time!

  56. Barry Kingston

    is there any way that the link will be generated automatically and that automatically send to him via mail

  57. Hans


    Is there anyway you could update this for PHP 7?

    I’ve tried changing and fixing some of the errors thrown, but I’m a good enough coder and not confident enough to fix it completely and trust that the script doesn’t do anything weird to the database.

Comments are closed.