153

I'm gearing up to deploy a Drupal 7 site and I can't find any documentation. What are the recommended security-conscious permissions for files and directories?

I am specifically interested to the default/files/ directory, the settings.php and .htaccess files.

4

10 Answers 10

73

Your web server should be able to read all of the files but not write to them. If your site involves uploading files then give the server permission to write to that one folder only.

More information on how to set that up, as well as some things that can happen if you don't, is available in the Drupal docs.

3
  • 13
    This is not true of the sites/default/files directory, which must be writable by the web server or you will have masses of troubles.
    – rooby
    Apr 25, 2013 at 5:05
  • 3
    @rooby Second sentence clarifies that.
    – kenorb
    Feb 15, 2015 at 16:55
  • 15
    Although it says "If your site involves uploading files" but that is not the only reason you might need write access to the files directory. A very common example is js/css aggregation, which is unrelated users uploading files. Other modules may also expect that directory to be writeable.
    – rooby
    Feb 16, 2015 at 8:00
103

That drupal page like so many is very long and confusing. But it contains this post by Jason, who hit the nail on the head:

Posted by Jason Sale on November 1, 2010 at 12:40pm

Thanks for writing this and everything, but all that I and 99% of people reading this page really want is a list of numbers next to a list of folders.

  • /default on 755
  • /default/files including all subfolders and files on 744 (or 755)
  • /default/themes including all subfolders and files on 755
  • /default/modules including all subfolders and files on 755
  • /default/settings.php and /default/default.settings.php on 444
7
  • 7
    The topic is long and confusing. The page fits the reality of the situation.
    – greggles
    Apr 29, 2013 at 15:05
  • 25
    ... of the Drupal docs! And that's why it needs to be changed to something simple & understandable immediately! Especially when it comes to security. JUST BECAUSE it's about security! Because it belongs to all of us. An infected server is not only a problem of the unexperienced Drupal user. It's a problem of all of us then. So it isn't very helpful to keep up complex & badly written docs inspired by developers narcissm to show off complexity & intricacy and how good theirself can handle it. I don't see the point for leaving out a simple file permissions tree graphic. webchick aggrees on that.
    – nilsun
    Nov 14, 2014 at 1:06
  • 4
    Why have 755 on /default/files? Shouldn't this always be 744 just in case someone manages to upload something malicious by hacking the front end (or through poor configuration)? Is there any reason to have 755? What about /tmp?
    – Webdrips
    Apr 28, 2015 at 22:43
  • 1
    The settings.php file should be 440. "Others" should not be able to see settings.php. 740 if you want to be able to write to it without sudo commands.
    – Bryden
    Dec 17, 2015 at 20:24
  • 1
    just curious why .htaccess file in files and tmp folder are need to be written by webserver? isn't it safe to mark it 444 just as settings.php. Reason I am asking is if junkfile.php can be uploaded by hacker to files folder via webserver then .htaccess file could be replaced. am I right?
    – kiranking
    Oct 15, 2018 at 10:28
86

My practice around creating a new Drupal site on a server is to have a user that is a part of the web server (typically Apache) group, and have that user own all the Drupal files. On Ubuntu, these are the commands to get that set up:

# Create a new example user, setting up /var/www/example as their home dir.
useradd -s /bin/bash -d /var/www/example -m example

# Now add that user to the Apache group. On Ubuntu/Debian this group is usually
# called www-data, on CentOS it's usually apache.
usermod -a -G www-data example

# Set up a password for this user.
passwd example

Once I have that set up, I'll log in as that user and install Drupal at /var/www/example/docroot or similar, and then create the files directory by hand and copy over the settings.php file. Since we log in as our example user before copying in Drupal, our file ownership and permissions should automatically be properly configured on all the core Drupal files and scripts (including .htaccess files).

su - example
cd docroot
cp sites/default/default.settings.php sites/default/settings.php

