How To Prevent Hotlinking With PHP

Hotlinking is the act of one site embedding content (such as images, audio files, and videos) hosted on another site. This uses up the ‘bandwidth’ (data transfer allowance) of the site hosting the file, which can be very expensive for webmasters who pay for hosting by the amount of data transferred — as a result, hotlinking is often called ‘leeching’ or ‘bandwidth theft’.

Hotlinking can be prevented using the .htaccess file on an Apache web server, but cheaper web hosting packages often don’t allow webmasters to change this file. The code shown below allows webmasters to prevent hotlinking entirely from within PHP (without .htaccess), which is supported by most web hosting packages. As well as preventing hotlinking the code also allows the site to benefit from direct links (when a site links to content on another site but doesn’t embed it).

How to use the code to prevent hotlinking

  1. Create a directory on your website to contain the anti-hotlinking script and your protected media.
  2. Copy the code in prevent-hotlinking.txt into a file called index.php inside that directory.
  3. Replace the URL on the ninth line of the script with the URL of your website — don’t forget to include the trailing slash.
  4. Replace the phrase ‘secret-directory-name-here’ in the second line of the script with something people are unlikely to guess (for example, a string of thirty random letters) and create a directory with this name inside the directory containing the script. Do not reveal the name of this secret directory to anyone, as doing so would allow people to hotlink to the files it contains. If you do accidentally reveal the directory name, just rename it and alter the script accordingly.
  5. For each piece of content you want to protect, place it in the secret directory (you can also create subdirectories inside that directory).
  6. For each piece of content create a file with the same name but with .php appended and place it in the same location as the content. This document should contain HTML (possibly with embedded PHP) to display when a site has either hotlinked or directly linked to yours. The simplest code will redirect the request to the page on your site containing the content:
  7. To include the content in your pages, use a URL of the form, where ‘script-directory’ is the directory containing the anti-hotlinking script, and ‘content-file’ is the name of the content file (if the content file is in a subdirectory of the secret directory, include the subdirectory in the path — for example ?file=subdirectory/contentfile). These URLs can be included in the pages mentioned in the previous step in order to create a custom page displaying the content instead of redirecting to an existing page on your site.
  8. Now when someone tries to hotlink to content on your site, people visiting that page will only see the content if they have already been to your site. Direct links to content on your site will return an HTML file containing the content so you can ensure visitors see the file in its original context.

The script currently includes MIME types for JPEG, PNG, WAV, and MIDI files. If you need to extend it to handle other file types, look up the MIME type name in the official list of MIME types and then add code of the following form above the line containing 'jpg'=>'image/jpeg',:


‘extension’ is the file extension (for example, ‘mpg’) and ‘mimetype’ is the official MIME type (for example, ‘video/mpeg’). Some formats do not have registered MIME types, in which case the x prefix should be used (for example, ‘image/x-myimageformat’).

How the code to prevent hotlinking works

 1  <?php
 2  $dir='secret-directory-name-here/';
 3  if ((!$file=realpath($dir.$_GET['file']))
 4      || strpos($file,realpath($dir))!==0 || substr($file,-4)=='.php'){
 5    header('HTTP/1.0 404 Not Found');
 6    exit();
 7  }
 8  $ref=$_SERVER['HTTP_REFERER'];
 9  if (strpos($ref,'')===0 || strpos($ref,'http')!==0){
10    $mime=array(
11      'jpg'=>'image/jpeg',
12      'png'=>'image/png',
13      'mid'=>'audio/x-midi',
14      'wav'=>'audio/x-wav'
15    );
16    $stat=stat($file);
17    header('Content-Type: '.$mime[substr($file,-3)]);
18    header('Content-Length: '.$stat[7]);
19    header('Last-Modified: '.gmdate('D, d M Y H:i:s',$stat[9]).' GMT');
20    readfile($file);
21    exit();
22  }
23  header('Pragma: no-cache');
24  header('Cache-Control: no-cache, no-store, must-revalidate');
25  include($file.'.php');
26  ?>
Line 2
Line 2 stores the name of secret directory so that the script can easily be modified.
Lines 3 to 7
On line 3, the first condition of the if-test checks that the file exists and resolves any relative path components (for exmaple ../). On line 4, the second condition checks the the path specified is in the secret directory (so that people can’t use relative path components to see the source code of scripts on your site), and the third condition checks that the path does not refer to one of the PHP files associated with the content. If the file specified should not be accessed (or doesn’t exist) a 404 (file not found) status code is returned and the script exits.
Lines 8 and 9
The if-test in line 9 could have been written in various forms, but using the ‘or’ operator with this order of parameters is fastest. The test allows the file to be displayed if either the referrer header does not refer to a website (for example, a blank refer or one obscured by security software), or if it refers to the correct wesbite. The slash at the end of the website name is necessary to prevent exploitation by websites with names of the form, and the use of ‘http’ in the second condition catches both regular (http://) and secure (https://) referrers.
Lines 19 to 21
These lines output the content file with the appropriate headers for content type, content length, and date of last modification, and then exit the script.
Lines 23 to 25
These lines output headers to prevent caching and then perform the action specified in the PHP file associated with the content file (for example, redirecting to the page containing the content). The anti-caching headers are needed so that if users later try to view the content as you intended they will receive the content file and not the HTML file. Note that the content file itself will be cached, which means that if a site hotlinks to the file and a user already has the file in their cache they will still see it. If they don’t already have the file in their cache, their browser will download an HTML file, which it will realise isn’t a media file and won’t display (unless the link was a direct link, rather than a hotlink).

For more ready-to-use examples of this kind, try O’Reilly & Associates’ excellent PHP Cookbook: