Spring Sale! Save 30% on all books w/ code: PLANET24
Web Dev + WordPress + Security

Protect WordPress Media Files

[ Protect WordPress Media Files ] This is an experimental technique that I am playing with. It’s the simplest possible way that I could think of to protect all files in the WordPress Media Library using only Apache/.htaccess. I’ve been testing the code on an image-heavy site and so far there are no issues. So I want to put the code out there for others to test and hopefully provide feedback if anything less than perfect. It’s a super simple method that prevents media files from being accessed from anywhere other than the site at which they are hosted.

What it Does

This technique adds a slice of code to your main .htaccess file. The code checks if the URI request is for any file inside of the WP Media Library, specifically any file located in the /wp-content/uploads/ directory. If the request is for a media file, the code then checks if the referrer matches your site URL. If it doesn’t match, the image request is from some other site, and thus will be blocked. This technique works because the site hosting the images always is the referrer for image requests.

The Code

Here is the .htaccess code to protect WP media files. Make sure to read the Pros and Cons before using this technique.

# Protect WP Media Files
# https://m0n.co/protect-media-files
<IfModule mod_rewrite.c>
	RewriteEngine On
	RewriteCond %{REQUEST_URI} /wp-content/uploads/ [NC]
	RewriteCond %{HTTP_REFERER} !^https://example.com [NC]
	RewriteRule .* - [F,L]
</IfModule>

Add that to your site’s main .htaccess file, change example.com to match your site, and done. You can (and should) test the technique by visiting pages that display your media files. Also try requesting the media files directly. Also you can test hotlinking of your images by using a free online hotlink checker. Test well and please report any bugs, issues, etc.

How it Works

For those interested in how the code works, here is a line-by-line breakdown:

  • Open the <IfModule> container if mod_rewrite exists
  • Make sure the rewrite engine is enabled (see note)
  • Check if the requested URI is for anything in the WP Media Library
  • Check if the referrer matches the site URL
  • If both of the previous conditions are true, the request is denied via 403 “Forbidden” response
  • Close the <IfModule> container

So the technique is very simple and lightweight. Again, remember to change the example.com with the actual URL of the site. Then test well.

Note: It’s okay to have more than one RewriteEngine On rule declared in your .htaccess file. Apache simply ignores once the Rewrite Engine is enabled.

Pros & Cons

There are some pros and cons to techniques like this. First the Pros:

  • Pro — Protects against hotlinking
  • Pro — Prevents direct image access (i.e., no referrer)
  • Pro — Helps to ensure images are display only on your site
  • Pro — Super simple, lightweight and fast

And the Cons:

  • Con — Prevents images from appearing in image search results
  • Con — Prevents direct image access (i.e., no referrer)
  • Con — Experimental, not well tested (as of 2021/11/21)
  • Con — It’s possible for bad actors to fake/spoof the referrer

So it’s a limited use-case scenario, where you want to retain as much control and protection for your images as possible. Notice that some items are on both pro and con lists. This is because whether pro or con depends on your goals, strategy, and so forth. For example, Andy may like to see his images appear in image-search results. While April on the other hand, would rather not.

Also keep in mind that this technique is not 100% guarantee of anything. It is just as strong as other anti-hotlink and image-protect techniques, but could be bypassed by anyone with the ability to spoof a referrer. Spoofing a referrer is one of the oldest tricks in the book, however most requests don’t bother spoofing anything. So the technique generally should be effective.

Related Infos

About the Author
Jeff Starr = Web Developer. Security Specialist. WordPress Buff.
.htaccess made easy: Improve site performance and security.

