Creating the Ultimate htaccess Anti-Hotlinking Strategy

[ Image: Illustration of two hands holding a glowing object ] When I wrote my article, Stupid htaccess Tricks, a couple of years ago, hotlink-protection via htaccess was becoming very popular. Many webmasters and bloggers were getting tired of wasting bandwidth on hotlinked resources, and therefore turned to the power of htaccess to protect their content. At that time, there were only a couple of different hotlink-protection methods available on the internet, and the functional difference between them was virtually insignificant. All that was necessary for up-and-coming bloggers-slash-site-administrators to eliminate leaking bandwidth and stolen resources was a relatively straightforward copy-&-paste procedure. Implementing the de facto htaccess hotlink protection code required a simple binary decision: “do you want hotlink-protection or not?”

These days, however, things are not so simple. Today, if you do a search on the internet for “htaccess hotlink protection”, you’ll get the phone book. There are countless mutations of the once tried-and-true htaccess code. There have been significant improvements, but there is also plenty of noise and error lurking among the countless techniques. These days, protecting your site’s assets and conserving bandwidth requires either trusting the first htaccess trick you find, or performing a mind-numbing and time-consuming amount of research to find the htaccess anti-hotlinking strategy that’s perfect for you and your domain. With this article, I do the grunt work for you — analyzing, deconstructing, and cannibalizing a contemporary collection of hotlink-protection methods to create the ultimate htaccess Anti-Hotlinking Strategy.

Conclusion

Before we dig into the critical analysis of the myriad methods, let us continue our practice of catering to all of you copy-&-paste hounds out there by providing the finished product right up front:

# ultimate hotlink protection
<IfModule mod_rewrite.c>
 RewriteEngine on
 RewriteCond %{HTTP_REFERER}     !^$
 RewriteCond %{REQUEST_FILENAME} -f
 RewriteCond %{REQUEST_FILENAME} \.(gif|jpe?g?|png)$           [NC]
 RewriteCond %{HTTP_REFERER}     !^https?://([^.]+\.)?domain\. [NC]
 RewriteRule \.(gif|jpe?g?|png)$                             - [F,NC,L]
</ifModule>

Of course, there is much more to the story, as well as a small army of configurational options and possibilities. Nonetheless, if you could care less about the carefully executed logic and reasoning behind the development of this “ultimate” hotlink protection strategy, feel free to copy and paste the entire chunk into your site’s root htaccess file and remember to change the term “domain” to match that of your own. No other adjustments or edits are necessary. Strictly plug-n-play dude.

Now, for the intellectually inquisitive, or for those seeking a deeper understanding of the htaccess rules involved in hotlink protection, may I enthusiastically invite you to “read on”..

Digging in..

Okay, after much deliberation, I have decided to break this down as simply and concisely as possible. Thus, we will consider our collection of anti-hotlinking techniques individually and sequentially. For each method, we will examine the complete code, and then proceed with a brief analysis and summary of the technique’s key aspects. As we deconstruct each strategy, we will collect these gems and cannibalize the best of the best to create the finished product. Additionally, we will check out a few choice code variations and alternate configurational options that serve to expand overall functionality while facilitating a more flexible implementation. Here is a peak at the menu:

  • Hotlink Protection via Stupid htaccess Tricks
  • Hotlink Protection via REQUEST_FILENAME
  • Hotlink protection allowing all variations of the owner’s URL
  • Hotlink protection allowing for multiple domains
  • Streamlined, simplified hotlink protection
  • Code variations and configurational options
  • The ultimate htaccess hotlink-protection strategy
  • Conclusion (for reals this time)

Sound good? Okay, let’s begin..

Hotlink Protection via Stupid htaccess Tricks

In my article, Stupid htaccess Tricks, I present the following method of htaccess hotlink protection:

# hotlink protection via stupid htaccess tricks
<IfModule mod_rewrite.c>
 RewriteEngine on
 RewriteCond %{HTTP_REFERER} !^$
 RewriteCond %{HTTP_REFERER} !^http://(www\.)?domain\.tld/.*$ [NC]
 RewriteRule .*\.(gif|jpg)$ http://www.domain.tld/eatme.jpe   [R,NC,L]