# Temporarily give the web server write permissions to settings.php
chgrp www-data sites/default/settings.php
chmod g+w sites/default/settings.php

Now let's set up the files directory.

# Create the directory.
mkdir sites/default/files

# Now set the group to the Apache group. -R means recursive, and -v means 
# verbose mode.
chgrp -Rv www-data sites/default/files

Next we'll set up permissions so that the web server can always write to any file that is in this directory. We do this by using 2775 in our chmod command. The 2 means that the group id will be preserved for any new files created in this directory. What that means is that www--data will always be the group on any files, thereby ensuring that web server and the user will both always have write permissions to any new files that are placed in this directory. The first 7 means that the owner (example) can R (Read) W (Write) and X (Execute) any files in here. The second 7 means that group (www-data) can also R W and X any files in this directory. Finally, the 5 means that other users can R and X files, but not write.

 chmod 2775 sites/default/files

If there are any existing files in this directory, be sure the web server has write perms on them.

 chmod g+w -R sites/default/files

Now Drupal is ready to be installed. When finished, it is VERY important to come back to settings.php and ensure that all users only have read permissions.

 chmod 444 sites/default/settings.php

That's it! This set up ensures you avoid any situations where either the user that owns the directory or the web server can't write/change/remove files in the files directory.

2
  • isn't it safe to set .htacess to 444 jsut as settings.php? anyway that file is rarely updated in drupal core.
    – kiranking
    Oct 15, 2018 at 10:33
  • You can set .htaccess to 444, but it will add extra headache when upgrading Drupal core and doesn't make your site any more secure.
    – q0rban
    Oct 16, 2018 at 11:19
35

The Drupal files folder should be writable by the webserver. The safest way to do that is to change the group and make it group writable, like this:

chgrp www-data sites/default/files
chmod g+w sites/default/files

The file upload folder aside, the safest is chmod 644 for all files, 755 for directories.

This could be accomplished like this (when run in the Drupal-site folder, the . is for current path):

find . -type f | xargs chmod 644
find . -type d | xargs chmod 755

Remember that you will need to set chmod g+w again after running the above command, since those will reset the chmod on all files and folders.

3
  • 2
    This answer assumes: you are on ubuntu. Your site is the only one running on that server. It also only solves the problem once instead of making it happen automatically (e.g. by changing the umask).
    – greggles
    Apr 29, 2013 at 15:04
  • Not necessarily Ubuntu, and it's still a helpful measure, even if there's multiple sites running on the same server. Changing the umask (which should hopefully already have these settings) is not something I'd recommend to people not well-versed in Linux. I know this is not perfect security, but let's not let perfect be the enemy of good here.
    – mikl
    Sep 6, 2015 at 17:43
  • On a multisite I run chgrp -R www-data sites/*/files and chmod -R g+w sites/*/files to get rid of the status page errors.
    – leymannx
    Jun 1, 2017 at 11:00
22

Any advice to "chmod blah" or "chown X" is meaningless without knowing: what the default user:group is on the files and what user and groups your webserver runs as.

The Drupal Docs others have linked to are quite good on the topic but one other resource is the Security Review Module which helps ensure you've got everything set properly.

Feature of Security Review Module

  • Safe file system permissions (protecting against arbitrary code execution)
  • Text formats don't allow dangerous tags (protecting against XSS)
  • PHP or Javascript in content (nodes and comments and fields in Drupal 7)
  • Safe error reporting (avoiding information disclosure)
  • Secure private files
  • Only safe upload extensions
  • Large amount of database errors (could be sign of SQLi attempts)
  • Large amount of failed logins (could be sign of brute-force attempts)
  • Responsible Drupal admin permissions (protecting against access misconfiguration)
  • Username as password (protecting against brute-force)
  • Password included in user emails (avoiding information disclosure)
  • PHP execution (protecting against arbitrary code execution)
  • Base URL set / D8 Trusted hosts (protecting against some phishing attempts)
  • Views access controlled (protecting against information disclosure)
