Stop Hotlinking

metadata

First, you may ask what is hotlinking? Hotlinking is when someone uses one of your images on their webpage. Usually this is very bad for two reasons:

It is this second point which is “hotlinking”. Below are two examples of someone hotlinking my images. They are real examples which you can read the address bar of and check out if you want.

Download:
  1. 512 px × 320 px (0.2 Mpx; 66.0 KiB)
  2. 1,024 px × 640 px (0.7 Mpx; 200.9 KiB)
  3. 1,280 px × 800 px (1.0 Mpx; 222.8 KiB)
Download:
  1. 512 px × 320 px (0.2 Mpx; 86.0 KiB)
  2. 1,024 px × 640 px (0.7 Mpx; 275.5 KiB)
  3. 1,280 px × 800 px (1.0 Mpx; 339.9 KiB)

This article will show you how to stop the hotlinking of your images. This article has been written after I read a great article on the subject at A List Apart (called Smarter Image Hotlinking Prevention) and I felt that I could improve on it by simplifying the solution.

Firstly, one must edit the .htaccess file on your web server. Now this is the solution that a quick Google search will turn up, but once this solution is implemented we have not finished, there is a final touch which needs to be done. So, in the root of your web server create a file called .htaccess which contains these lines:

1
2
3
4
5
6

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} .*jpg$|.*jpeg$|.*bmp$|.*gif$|.*png$ [NC]
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://(.+\.)?exampledomain1 [NC]
RewriteCond %{HTTP_REFERER} !^http://(.+\.)?exampledomain2 [NC]
RewriteRule (.*) /path/to/custom/script.php?src=$1

              
You may also download “stop-hotlinking-htaccess” directly or view “stop-hotlinking-htaccess” on GitHub Gist (you may need to manually checkout the “main” branch).

If the file already exists then just append these lines. If you are having difficulty creating this file then call it something else and edit it on your computer then upload it via FTP and rename it once it is on the server.

What this file does is only look at files which have extensions jpg, gif or png. Then, when someone tries to access one of these files it looks to see where the request is coming from. If the request is from one of your “example domains” (which you can change to your own website), or if the HTTP Referer is empty, then the request can proceed as normal. If not, then the request will get sent to /path/to/custom/script.php?src=/path/to/requested/image.jpg. Now, the user will not see this address, they will get the address that they want but the actual place where the response comes from will be that PHP script. As far as the user is concerned this is a totally transparent process - they will never know.

Now we need to create that PHP script and then we’re done. You’ll have to make sure that the link in the .htaccess points to the actual location of the PHP script we’re about to make. The PHP script should contain something like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

 <?php
header("Content-type: text/html");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Random Date in Past
header("Cache-Control: no-cache");
header("Pragma: no-cache");
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Hotlinked Image</title>
    <meta name="description" content="A page to display an image as it was directly linked to by an external site." />
</head>
<body>
<h1>Requested Image</h1>
<?php
if(!isset($_GET["src"]) || empty($_GET["src"])) {
    echo "<p>No image was linked, perhaps you've just navigated to this page on your own?</p>";
}
else {
    echo "<p>As you clicked on a <i>direct</i> link to this image from an external site, this page has been shown. The image you're after is shown below. You may click on it to fully display just the image. You may have to press <i>F5</i> to refresh it once you've clicked on the link.</p>";
    $temp = getimagesize($_SERVER["DOCUMENT_ROOT"] . "/" . strip_tags($_GET["src"]));
    $desiredWidth = 600;
    if($temp[0] > $desiredWidth) {
        $width = $desiredWidth;
        $height = $temp[1] * ($desiredWidth/$temp[0]);
    }
    else {
        $width = $temp[0];
        $height = $temp[1];
    }
    echo "<p><a href=\"/" . strip_tags($_GET["src"]) . "\" title=\"Requested Image\"><img src=\"/" . strip_tags($_GET["src"]) . "\" alt=\"Requested Image\" width=\"" . $width . "\" height=\"" . $height . "\" /></a></p>";
    unset($temp,$desiredWidth,$width,$height);
}
?>
</body>
</html>

              
You may also download “stop-hotlinking-script.php” directly or view “stop-hotlinking-script.php” on GitHub Gist (you may need to manually checkout the “main” branch).

Now, a few things need to be explained so you can see how it works.

  1. The header("Content-type: text/html"); is very important. This tells the web browser that this file is a HTML file. This will stop the image being displayed straight away as the browser is expecting an image, so to be told it’s getting some text will throw a spanner in the works. Problem solved: image is not displayed.

  2. Now, the stuff about header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); is just telling the browser not to remember this page so that if it visits it again it’ll fetch it without using a cached version.

  3. Now, the rest is just a fancy way of displaying the image that the person wanted if they visit the page. This will only happen if the user visits the page on its own and it’s just a way to show your content, on your site. It contains a link to see the image full size. Now obviously this will have the same address, but as they will have clicked on a link from your own site then they’ll just get the image on it’s own. This is also why we put the anti-cache lines in as we don’t want the browser to remember the text bits.

The reason why this method should be used (and is better) than simple denials is that if someone puts a text link to your image on a website and then a user follows this link it will look to the .htaccess file as if it is being hotlinked and it will not display it - which is bad! Using this method stops your images being displayed on alien websites but still shows your image for visitors coming from those alien websites of their own accord.

To continue the worked example, this is what their pages look like after my solution has been implemented:

Download:
  1. 512 px × 320 px (0.2 Mpx; 64.1 KiB)
  2. 1,024 px × 640 px (0.7 Mpx; 185.8 KiB)
  3. 1,280 px × 800 px (1.0 Mpx; 180.7 KiB)
Download:
  1. 512 px × 320 px (0.2 Mpx; 90.9 KiB)
  2. 1,024 px × 640 px (0.7 Mpx; 264.2 KiB)
  3. 1,280 px × 800 px (1.0 Mpx; 254.9 KiB)

… you see? No images! If a link was provided to one of the images then the HTTP Referer will also be the same as a hotlink and so it will trigger the PHP page, as we’ve already discussed above. The pages will then look like this:

Download:
  1. 512 px × 320 px (0.2 Mpx; 146.3 KiB)
  2. 1,024 px × 640 px (0.7 Mpx; 480.9 KiB)
  3. 1,280 px × 800 px (1.0 Mpx; 593.4 KiB)
Download:
  1. 512 px × 320 px (0.2 Mpx; 134.1 KiB)
  2. 1,024 px × 640 px (0.7 Mpx; 431.5 KiB)
  3. 1,280 px × 800 px (1.0 Mpx; 508.6 KiB)

… which show the preview image and a link to see the image on its own. I hope this helped!