</ifModule>

Back when I wrote the article, this basic hotlinking technique was widely employed and taken as the de facto standard method of preventing hotlinking scumbags. Although simplistic, there are several key aspects to this technique:

Error Prevention — The hotlink-protection rules are enclosed within an IfModule container that checks for the availability of the required Apache rewrite module (aka mod_rewrite) before attempting to process any Rewrite directives. This helps to avoid sudden, unwanted errors from crashing your site, and is just good practice in general.

Consolidated Canonicalization — As we will see, many other hotlink-protection methods use two RewriteCond rules for each targeted domain (either blocked or allowed) in order to accommodate for both the www and non-www versions of the site’s URL. Here, we have eliminated redundancy by considering either URL with a single line of code. We do this by preceding the domain with “(www\.)?”, which makes the www optional.

So, from this hotlink-protection ruleset, we will keep the error-preventing IfModule container and the consolidated canonicalization trick. The remainder of this technique is quite common, and will be repeated several times before this article has finished. Let’s move on..

Hotlink Protection via REQUEST_FILENAME

This method of hotlink protection takes a different approach by using the REQUEST_FILENAME parameter in the RewriteCond, thereby targeting the names of the hotlinked files rather than the referring domain:

# hotlink protection via request filename
RewriteEngine On
RewriteCond %{REQUEST_FILENAME}   -f
RewriteCond %{REQUEST_FILENAME}   -d
RewriteCond %{REQUEST_FILENAME}   \.(jpeg|jpg|gif|png)$         [NC]
RewriteCond %{HTTP_REFERER}       !^http://domain\.tld/.*$      [NC]
RewriteRule protected/(.*)        http://domain.tld/hotlink.jpg [R,NC,L]

This is another widely used technique that has been modified in countless ways. This version represents a generalized technique for protecting a specific directory (i.e., /protected/) from hotlink requests. Key aspects of this technique include the following:

