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:
- By default, an image cannot be hotlinked.
- Users who link directly to an image hosted on my website are redirected to a descriptive Web page.
- Specified directories allow hotlinking.
- All anti-hotlinking rules are contained in a single
.htaccessfile. (As you’ll note below, this is a change from my previous tutorial.) - 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 %{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:
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
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.
cdto your home Web directory.- Execute:
find . -name .htaccess - 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.
August 21st, 2006 at 11:52 pm
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?
August 22nd, 2006 at 12:07 am
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?
August 22nd, 2006 at 12:27 am
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…
September 9th, 2006 at 4:12 am
the link to the “view_image” file isn’t working…
January 8th, 2007 at 1:34 am
Is there a way to prevent directly access a link via the browser and hot linking??
Thanks
January 8th, 2007 at 7:59 am
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
January 8th, 2007 at 12:15 pm
pjack: You can try removing this line from the example:
RewriteCond %{HTTP_REFERER} !^$January 17th, 2007 at 5:36 pm
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
January 17th, 2007 at 6:58 pm
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.
January 17th, 2007 at 7:12 pm
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
January 29th, 2007 at 11:58 pm
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)
March 6th, 2007 at 1:21 am
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)
March 9th, 2007 at 4:50 am
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.
March 15th, 2007 at 2:36 pm
just replace yourdomain.com with widget.com.au
March 15th, 2007 at 2:37 pm
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.
June 25th, 2007 at 10:48 am
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…
June 25th, 2007 at 10:50 am
I did not copy and paste correctly… the first line actually is:
RewriteEngine on
June 25th, 2007 at 11:21 am
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…
August 29th, 2007 at 1:29 pm
I love you
this really helped a lot!! =)
November 13th, 2007 at 10:16 am
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?
November 13th, 2007 at 11:51 am
I do want the benefits of their competitors. I’d like to let through any legitimate web-based aggregator (usually RSS readers).
December 16th, 2007 at 5:38 pm
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….
January 25th, 2008 at 10:19 am
Igor said:
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.
January 30th, 2008 at 11:23 pm
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 :-)
April 3rd, 2008 at 6:05 pm
Thanks a lot for this, should help me a lot!
May 28th, 2008 at 3:18 pm
Thank you for the helpful article.
June 9th, 2008 at 8:28 am
Thanks for the code and article!