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.

147 Responses to “Preventing image hotlinking: An improved tutorial”

Pages: « 1 2 3 4 [5]

  1. 121
    Chris PV Says:

    Man, I thought it was working, and now it’s NOT, and I’m nothing but confused. The code seems to work when I tested it once…then ceased to work.

    I’ve been assured by tech support on my host that yes, they are Apache. Here’s the code I’m using:

    RewriteCond %{HTTP_REFERER} !^$
    RewriteCond %{REQUEST_FILENAME} \.(gif|jpe?g|png|bmp)$ [NC]
    RewriteRule \.(gif|jpeg|jpg|png)$ http://holyshrineofjourney.com/image.jpe [NC,L]

    It doesn’t work, and I can’t figure out why. Any ideas?

  2. 122
    Chris PV Says:

    Let me rephrase my last post — I just went & rechecked my code, and this is the correct htaccess I have there:

    RewriteEngine on
    RewriteCond %{HTTP_REFERER} !^$
    RewriteCond %{HTTP_REFERER} !^http://([a-z0-9]+\.)?holyshrineofjourney\.com [NC]
    RewriteRule \.(gif|jpeg|jpg|png)$ http://holyshrineofjourney.com/hotlink.jpe [NC,L]

    The hotlinking still works. I’ve tested from forum boards and my blog space, and the hotlinked image still appears. Again, I’ve been assured this is an Apache server, but I’m starting to suspect otherwise — the code worked perfectly on my prior host. Any ideas?

  3. 123
    Tom Sherman (blog owner) Says:

    You could try simplifying things:

    RewriteEngine on
    RewriteCond %{HTTP_REFERER} !^$
    RewriteCond %{HTTP_REFERER} !holyshrineofjourney\.com [NC]
    RewriteRule \.(gif|jpe?g|png)$ http://holyshrineofjourney.com/hotlink.jpe [NC,L]

    See how that goes…

  4. 124
    pm Says:

    the link to the “view_image” file isn’t working…

  5. 125
    pjack Says:

    Is there a way to prevent directly access a link via the browser and hot linking??

    Thanks

  6. 126
    Apache Dude Says:

    Awesome article man, very helpful. I especially like how you are checking for blank referrers.

    Here is a really good post dealing with securing SSL

  7. 127
    Tom Sherman (blog owner) Says:

    pjack: You can try removing this line from the example:

    RewriteCond %{HTTP_REFERER} !^$

  8. 128
    coredump Says:

    i have a new one for you, this one seems tricky. it seems that you cant prevent swf hotlinking. they have a param name that will allow the browser to represent itself as coming from your own domain so it bypasses the rewrite engine. look at the source below (from spelletjes.nl/game11571.html):
    param name=”allowScriptAccess” value=”sameDomain”
    param name=”movie” value=”http://www.yougetalife.com/flash/screwball.swf”
    param name=”quality” value=”high”
    embed src=”http://www.yougetalife.com/flash/screwball.swf” quality=”high” width=”570″ height=”428″ name=”spel” allowScriptAccess=”sameDomain” type=”application/x-shockwave-flash” pluginspage=”http://www.macromedia.com/go/getflashplayer”

    i removed the html tags so i dont mess up anything on the layout here, just in case.

    i added on my rewrite conditions to include .swf to not be hotlinked, but it appears this works differently with flash.

    any ideas? i host my websites on my dsl and i dont want people hijacking my bandwidth. thanks

  9. 129
    Tom Sherman (blog owner) Says:

    This parameter has nothing to do with hotlinking, which involves http only, not the security model of Flash. More on allowScriptAccess here.

    You can prevent hotlinking for SWFs. Just add the “swf” extension to the code above.

  10. 130
    coredump Says:

    yeah i read that, it only tells me how to steal someones bandwidth, im trying to contact adobe but their support page wont allow me to get to the open a case.

    i did add the swf to the rewrite rule as well as .exe, .jpg, jpeg, gif, jpe, and some others, but this swf option fools the rewrite engine into thinking theyre loading it from my site and not a third party.

    thanks for the reply though. ill continue to try adobe and will try to contact someone at apache and see what their thoughts are.

    your site has a lot of helpful info, so i came here since it is where i learned how to block hotlinking in the first place :)

    thanks again

  11. 131
    yo go re Says:

    Thank you bery much. An hour ago I had no clue at all what .htaccess was, and now I’ve cut off almost all hotlinking. This was a very straightforward and understandable piece, and it’s helped me a bunch.

    (the view_image.shtml source seems to be missing, though, so for now it’s just a plain ol’ moritorium on hotlinking)

  12. 132
    Sylvain Says:

    Dude, you rule!

    I tried this code from another tutorial:

    RewriteEngine on
    RewriteCond %{HTTP_REFERER} !^$
    RewriteCond %{HTTP_REFERER} !^http://(www\.)?yourdomain.com(/)?.*$ [NC]
    RewriteRule \.(gif|jpe?g|png|bmp)$ /images/humiliatingimage.gif [L,NC]

    but my own images started getting replaced when viewed in a pop up java script.

    I’ve tried yours and it’s working perfectly.

    Thanks!

    P.S. Now they’re getting this:

    http://www.sylvainbouchard.com/images/thisiswhatyougetforstealingbandwidth.jpg

    Please feel free to use it (once it’s uploaded onto your own server, that is)

  13. 133
    Jon Says:

    nice script mate!

    How do I adjust it if my domain is widget.com.au?

    (an Australian domain)

    And will your script allow access to google.com.au?

    Thanks,

    Jon.

  14. 134
    coredump Says:

    just replace yourdomain.com with widget.com.au

  15. 135
    coredump Says:

    sorry for double posting, but the second part of the question would be to add a line similar to the line to allow your domain but use the google.com.au as well.

    i use that for a site to borrow images from my bandwidth.

  16. 136
    Scott S. Says:

    I am getting this error when I implement the following code…

    [an error occurred while processing this directive] The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there was an error in a CGI script. [an error occurred while processing this directive]

    —code—-

    ewriteEngine on
    RewriteRule ^.*$ - [L]
    RewriteBase /
    RewriteCond %{REQUEST_FILENAME} \.(gif|jpe?g|png|bmp)$ [NC]
    RewriteCond %{HTTP_REFERER} !^$
    RewriteCond %{HTTP_REFERER} !americansandassociation\.org [NC]
    RewriteCond %{HTTP_REFERER} !asasand\.org [NC]
    RewriteCond %{HTTP_REFERER} !asasand\.com [NC]
    RewriteCond %{HTTP_REFERER} !glamisonline\.org [NC]
    RewriteCond %{HTTP_REFERER} !isdratrt\.org [NC]
    RewriteCond %{HTTP_REFERER} !google\. [NC]
    RewriteCond %{HTTP_REFERER} !search\?q=cache [NC]
    RewriteRule (.*) /im_a_thief/thief.html?/$1 [R,NC,L]
    DirectoryIndex index.php

    Any ideas… on what could be wrong…

  17. 137
    Scott S. Says:

    I did not copy and paste correctly… the first line actually is:

    RewriteEngine on

  18. 138
    Scott S. Says:

    Ok.. I got rid of the error message.. it was my mod_rewrite, it was not loading properly.. but the code still does work, and I can still link to images on the website…

  19. 139
    Hip Hop Says:

    I love you

    this really helped a lot!! =)

  20. 140
    Lucas Says:

    How come you allow Bloglines and Google through, Tom? I could see blocking out everybody, but not almost everybody.

    Once you decide that you want the benefits of Bloglines and Google linking and embedding your stuff, don’t you also want the benefits of their competitors?

  21. 141
    Tom Sherman (blog owner) Says:

    I do want the benefits of their competitors. I’d like to let through any legitimate web-based aggregator (usually RSS readers).

  22. 142
    Igor Says:

    Great solution, thanks! Many of our images end up in forums and this is a great way for at leat the text links like:
    123
    However if the link is direct to the image
    like:

    the result is a broken image.
    Wouldn’t it be possible to place the image with some html-tags around it, say in a to make it clear where the picture came from?
    An inbound link is an inbound link….

  23. 143
    bartellonline.com Says:

    Igor said:

    Wouldnt it be possible to place the image with some html-tags around it, say in a to make it clear where the picture came from?

    I don’t see any link for your 2nd example, but if you’re talking about cases where people link to your images using tags tags (e.g. <img> src=”http://your_server/your_image.jpg</img>) I’m pretty sure wrapping html tags around the image won’t work since the browser will be expecting an image and not parsing for html.

    I use a similar approach to that described in the tutorial and the script I use in the RewriteRule is a PHP program that creates a jpeg on the fly that contains text with a notice about hotlinking and the URI where the image can be found on my website in its original context. I can’t post my own example here since images tags aren’t amoung the “Allowed tags” (which I’m betting is why your 2nd example didn’t work), but unless the other person removes the link you can find the likely result of hotlinking to one of my images at http://www.hartachina.ro/poza-china-shenyang-stone_animals_.html

    Just to avoid any confusion from possible experiments, another difference in my approach is that I reverse the turorials approach to specifying what sites are allowed to hotlink, initially allowing all sites to hotlink then adding RewriteCond rules to deny specific sites after I notice the referrals in my logs and have a look at the site. I choose this approach for several reasons, among them to avoid missing an opportunity to have my images included in new search engines. I might also allow the link to work if the other site is decent and the author has at least had the courtesy to credit my site and include a text link to it together with the image. This does result in a fairly long and growing list of RewriteConds (about 150 so far) but so far it has had no noticeable impact on the server performance since it’s not terribly busy.

    Hope this helps.

  24. 144
    Marc Says:

    Hi,
    Your link no longer works.
    http://underscorebleach.net/view_image?/jotsheet/images/carrot_top.jpg
    Just throws up a 404 page.

    I’m redirecting to a php page and the image doesn’t show. If I refresh the page it appears, but only because it now has a referer.

    I’d like to see yours working so that I know it does work. heh :-)

  25. 145
    Lil Wayne Says:

    Thanks a lot for this, should help me a lot!

  26. 146
    hot myspace layouts Says:

    Thank you for the helpful article.

  27. 147
    Patrick Says:

    Thanks for the code and article!

Pages: « 1 2 3 4 [5]