Targets the file, not the referrer — Although I am not sure if it is more effective to target the requested file rather than the actual referrer, using REQUEST_FILENAME is known to be quite effective. In this particular example, all requests for .jpg, .jpeg, .gif, and .png images that are located in the “protected” directory will be blocked, unless from the owner’s domain (i.e., http://domain.tld/).

Checks for existence of requested file and directory — This is an added layer of protection that many hotlink protection methods use to further secure their server environment. In the second and third lines in our example, we are checking for the existence of the requested file (-f) and directory (-d). If the requested file and directory actually exist on the server, the remaining rules will be processed. If everything lines up, requests for protected file types will return the hotlink.jpg image; otherwise, the requested image does not exist and hotlink.jpg will not be served. This prevents the serving of your anti-hotlinking image in cases where the requested image does not exist, thus saving you bandwidth and avoiding confusion in general.

Beyond these two features — targeting the file and checking the file/directory— the remainder of this technique is rather common. In addition to these two gems, exclusively protecting a specific directory is also a handy trick. Let’s save these three items in our collective memory and continue with another example..

Hotlink protection allowing all variations of the owner’s URL

Another common implementation of hotlink protection allows image access only for all variations of the owner’s URL, including both www and non-www versions, as well as the IP address and port 80 access for the domain:

# hotlink protection allowing all source urls
RewriteEngine on
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://domain.tld.*$            [NC]
RewriteCond %{HTTP_REFERER} !^http://www.domain.tld.*$        [NC]
RewriteCond %{HTTP_REFERER} !^http://domain.tld:80.*$         [NC]
RewriteCond %{HTTP_REFERER} !^http://www.domain.tld:80.*$     [NC]
RewriteCond %{HTTP_REFERER} !^http://123.123.123.123.*$       [NC]
RewriteCond %{HTTP_REFERER} !^http://123.123.123.123:80.*$    [NC]
RewriteRule .*[Jj][Pp][Gg]$|.*[Gg][Ii][Ff]$|.*[Pp][Nn][Gg]$ - [F,NC,L]

This common method incorporates two key aspects that we will cannibalize for our “ultimate” hotlink-protection strategy:

Comprehensive access for the source domain — This technique goes to great lengths to ensure that every possible version of the source domain is allowed open access to all images. Aside from blank referrer requests, all other domains and access attempts are stopped cold. It is unnecessary, however, to employ six lines of code to account for all instances of the host domain. Later in the article, when we integrate this aspect into our improved strategy, we will accomplish the same thing (and more) with only two lines of code.

Accounts for all variations of the target file extensions — The last line of this ruleset specifies which types of images to protect. In this case, we are protecting .jpg, .gif, and .png file types. Even better, we are preventing access via any variation of the file extension itself. File extensions written in uppercase, lowercase, or any combination thereof, are effectively blocked. This is a key aspect of any hotlink protection technique. Fortunately, however, the [NC] specified at the end of the last line makes it unnecessary to specify both uppercase and lowercase letters in each of the file names.

As mentioned, allowing comprehensive access is important, not only for the URL variations specified here, but for any required subdomains as well. Further, this code would benefit from the addition of logical [OR] operators combined with each of the first six [NC] operators. Without explicitly specifying “or” after each line, the conditions are processed with an inherent “and”, meaning that all conditions must apply before the rewrite occurs.

Hotlink protection allowing for multiple domains

In this example, we demonstrate a widely used technique for allowing image access to multiple domains, including Yahoo!, Google, and three additional domains:

# hotlink protection allowing for multiple domains
RewriteEngine on
RewriteCond %{HTTP_REFERER}  .
RewriteCond %{HTTP_REFERER}  !^http://([^.]+\.)?domain-01\. [NC]
RewriteCond %{HTTP_REFERER}  !^http://([^.]+\.)?domain-02\. [NC]
RewriteCond %{HTTP_REFERER}  !^http://([^.]+\.)?domain-03\. [NC]
RewriteCond %{HTTP_REFERER}  !search\?q=cache               [NC]
RewriteCond %{HTTP_REFERER}  !google\.                      [NC]
RewriteCond %{HTTP_REFERER}  !yahoo\.                       [NC]
RewriteCond %{REQUEST_URI}   !^/hotlink\.jpg$               [NC]
RewriteRule \.(gif|jpg|png)$ http://domain.tld/hotlink.jpg  [R,NC,L]

This very useful method enables us to specify additional domains for which to allow image/resource access. There are many situations in which webmasters need to extend access to search engines, feed readers, and associate sites. Here are the key points of this method:

Better “blank-referrer” access — Every serious hotlink protection strategy provides resource access for “blank-referrer” (or “no-referrer”) requests. Blank referrers are commonly associated with third-party ISPs, firewalls, direct requests, and other such situations. Unless you have specific reason to do otherwise, it is highly recommended that you enable access to blank-referrer requests. As seen in previous examples, this is accomplished via “!^$”, which means “not blank.” This example employs the more semantically correct “ . ” (dot), which specifies any character, including …

Comprehensive access for multiple domains, subdomains — Following the pattern presented in the first three lines, we may allow access to as many domains as necessary. Even better, all subdomains associated with the allowed domains are also included via the regex, ([^.]+\.)?, which literally matches virtually any string preceding the domain. Thus, all subdomains are also allowed access.

Access for Google, Yahoo!, et al — Allowing access to the major search engines is a great way to attract new visitors to your site. Of course, there are many possibilities here, depending on your personal site-optimization strategy.

Allow universal access to the hotlink.jpg image — If you are serving a nasty image to the worms that would otherwise steal your bandwidth, it is important that they are able to access it. Many of the hotlink-protection strategies I have seen around the Web somehow fail to accommodate or mention this important aspect. Fortunately, this example reminds us of the fact by allowing complete access to our offensive anti-hotlinking image.

Wow, that’s a lot of useful material from this technique. Let’s add it to our stash and move on..

Streamlined, simplified hotlink protection

In our final example of htaccess hotlink-protection techniques, we examine an effective, streamlined approach:

RewriteEngine on
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://([a-z0-9]+\.)?domain\.tld [NC]
RewriteRule \.(gif|jpe?g|png)$ - [F,NC,L]

Although simple, this method provides two key aspects:

Alternate wildcard regex wildcard string for subdomains — In our previous example, we enable access to all subdomains associated with our allowed domains via regular expression. This method accomplishes the same functionality, only by using an alternate regex statement. Both are effective, but it is good to have an alternative ;)