7 responses to “Protect WordPress Media Files”

  1. Hi Jeff,

    Thank you for this. I like the simplicity of your solution. I’m wondering about something. There’s a WordPress security plugin I like to use, which has a ‘Prevent Image Hotlinking’ feature. Their solution also writes to the .htaccess file and produces a slightly longer piece of code that looks like this:

    #AIOWPS_PREVENT_IMAGE_HOTLINKS_START
    <IfModule>
    RewriteEngine On
    RewriteCond %{HTTP_REFERER} !^$
    RewriteCond %{REQUEST_FILENAME} -f
    RewriteCond %{REQUEST_FILENAME} \.(gif|jpe?g?|png)$ [NC]
    RewriteCond %{HTTP_REFERER} !^http(s)?://(.*)?\.mywebsite\.com [NC]
    RewriteRule \.(gif|jpe?g?|png)$ - [F,NC,L]
    </IfModule>
    #AIOWPS_PREVENT_IMAGE_HOTLINKS_END

    I’m wondering how this code works differently from yours and if it would have different pros and cons.

    Thanks again,

    Dave

  2. Hi Jeff, me again,

    So I decided to use the hotlink checker you referenced to test the security plugin’s code and your code on a live site.

    Turns out the security plugin’s code doesn’t actually stop hotlinking from happening and your code does. So I guess that answers my question about the difference between the two ; )

    At the same time, I’m assuming the developers of the security plugin did test their solution and found it satisfactory, so I’m still curious about your thoughts too…

    • Hey Dave, great to hear from you. I hope you are doing well.

      Not sure what’s up with your security plugin’s .htaccess rules. Looks like the logic is a bit off though. Would need to do some testing to be sure. Best advice would be to ask the plugin providers why it’s not working, they should be able to resolve any logical inconsistencies.

  3. I used it, great:) Something for protecting themes folder and files?

    • Jim S Smith 2021/12/31 9:13 am

      Wasn’t sure what you were specifically referring to as to protecting the “themes” folders, but the general RewriteCond for protecting the media within them should also work there – unless you have another .htaccess file within that/those folder(s) that are “short-circuiting” your upper-level .htaccess file.

      If you are talking about keeping web-access to those files limited to within your site’s resources (IE: called by your web page(s), for instance), You could try:

      RewriteCond %{REQUEST_URI} /themes/.*
      RewriteCond %{HTTP_REFERER} !^http(s)?://(.*)?\.my-website\.com [NC]
      
      # Send them back to your site's homepage then.
      RewriteRule .* / [R=301]

      But I would test it first! If any of your themes use JavaScript to “Request” any other resources within your “theme’s” folder, this may not work as well – as Javascripted “Request events” often do not send a “referrer”-header!

      This bit of directives is to only allow access to the contents of the “themes” folder – if “referred to by” a request from within your webpage(s) on your site.

      IF you are confident that NONE of your themes ever need to have “web-access” (IE: via “HREF” or any “SRC” values) to any of the PHP files in them (which NONE of them should, normally),

      You could try this one:

      RewriteCond %{REQUEST_URI} /themes/.*\.php
      RewriteRule .* / [R=301]
      
      # OR - Perhaps even one-liner like this one?
      RewriteRule /themes/.*\.php / [R=301]

      Again, TEST them one-at-a-time first, before committing them to your live site(s).

  4. Jim S Smith 2021/12/26 9:49 am

    I actually use several sets of Rewrites to further protect my media and the folders in general. THIS kind became necessary because of other nefarious attempts of attacks too. First off, I try to reduce how many lines in the .htaccess I use to get the job done:

    # NO, you may NOT have my images!
    RewriteCond %{HTTP_REFERER} !^http(s)?://(.*)?\.my-website\.com [NC]
    
    RewriteRule \.(gif|jpe?g?|png)$ - [F]
    
    # NO, you may NOT try to access prohibited files or use prohibited methods!
    
    RewriteCond %{REQUEST_METHOD} !(HEAD|GET) [NC,OR]
    RewriteCond %{HTTP:Content-Disposition} \.(php.?|s?pl|shl?|s?html?|(f|s)?cgi|pyc?|aspx?|jspx?) [NC]
    
    RewriteRule /wp-content/uploads/.* - [F]

    ( The last RewriteCond should work well, even if you didn’t set the MIME-type restrictions within WordPress – but it is recommended that you use the WordPress feature at a minimum. )

    As I always caution:

    TEST everything, individually, before applying it to your “live site(s)”.

    – J.S.Smith

  5. Nice hack, Jeff! I used it and it works nicely. Thanks for sharing

Comments are closed for this post. Something to add? Let me know.
Welcome
Perishable Press is operated by Jeff Starr, a professional web developer and book author with two decades of experience. Here you will find posts about web development, WordPress, security, and more »
Wizard’s SQL for WordPress: Over 300+ recipes! Check the Demo »
Thoughts
I live right next door to the absolute loudest car in town. And the owner loves to drive it.
8G Firewall now out of beta testing, ready for use on production sites.
It's all about that ad revenue baby.
Note to self: encrypting 500 GB of data on my iMac takes around 8 hours.
Getting back into things after a bit of a break. Currently 7° F outside. Chillz.
2024 is going to make 2020 look like a vacation. Prepare accordingly.
First snow of the year :)
Newsletter
Get news, updates, deals & tips via email.
Email kept private. Easy unsubscribe anytime.