11

I will reply considering the case the files are created on the server using FTP, using credentials different from the one under which is the web server runs (normally, Apache runs as nobody/nobody). This means that the user who owns the files manually created before running the Drupal installer (which includes also the files uploaded on the server from the Drupal archive) is not the user used to run the web server (neither the username or the group matches). This scenario applies also to the case where those files are created using SSH.

  • The settings.php file must be writable from the Drupal installer, but once the installation is done it is suggested to make it read-only (the installer suggest that, and Drupal will periodically check if the file is really read-only). In the scenario I am describing, the permission of this file should at least be 644.
  • The .htaccess files (which are present in at least two places) should have the permission 644. The user who created the file should still be able to overwrite the file, in the case a next version of Drupal comes with an .htaccess file that has been updated (it happened already once, when it has been added a line to that file to avoid a security issue). It is also possible to set the permissions to 444, but in that case, the permissions should be changed back to 644 when the file needs to be updated.
  • The directory containing the files created by the modules (the default/files directory) must be (for the user that is assigned to the web server processes, which is then the user assigned to to the PHP scripts running on that web server):
    • readable
    • writable
    • traversable (the modules must be able to reach default/files/<directory-used-by-the-module>/<sub-directory-used-by-the-module>)
0
11

Recommended file/directory permissions:

  • Drupal webroot should be world readable (see: updater.inc): 0755
  • for public upload directories: 0755 or 0775
  • for private upload directories: 0750 or 0770
  • for public uploaded files: 0644 or 0664
  • for private uploaded files: 0640 or 0660
  • for .htaccess within upload directories (see: file_create_htaccess()): 0444 (default) or 0644
  • for settings.php read-only for all (and other confidential files): 0440
  • for all other web directories: 0755
  • for all other web files: 0644

Recommended file/directory ownership:

  • owner of all upload directories/files should be set to Apache user,
  • owner of all web/sources directories/files should be set to non-Apache user,
  • (optionally) group of all sources should be set to Apache group,

Here are the variables which control default dir/file permissions for new items:

file_chmod_directory: 0775
file_chmod_file: 0664

Here is some script for fixing permissions: fix-permissions.sh


Read more:


Here is script which I'm using to fix permissions on remote host for public/private directories:

#!/bin/sh -e
# Script to correct public/private directory and files permissions.
[ -z "$1" ] && { echo Usage: $0 @remote.dst; exit 1; }

DST="$1" && shift
GET_HTTP_GROUP='ps axo user,group,comm | egrep "(apache|httpd)" | grep -v ^root | uniq | cut -d\  -f 1'

drush $* $DST ssh 'PUB=$(drush dd %files) && PRIV=$(drush dd %private) && AGROUP=$('"$GET_HTTP_GROUP"') && chgrp -vR $AGROUP $PUB $PRIV && chmod -vR u+rwX,g+rwX,o+rX $PUB $PRIV'

Note: Above code will try to retrieve Apache group and set it to GET_HTTP_GROUP variable.

1
  • very nice cheat-sheet , bookmarked. Good job..
    – kiranking
    Sep 14, 2016 at 16:29
5

This shell script is found at the bottom of this page: https://www.drupal.org/node/244924

I run it occasionally to ensure that my permissions are set up correctly.