Condensed file extension list — This example demonstrates a way to consolidate and simplify the list of protected file types by using employing a “?” (i.e., question mark) to signify an optional letter “e” for the .jp(e)g extension. Here, the question mark indicates that the preceding character (the letter “e” in this case) is optional, thereby producing a match for either scenario. This is a useful trick that will serve us well in our final strategy.

Now that we have pillaged a fine collection of contemporary htaccess anti-hotlinking rules, let’s shift gears momentarily and consider several individual htaccess directives involved with hotlink prevention. Many of the following rules are alternate versions of previously considered code.

Code variations and configurational options

Let’s face it, there are often many ways to formulate htaccess directives, especially when it comes to using Apache’s mod_rewrite to prevent hotlinking. Here are a few examples demonstrating variations of previous code examples..

Optimizing the file type list

In our third anti-hotlink example, the following code is used to specify the protected file types:

RewriteRule .*[Jj][Pp][Gg]$|.*[Gg][Ii][Ff]$|.*[Pp][Nn][Gg]$ - [F,NC,L]

That works, but we can simplify it a bit:

RewriteRule \.([Jj][Pp][Gg]|[Gg][Ii][Ff]|[Pp][Nn][Gg])$ - [F,NC,L]

Okay, that’s a little better, but there is still room for improvement. Notice the NC in the brackets. That tells Apache to ignore the casing of characters in the regex string. Thus, explicitly specifying both uppercase and lowercase characters in the list of file types is unnecessary. This fact enables us to simplify the RewriteRule quite significantly:

RewriteRule \.(jpg|gif|png)$ - [F,NC,L]

There, that’s much better — but we can improve it a little bit more by supporting all three types of .jpg files (i.e., .jpg, .jpeg, and .jpe), in addition to .gif and .png files:

RewriteRule \.(jpe?g?|gif|png)$ - [F,NC,L]

Of course, to protect additional file types, simply add another pipe symbol (“ | ”) followed by the associated file extension. For example, to add several additional file types, we could write something like this:

RewriteRule \.(jpe?g?|gif|png|bmp|tiff?|pic)$ - [F,NC,L]

And yes, we can easily support other, non-image resources files as well. Let’s protect some multimedia files and Microsoft documents:

RewriteRule \.(jpe?g?|gif|png|bmp|tiff?|pic|mp3|doc|xls)$ - [F,NC,L]

Different responses to hotlink requests

In most of the anti-hotlinking rules presented above, the server responds to all blocked resource requests with a “403 – Forbidden” error. This works well enough, but there are many situations where a simple 403 just doesn’t cut the mustard. For example, if you utterly despise hotlinking bandwidth thieves, you may prefer to serve ‘em a piping-hot close-up of your favorite hairy hole (or whatever). To do this, modify the last line in your set of rules as follows:

# serve em a macro shot of your hairy hole
RewriteRule \.(jpe?g?|gif|png)$ http://www.domain.tld/hairy-hole.jpg

Now, when you do this, you want to ensure that you are not blocking access to your hairy-hole.jpg by including a line similar to this directly before the RewriteRule:

# ensure access to your hairy hole jpg
RewriteCond %{REQUEST_URI} !^/hairy-hole\.jpg$ [NC]

Likewise, rather a macro-shot of your nose hole (or ear hole, for that matter), you may want to refer hotlinking scum to a specific file, say “hotlink-policy.html”. In this case, modify the last line in your set of rules as follows:

# send em a web page explaining your hotlink policy
RewriteRule \.(gif|jpe?g|png)$ http://www.domain.tld/hotlink-policy.html

