See the official online handbook for more information about securing private files. The information about private files starts at the "Managing file locations and access" header.

If you set "public" as the download method, you can still protect some of your folders by settings in your .htaccess file, if you have mod_rewrite enabled.

For instance, if your files live in sites/default/files, and you want to protect everything in sites/default/files/protected_download_dir, then you can add the following line to your central .htaccess file:

RewriteRule ^sites\/default\/files\/(protected_download_dir\/.*)$ index.php?q=system/files/$1 [L,QSA]

The files in this folder (or, all files that match the regular expression) will not be served directly by apache, but by a full drupal request using the file_download() callback. The routing for system/files is defined in system_menu().

It is recommended to force the browser to download the file instead of displaying the file. If you for instance offer a protected .jpg for download, the browser will try to open it in the browser which will result in a 404 (which means access denied in this case).
You can use the File Force module which gives the option to select a "file force" formatter for displaying the file which will make sure it downloads or use the or Download file module which takes a different approach. Or you could also force this from within .htaccess.

.htaccess troubleshooting

There are some pitfalls when writing the htaccess file: you need to write correct regex syntax, you need to escape things like slashes ("\/") and dots ("\."), you need linebreaks between different rules, you might need specific RewriteCond statements with each rule, you need the correct apache configuration, etc.

If you have something to share about your htaccess configuration, please do so!

Access Checking for File Downloads

Modules can do access checking on files downloaded through file_download(), by implementing hook_file_download().

For instance, the filefield module will do access checking for all files that you upload via filefield. It will only give access to people who are allowed to see the node and the field the file belongs to.

In addition, you can look for modules that restrict file downloads based on the folder name or other criteria.

Access Checking built-in with Filefield

The filefield module has access checking built-in. To make use of this, please

  1. Configure the filefield you want to protect. Use the filefield path module, to define where the files for the filefield should be stored by default.
  2. Add a line to your .htaccess to protect this folder (see above).
  3. Use whatever modules to restrict node or field access.

Files Attached to Nodes

If you want to protect files uploaded with the core upload module, have a look at the Private Upload module.

Folder-specific Permissions

As an alternative, you can restrict folder access based on the Drupal permission system, introducing one permission per folder. Have a look at

Typical Slip-Through Candidates

If you want to protect your files, you should think of the following situations where a file might remain unprotected:

  • Thumbnail images (for instance, those generated with imagecache or imagefield) typically live in a different directory than the original. If you want to protect these as well, you need to add new rules to your .htaccess file. You might even have to install additional modules for the access checking.
  • Import directories. If you use an importer to add your files, it can happen that a copy of the file is still in the import directory. Solution is to either manually clean up the import directory (via FTP), or to protect it (using a .htaccess rule).
  • Orphan files. Sometimes when you delete a node with a filefield, the file will still be there, but it will no longer be associated with any filefield node. This can happen if your filesystem permissions don't allow deleting files. As a consequence, filefield's access checking will no longer apply to this file. You need to find (or code) a new module to protect these files, or you have to clean up manually.

Comments

jgoldbach’s picture

If it helps anyone, I got the following to work pretty nicely (D6.15 on Linux)

sites/default/.htaccess

<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule ^files/books/(private\/.*)$ /index.php?q=system/files/books/$1
</IfModule>

This keeps the rewrite out of Drupal's .htaccess file to minimize the risk of overwriting it during upgrade. There was no .htaccess in the default directory so this seemed a logical spot.

Putting it in the files directory (or lower) for most installs would mean that the server could write to the file which may compromise things a bit.

Not sure of the IfModule statement is necessary, nor if the RewriteEngine on is necessary but they didn't seem to hurt or generate a warning. Also, for those of you having problems with rewriting in general, the rewrite log directive is invaluable.

http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html#rewritelog

metastate’s picture

Adding the rewrite rule described on this page to your main htaccess file works great with CCK FileField. Couple things to keep in mind when setting this up though:

1) Be sure to clear your browser cache after adding the rewrite rule to your htaccess file. Depending on the browser, refreshing the page might not load the updated htaccess file. Clear your browser's cache and reload the page before moving on to any other troubleshooting.

2) If you are using a multi-site installation you will need to escape out any periods ( . ) in the file path. To do this, add a \ before each period in the folder name. For example, the rewrite rule for a multi-site installation would look like this:

RewriteRule ^sites\/example\.com\/files\/(protected_download_dir\/.*)$ index.php?q=system/files/$1 [L,QSA]

