Before WordPress 3.0, I use codes from the plugin Private Files in one of client website to protect the files from illegal download/viewing. It works by not allowing access to WP’s uploads directory and sending and then serving a custom 404 page. It works fine until WordPress 3.0 started sending headers before calling the template’s 404.php. This resulted to the plugin not generating the requested file. After so much googling, I found out that there’s a better way of doing it by using redirect in .htaccess.

Here’s my .htaccess placed on the /wp-content/uploads/ directory.

IndexIgnore *
Options +FollowSymlinks
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^http://(www.)? [NC]
RewriteCond %{REQUEST_URI} !hotlink.(gif|png|jpg|doc|xls|pdf|html|htm|xlsx|docx) [NC]
RewriteRule .*.(gif|png|jpg|doc|xls|pdf|html|htm|xlsx|docx)$ [NC]

What it does is that files will not be displayed if the referrer is not my own domain. It forces the user/visitor to click on the link to be able to view/download the file(only if the extension is any of those listed above). The checking is not case-sensitive (that’s what [NC] stands for).

Together with my custom codes for user access management, I was able to display and hide links based on the user profile. At the same time, search engines like Google will not be able to crawl and cache my files. So for me, it’s actually many birds in one stone and I didn’t even have to add another code on my themes and plugins (actually, I removed the custom 404). Just to be clear, I managed to get the following with this fix.

  1. Prevent Google from caching and viewing my files
  2. Prevent hotlinking. That is, another site will use images stored on my server to display them on another website. Another thing is that files will not be served/viewed by simply pasting the URL/URI on the address bar.
  3. Forcing the users to login before downloading/viewing files they own.
  4. Prevent users from viewing other user’s files when they are logged in. This is achieved by not displaying the link to the files they do not own.

Whew, that’s a lot! Unfortunately, it took me quite some time to find the solution. Actually, two nights of sleep was lost for this. And like many of the problems I’ve written before, the solution is so damn simple. By the way, the same can be implemented in NGINX (which I also use). Of course, you need to translate the directives.

Source : Unfortunately, I forgot where I got this but it’s somewhere in the forum. I’ll try to update the post once I find it again.

Note : The code is just a sample. It’s not actually running on this blog.


9 Thoughts on “Private File Attachments in WordPress

  1. Thanks for this post. This was very useful.

    Just one question though. How did you prevent users from viewing other userโ€™s files when they are logged in. How to check the ownership ?

  2. sergey on August 8, 2011 at 5:22 pm said:

    Just wanted to thank you for the tip! I got it working on WP 3.2.1 and it saved my bacon!

  3. Just a warning:

    This works for images, but not for audio. Say you upload an .mp3 to your uploads folder. If people know the URL, they can still download it.

  4. Nevermind, I forgot to add ‘mp3’ with a bar in the list of files…

  5. Thank you! ๐Ÿ™‚

  6. Thnx a lot for this great hint,
    I’ve been trying for hours to figure out, how this works. Wish I had found this post earlier ๐Ÿ™‚

  7. Well it seems that using this rewrite rules you can’t see the images in the WYSIWYG Editor. I think this is because WP uses an iframe there?
    Any ideas how to fix that?

  8. Hey, I figured out what was going wrong. Obviously, this problem occurs only under Safari, since for Safari requests the HTTP_HOST variable contains also the 80 http port (as far as I understand from this post:

    So I modified the corresponding line to:
    RewriteCond %{HTTP_REFERER} !^http://(www.)?* [NC]

    With this modification everything works well in all browsers.


Post Navigation