Finally, if you are concerned that your server will not process the RewriteRule correctly unless the requested file type happens to match that of your hairy hole image, you will need to prepare a version of your hairy-hole image in each of the protected file formats. For example, if you are protecting .jpg, gif, and png file types, and would like to serve hotlinkers a copy of your hairy hole, you will need to prepare a version of the image in each of the three file formats (e.g., hairy-hole.jpg, hairy-hole.gif, and hairy-hole.png). Then, to summon the matching file type when hotlinking is detected, replace the last line in your ruleset as follows:

# serve em a copy of your hairy hole with a matching file type
RewriteRule \.(jpg|gif|png)$ http://domain.tld/hairy-hole.$1 [F,NC,L]

As described, this directive matches the requested file type with a similarly formatted copy of your hairy hole. You know the one. Now, let’s take a look at a complete example using this technique:

# serve hotlinkers a macro shot of your hairy hole
RewriteEngine on
RewriteCond %{HTTP_REFERER}  !^$
RewriteCond %{HTTP_REFERER}  !^http://([a-z0-9]+\.)?domain\.tld [NC]
RewriteCond %{REQUEST_URI}   !^/hairy-hole\.                    [NC]
RewriteRule \.(jpg|gif|png)$ http://domain.tld/hairy-hole.$1    [F,NC,L]

Let’s move on..

Serve ‘em a nice 404 page

A great way to confuse content thieves is to respond to their hotlink requests with an artificial 404 page. To do this, use the following code in your htaccess file:

RewriteEngine on
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://([a-z0-9]+\.)?domain\.tld      [NC]
RewriteRule ^(.*)\.(gif|jpg|png)$ http://domain.tld/404.php?$1.$2   [NC,L]

..and then place a file called 404.php in the site root (or directory of your choice, just change the htaccess RewriteRule to match the new location). Within the blank 404.php document, copy & paste the following:

<?php header("HTTP/1.0 404 Not Found"); ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>404 Not Found</title></head>
<body><h1>404 Not Found</h1>
<p>The requested URL /<?php if ($_SERVER['QUERY_STRING']) : echo $_SERVER['QUERY_STRING']; endif; ?> was not found on this server.</p>
<p>Additionally, a 404 Not Found error was encountered while trying to use an ErrorDocument to handle the request.</p>
</body></html>

Protect resources on secure domains

As we wrap up the odds and ends, let’s take a look at a simple tweak that will expand hotlink protection to target resources on secure (https://) domain. To achieve this, simply add a “s?” (optional “s” character) after the http portion of the HTTP_REFERER string that represents the site in question. Here is an example:

RewriteEngine on
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https?://([a-z0-9]+\.)?domain\.tld [NC]
RewriteRule \.(gif|jpe?g|png)$ - [F,NC,L]

At last, the ultimate htaccess hotlink-protection strategy

Now that we have deconstructed a plethora of htaccess hotlink-protection directives, it’s time to throw down the “ultimate” htaccess hotlink-protection strategy. Rather than present a convoluted, one-size-fits-all chunk of heavily commented htaccess code, I provide two different versions, one requiring minimal editing, and another packed with everything:

  • Complete site protection — complete protection, minimal editing.
  • Comprehensive protection — allow multiple sites, your IP, and more.

Version 1) Complete Hotlink Protection

If you are looking for complete hotlink protection for your site and all subdomains, copy & paste the following code into your site’s root htaccess file:

# ultimate hotlink protection
<IfModule mod_rewrite.c>
 RewriteEngine on
 RewriteCond %{HTTP_REFERER}     !^$
 RewriteCond %{REQUEST_FILENAME} -f
 RewriteCond %{REQUEST_FILENAME} \.(gif|jpe?g?|png)$           [NC]
 RewriteCond %{HTTP_REFERER}     !^https?://([^.]+\.)?domain\. [NC]
 RewriteRule \.(gif|jpe?g?|png)$                             - [F,NC,L]
</ifModule>

To use the previous code, only one edit is required: change the term “domain” to match your domain. For example, if your domain name is http://www.website.com/, you would replace “domain” with “website”. Note that this code is set to protect the following file types: .jpg, .jpeg, .jpe, gif, and png. To protect additional files, such as those with the .ico format, simply add “|ico” after the “|png” in both the 6th and 8th lines.