#!/bin/bash
# Help menu
print_help() {
cat <<-HELP
This script is used to fix permissions of a Drupal installation
you need to provide the following arguments:
1) Path to your Drupal installation.
2) Username of the user that you want to give files/directories ownership.
3) HTTPD group name (defaults to www-data for Apache).
Usage: (sudo) bash ${0##*/} --drupal_path=PATH --drupal_user=USER --httpd_group=GROUP
Example: (sudo) bash ${0##*/} --drupal_path=/usr/local/apache2/htdocs --drupal_user=john --httpd_group=www-data
HELP
exit 0
}
if [ $(id -u) != 0 ]; then
  printf "**************************************\n"
  printf "* Error: You must run this with sudo. *\n"
  printf "**************************************\n"
  print_help
  exit 1
fi
drupal_path=${1%/}
drupal_user=${2}
httpd_group="${3:-www-data}"
# Parse Command Line Arguments
while [ $# -gt 0 ]; do
  case "$1" in
    --drupal_path=*)
      drupal_path="${1#*=}"
      ;;
    --drupal_user=*)
      drupal_user="${1#*=}"
      ;;
    --httpd_group=*)
      httpd_group="${1#*=}"
      ;;
    --help) print_help;;
    *)
      printf "***********************************************************\n"
      printf "* Error: Invalid argument, run --help for valid arguments. *\n"
      printf "***********************************************************\n"
      exit 1
  esac
  shift
done
if [ -z "${drupal_path}" ] || [ ! -d "${drupal_path}/sites" ] || [ ! -f "${drupal_path}/core/modules/system/system.module" ] && [ ! -f "${drupal_path}/modules/system/system.module" ]; then
  printf "*********************************************\n"
  printf "* Error: Please provide a valid Drupal path. *\n"
  printf "*********************************************\n"
  print_help
  exit 1
fi
if [ -z "${drupal_user}" ] || [[ $(id -un "${drupal_user}" 2> /dev/null) != "${drupal_user}" ]]; then
  printf "*************************************\n"
  printf "* Error: Please provide a valid user. *\n"
  printf "*************************************\n"
  print_help
  exit 1
fi
cd $drupal_path
printf "Changing ownership of all contents of "${drupal_path}":\n user => "${drupal_user}" \t group => "${httpd_group}"\n"
chown -R ${drupal_user}:${httpd_group} .
printf "Changing permissions of all directories inside "${drupal_path}" to "rwxr-x---"...\n"
find . -type d -exec chmod u=rwx,g=rx,o= '{}' \;
printf "Changing permissions of all files inside "${drupal_path}" to "rw-r-----"...\n"
find . -type f -exec chmod u=rw,g=r,o= '{}' \;
printf "Changing permissions of "files" directories in "${drupal_path}/sites" to "rwxrwx---"...\n"
cd sites
find . -type d -name files -exec chmod ug=rwx,o= '{}' \;
printf "Changing permissions of all files inside all "files" directories in "${drupal_path}/sites" to "rw-rw----"...\n"
printf "Changing permissions of all directories inside all "files" directories in "${drupal_path}/sites" to "rwxrwx---"...\n"
for x in ./*/files; do
    find ${x} -type d -exec chmod ug=rwx,o= '{}' \;
    find ${x} -type f -exec chmod ug=rw,o= '{}' \;
done
echo "Done setting proper permissions on files and directories"
Copy the code above to a file, name it "fix-permissions.sh" and run it as follows:
sudo bash fix-permissions.sh --drupal_path=your/drupal/path --drupal_user=your_user_name

Note: The server group name is assumed "www-data", if it differs use the --httpd_group=GROUP argument.
4

Also if you are running fastcgi, the php runs AS the user, and will have access to all files that the user has access to unless you deliberately try to avoid this.

2

This helped me with my OSX Permission issues. I found it in https://www.drupal.org/node/244924#comment-3741738 by protoplasm user. I was like him having issues after a migration.

[root@localhost]cd /path_to_drupal_installation/sites
[root@localhost]find . -type d -name files -exec chmod ug=rwx,o= '{}' \;
[root@localhost]find . -name files -type d -exec find '{}' -type f \; | while read FILE; do chmod ug=rw,o= "$FILE"; done
[root@localhost]find . -name files -type d -exec find '{}' -type d \; | while read DIR; do chmod ug=rwx,o= "$DIR"; done

Not the answer you're looking for? Browse other questions tagged or ask your own question.