psst.. this blog is on hiatus.

Preventing image hotlinking: An improved tutorial

A few months ago, I wrote up a little tutorial on stopping hotlinking (or hot-linking, also known as bandwidth theft) called Selective hotlinking prevention through .htaccess”>”Selective hotlinking prevention through .htaccess.” The idea was simple: prevent random users from stealing bandwidth while allowing defined directories to be hotlinked, e.g. for posting images on a message board. The technique described in the previous entry is still valid, but I’d like to describe an improved and more efficient approach to hotlinking prevention.

My “policies” on hotlinking

Most webmasters are content to simply prevent hotlinking and save bandwidth. I do a bit more. Here are my “policies” on hotlinking:

  1. By default, an image cannot be hotlinked.
  2. Users who link directly to an image hosted on my website are redirected to a descriptive Web page.
  3. Specified directories allow hotlinking.
  4. All anti-hotlinking rules are contained in a single .htaccess file. (As you’ll note below, this is a change from my previous tutorial.)
  5. The regular expressions used to defeat hotlinking are (hopefully!) as efficient as possible.

Redirecting hotlinked images to a descriptive Web page

You’ve probably noticed that many webmasters choose to redirect hotlinked images to something funny or intentionally offensive, or simply to drop the request at the Web server level (accomplished with the “F” [forbidden] flag). I decided to do something different. I haven’t seen this anywhere else, so maybe it’s an original idea… But this is the Internet, where everything’s already been done somewhere by someone, so I highly doubt that I thought of it first. ;)

My idea was to redirect HTTP requests for hotlinked images to a Web page giving context to the image. Of course, if you redirect a request for a JPEG to an HTML page, a user attempting to load the image via an IMG tag is going to get the ol’ red X—we’ve got a serious content-type mismatch. (This is already the behavior of anti-hotlinking sites that deny that request altogether.) But hotlinking can also be thought of in a looser sense. When other users link directly to content on your site without providing its context, it can also be a form of hotlinking. For example, by linking directly to images on your site without linking to the parent page that describes the pictures, another website can hijack your bandwidth.

Thus, I decided to redirect requests for images not originating from my Web site to a “container” Web page, which provides a link to my site, explains that the image is hosted at underscorebleach.net, and looks prettier than the image by itself. Here is an example image of Carrot Top looking like the Ultimate Warrior:

Here’s how I do it. First, we slap that regex down on the incoming HTTP request to gauge whether it’s a hotlinked image.

RewriteCond %{REQUEST_FILENAME} \.(gif|jpe?g|png)$ [NC]
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !underscorebleach\.net [NC]
RewriteCond %{HTTP_REFERER} !bloglines\.com [NC]
RewriteCond %{HTTP_REFERER} !google\. [NC]
RewriteCond %{HTTP_REFERER} !search\?q=cache [NC]
RewriteRule (.*) /view_image.shtml?/$1 [R,NC,L]

Lines 2 through 6 allow hotlinking from my site, Bloglines, Google, and cached items. I also allow requests with a null HTTP_REFERER value to obtain the image; this occurs in the case of bookmarks, some proxies, some browser settings, some third-party privacy plugins, etc. If you try to get tricky and force users to have a referrer from your own domain, you’re likely to get yourself in trouble. Trust me.

The last line redirects users to an SHTML page. Notice that I pass the value of the REQUEST_URI as a parameter in the URL to view_image.shtml. In the source, I then use a simple SSI directive to output the image. Here is the source of view_image.shtml. (file has .txt extension but put it on your website as .shtml) [updated 9/14/05 for clarity]

A quick note about this redirection technique: You do need to actually redirect, that is, use the “R” flag on the RewriteRule. If you don’t, you’re going to feed the browser an HTML page when it’s expecting an image, and it’s liable to get confuzzled.

And voila! Now, when users link to your images, they get the image, but they also get an unavoidable little advertising pitch from you, the webmaster and payer of bandwidth bills.

(Note for technical users: I’ve enabled extensionless URLs on my Web site, so “view_image.shtml” is actually “view_image” and is still server-parsed. Also, if you write PHP, feel free to convert the example view_image.shtml container page to PHP and post a link to it in the comments.)

Consolidating directives into a single .htaccess file

The power and the frustration of .htaccess is its overriding sway. A rule applied to the directory /example/ applies to /example/subdirectory/ on down. It can be “undone,” but it’s a little tricky. In my previous tutorial, I recommended inserting a “dummy rule” in an subdirectory’s .htaccess to undo rules from its parent directory. I no longer recommend this technique, because it’s too unwieldly to maintain in the case of many subdirectories. Besides, it’s inelegant.

The superior approach is to place all anti-hotlinking rules in a single .htaccess file at the root level of the website. Here is the key point: Rules for subdirectories must be higher (executed first) in the file. Combined with the “L” (last) flag that can be applied to a RewriteCond, we create a situation simulating an “if” conditional in a programming language married to a “break” statement. Here is an example for a directory /hotlinking-allowed:

RewriteEngine on
RewriteCond %{REQUEST_URI} ^/hotlinking-allowed
RewriteRule ^.*$ - [L]

RewriteBase /
RewriteCond %{REQUEST_FILENAME} \.(gif|jpe?g|png)$ [NC]
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !underscorebleach\.net [NC]
RewriteCond %{HTTP_REFERER} !bloglines\.com [NC]
RewriteCond %{HTTP_REFERER} !google\. [NC]
RewriteCond %{HTTP_REFERER} !search\?q=cache [NC]
RewriteRule (.*) /view_image.shtml?/$1 [R,NC,L]

So here’s what we’ve accomplished. With the above .htaccess, we’ve disallowed hotlinking from every directory except /hotlinking-allowed/. When an HTTP request for the hotlinked image http://underscorebleach.net/hotlinking-allowed/take_it.jpg comes in, it matches the first set of rules in the .htaccess file. Apache applies the rule and does not step through the remaining rules. However, when a request for the hotlinked image http://underscorebleach.net/tsk_tsk.jpg arrives, it won’t match the first set of rules. Then the second set of rules will smack it down and re-direct it to our cute, little container page.

Allowing hotlinking from multiple directories

Want to apply the above to multiple directories? No problemo. For the directories tacobell, bestfood, and ever, we’d use this

RewriteEngine on
RewriteCond %{REQUEST_URI} ^/(tacobell|bestfood|ever)
RewriteRule ^.*$ - [L]

Adjust accordingly as needed.

(Note: This tutorial had a typo in the RewriteCond above indicating brackets ‘[‘ instead of paranthesis ‘(‘. This was an important error, in terms of regexp, as the faulty RewriteCond is inefficient.)

Consolidating multiple .htaccess files

Already have a bunch of .htaccess files and want to consolidate them as described above? Maybe you can’t even find them anymore? Well, time to pull out our ol’ friend find.

  1. cd to your home Web directory.
  2. Execute: find . -name .htaccess
  3. Clean up.

Parting words, closing thoughts, hopes and dreams

I hope that inspires all of you Googling webmasters. If anything’s unclear, drop a comment on this page so everyone will benefit from your question. There is a LOT of information in the full comments; please read through what’s already been covered before posting a comment. You’ll get an answer more quickly, and you won’t waste my time. Many people are trying to accomplish the same ends. Thanks!

Oh, and if you found the info on this page useful, consider buying me a $2 coffee. :) It’s painless, I promise.