Version 2) Comprehensive Hotlink Protection

For those of you desiring a more robust, flexible solution for hotlink protection, the following code encompasses the entire spectrum of functionality. In order to accommodate multiple features, certain lines of code have been temporarily disabled via comments (i.e., pound signs #). Further, comments are included throughout the code to explain the various options. Having said that, grab a copy, read through the code, and edit to taste:

###############################
# ultimate hotlink protection #
###############################

# disable directory browsing
# uncomment this option to protect access to directories
# Options -Indexes

# enable the following of symlinks
# uncomment this option if hotlink protection fails to work
# Options +FollowSymLinks

# verify presence of mod rewrite
<IfModule mod_rewrite.c>

 # enable the rewrite engine
 RewriteEngine on

 # check that file exists
 RewriteCond %{REQUEST_FILENAME} -f

 # check for requested file types
 # include additional file types here
 RewriteCond %{REQUEST_FILENAME} \.(gif|jpe?g?|png)$                [NC]

 # allow blank referrer requests
 RewriteCond %{HTTP_REFERER}     !^$

 # allow all requests from your domain
 # edit the domain to match your own
 RewriteCond %{HTTP_REFERER}     !^https?://([^.]+\.)?domain\.      [NC]

 # allow all requests from your ip address
 # edit the ip address to match your own
 RewriteCond %{HTTP_REFERER}     !^https?://123\.123\.123\.123(.*)$ [NC]

 # additional site access
 # include additional sites here replace domain names and or 
 # remove unnecessary lines or add new lines for more sites
 RewriteCond %{HTTP_REFERER}     !^https?://([^.]+\.)?domain_01\.   [NC]
 RewriteCond %{HTTP_REFERER}     !^https?://([^.]+\.)?domain_02\.   [NC]
 RewriteCond %{HTTP_REFERER}     !^https?://([^.]+\.)?domain_03\.   [NC]

 # search engine access
 # include or remove search engines feed readers and other sites
 RewriteCond %{HTTP_REFERER}     !search\?q=cache                   [NC]
 RewriteCond %{HTTP_REFERER}     !google\.                          [NC]
 RewriteCond %{HTTP_REFERER}     !yahoo\.                           [NC]

 # allow access to all requests for your anti hotlink image
 # to serve an image instead of delivering a 403 error
 # uncomment the next line and edit the path and file name
 # RewriteCond %{REQUEST_URI}    !^/hotlink\.jpg$                   [NC]

 # deliver the hotlink image for all requests for the listed files
 # protect additional file types by editing the list below
 # if you are serving a file instead of a 403 
 # uncomment the next line and edit the path and file name
 # RewriteRule \.(gif|jpe?g?|png)$ http://domain.tld/hotlink.jpg    [R,NC,L]

 # return a 403 error when any of the following file types is requested
 # you can protect additional file types by editing the list below
 RewriteRule \.(gif|jpe?g?|png)$                                  - [F,NC,L]

# close the module container
</ifModule>

Essentially, both of these hotlink-prevention methods are the same, employing the same underlying htaccess rules. The first version provides the fundamental functionality required to completely protect any domain, and is a generalized version intended for everyday, plug-n-play usage. The second version provides the same comprehensive coverage while also facilitating flexible customization and configuration via strategically implemented code comments.

Wrapping up then, a huge “thank you” goes out to everyone who contributed to the myriad anti-hotlink techniques discussed in this article. And, as is always the case with code presented here at Perishable Press, if you know of any way to improve any of the code examples, please drop a comment or contact me directly. Thanks ;)

Conclusion (for reals this time)

I hope this article has shed some light on the various aspects of hotlink protection via htaccess. Hopefully, the code examples presented here will help beginners gain a clearer understanding of how hotlink prevention works, while reminding a few of the Rewrite veterans in our audience of some of the subtleties associated with mod_rewrite and the process of preventing access to target resources. At the very least, I hope that the “ultimate” htaccess hotlink-protection strategy developed in this article provides webmasters with more control over their resources, images and otherwise. … God bless! ;)

Resources