Change "example" and "com" to match your multi-site folder name and "protected_download_dir" to match the folder name in your files folder where you are uploading the files to.

Also, you want to be careful where you place the rewrite rule in your htaccess file. I always place my custom rewrites near the end of the file, but before this line:

# Rewrite URLs of the form 'x' to the form 'index.php?q=x'.

marcoka’s picture

if i put:

RewriteRule ^sites\/default\/files\/(privatefiles\/.*)$ index.php?q=system/files/$1 [L,QSA]

in my .htacces in the drupal root, i still can access all files like:

http://192.168.1.238/WORKSPACE_DRUPAL/testsite_public/sites/default/file...

SOLUTION:

I run drupal on my local dev server 192.168.1.238 and installed drupal in the dir:

http://192.168.1.238/WORKSPACE_DRUPAL/drupal_testsite_public

Meaning that the apache public html is http://192.168.1.238

so if you use something like the following, it does not work:


RewriteEngine on
RewriteBase /system/files/privatefiles
RewriteRule ^(.*)$ $1 [L,R=301]

you need to put the following in there to work:

RewriteBase /WORKSPACE_DRUPAL/drupal_testsite_public/system/files/privatefiles

Coupon Code Swap’s picture

RewriteRule ^sites\/default\/files\/(protected_download_dir\/.*)$ index.php?q=system/files/$1 [L,QSA]

Is it possible to adjust this code so that it works recursively on all subdirectories that contain files in the sites/default/files/ folder?

trentharlem’s picture

Adding password protection to a directory using .htaccess takes two stages. The first part is to add the appropriate lines to your .htaccess file in the directory you would like to protect. Everything below this directory will be password protected:

AuthName "Section Name"
AuthType Basic
AuthUserFile /full/path/to/.htpasswd
Require valid-user

There are a few parts of this which you will need to change for your site. You should replace "Section Name" with the name of the part of the site you are protecting e.g. "Members Area".

The /full/parth/to/.htpasswd should be changed to reflect the full server path to the .htpasswd file (more on this later). If you do not know what the full path to your webspace is, check your Hostmonster cPanel. Look on the left "stats" column of the cPanel.

The .htpasswd File

Password protecting a directory takes a little more work than any of the other .htaccess functions because you must also create a file to contain the usernames and passwords which are allowed to access the site. These should be placed in a file which (by default) should be called .htpasswd. Like the .htaccess file, this is a file with no name and an 8 letter extension. This can be placed anywhere within you website (as the passwords are encrypted) but it is advisable to store it outside the web root (in your home directory) so that it is impossible to access it from the web.

Entering Usernames And Passwords

Once you have created your .htpasswd file (you can do this in a standard text editor) you must enter the usernames and passwords to access the site. They should be entered as follows:

username:password

where the password is the encrypted format of the password. To encrypt the password you will either need to use one of the pre-made scripts available on the web or write your own. There is a good username/password service at the KxS site (http://www.kxs.net/support/htaccess_pw.html) which will allow you to enter the user name and password and will output it in the correct format.

For multiple users, just add extra lines to your .htpasswd file in the same format as the first. There are even scripts available for free which will manage the .htpasswd file and will allow automatic adding/removing of users etc.

Accessing The Site

When you try to access a site which has been protected by .htaccess your browser will pop up a standard username/password dialog box. If you don't like this, there are certain scripts available which allow you to embed a username/password box in a website to do the authentication. You can also send the username and password (unencrypted) in the URL as follows:

http://username:password@www.website.com/directory/

jvdurme’s picture

Hi all, I have followed all the steps but can't make it work. Here's what I did (drupal 6):

1. rewrite engine of apache works (clean urls work fine)
2. added to /var/www/drupal/sites/default/files/.htaccess the line:

RewriteRule ^sites\/default\/files\/(music\/.*)$ index.php?q=system/files/$1 [L,QSA]

3. restart apache, clear browser cache
4. uploaded a file with filefield and downloaded it (as administrator)
5. go to the download counter page (http://localhost/drupal/download_counter), but no downloads are recorded

I then tried by making a new .htaccess file in the root folder /var/www/drupal/.htaccess and put the line in there, but also no luck.

I'm a bit out of ideas here. Anyone has more info?

Thanks in advance!

Joost

EDIT: nevermind, I use private file system now with 6.x-2.x module.

Wakken!

Fannon’s picture

I've had a hard time to get this to work.
So I would like to share how i found my way in the struggle of URL Rewrites :)

Those 2 Tutorials gave me a good overview how the URL Rewriting works:
http://www.easymodrewrite.com/guide-regular-expressions
http://www.easymodrewrite.com/guide-syntax

Goal: To protect all Files in /sites/default/files/podcast/audio/************** and some other directories.
The reason behind this was to get the Download Count Module working with a public filesystem.

The Rewrite should go this way:
/sites/default/files/podcast/audio/example.mp3 -> /system/files/podcast/audio/example.mp3

I've placed my .htacces file in the /sites/default/ folder. This way it wont get overwritten so easily.
The .htaccess looks now like this:

<IfModule mod_rewrite.c>
RewriteEngine on
RewriteBase /

RewriteRule ^files/(beitrag/medien/.*)$ /system/files/$1
RewriteRule ^files/(beitrag/media/.*)$ /system/files/$1
RewriteRule ^files/(podcast/audio/.*)$ /system/files/$1
RewriteRule ^files/(podcast/anhang/.*)$ /system/files/$1
</IfModule>

I hope this helps other people to get this working.

Greets,
Simon

Fannon’s picture

Strangely the Rewrites didn't work for all folders (for one it did) aslong I had escaped "/" like this: sites\/default\/
Adjusting this to sites/default/ did solve the problem for me.

GaryWong’s picture

Agreed.. same for me on my OSX box. Hmm, I'll have to go back and check all my OTHER mod_rewrites rules now on *nix/windows/etc...

thanks for this great thread!
gary

Sutharsan’s picture

Drupal 7 takes care of the .htaccess automatically. When you define a private files directory a .htaccess file is created with:

SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
Deny from all
Options None

-- Erik

j4’s picture

Hi Sutharsan,

If this denies the files for all, how does the admin get to download the files?
Sorry if this is an obvious question!

Thanks
Jaya

Jaya

donquixote’s picture

The file is served via php then, instead of apache doing it directly.

ñull’s picture

I want to upgrade from Drupal 6. How would I use the same system with .htaccess redirect in Drupal 7?

bmanbeck’s picture

This worked great for me on a shared server at Bluehost, but I just moved to a dedicated server at Verio and it doesn't work anymore. Any idea what Apache settings need to be changed to get this working again like it did at Bluehost shared?

MattMoody’s picture

I need give user a link to download one file from private directory. How can I do this? I think to copy needed file and give user link with some key (example: http://sitename.com/system/files/file.zip?key=e9ej382ej3dfls) Please give me a hint where to look info.

Dret’s picture

Hi to all!

I,m trying to find a solution to restrict the Webform file submission (without using module dedicated due to is no longer supported and updated).

I try with a custom module used for File field (here: http://www.drupalcoder.com/blog/mixing-private-and-public-downloads-in-d... ) but result is:

1) Files are protected as well (Anonymous get a Access denied)
2) But with correct permission, nobody can download the (registred user get a Page Not Found)

Any Ideas about?

Thanks!

metastate’s picture

Last time I looked into this, the only solution I found for Webform on a Drupal 6 site was to switch to the private file system for the whole site. Pretty sure nothing else works.

Looks like there is a patch if you are using Drupal 7 - Webform: Support private file fields in Drupal 7

jvieille’s picture

I am struggling with this and cannot find a solution. Webform is a sort of second Drupal, with different, mostly incompatible design...

Using

RewriteRule ^sites\/default\/files\/(protected_download_dir\/.*)$ index.php?q=system/files/$1 [L,QSA]

gives me the best for the time being : files are correctly protected from url direct download and still visible in Drupal for users with the appropriate permissions.
However, for this to work,
1) use the multifile component
2) the rewrite must not be set at the time of the submission, and then be enabled afterwards.

If the rewrite is effective, at the time of the upload, the new files are not visible to anyone.
The records in the file table look exactly the same in both situations.

JV

roynilanjan’s picture

Approach can be,

RewriteRule ^sites\/default\/files\/(protected_download_dir\/.*)$ index.php?q=system/files/$1 [L,QSA]
After that permanent redirection , we can implement in such a way to apply access protection


function hook_menu_alter(&$items) {
  $items['system/files']['access callback'] = 'callback'; 
}
rkchallengers’s picture

function hook_menu_alter(&$items) {
$items['system/files']['access callback'] = 'callback';
}

Tilottama11’s picture

Thanks roynilanjan and rkchallengers for posting the solution. It worked.