Last updated 7 February 2018. Created on 10 April 2008.
Edited by TynanFox, kiamlaluno, fonant, mrweiner. Log in to edit this page.

Table of Content #

Introduction #

The server file system should be configured so that the web server (e.g. Apache) does not have permission to edit or write the files which it then executes. That is, all of your files should be 'read only' for the Apache process, and owned with write permissions by a separate user.

Note that this whole article is about "defense in depth." Drupal can run quite safely with permissions a little "looser" than they should be. But if an administrator account is compromised by an attacker or an attacker gains the ability to execute arbitrary code then the configuration below will limit their ability to further exploit your site.

Caution: Individual Results will Vary #

Bear in mind that the process of securing files varies depending on individual server configuration. Always consult your server's documentation for further guidance. The methods for securing file permissions described in this guide assume you are a server administrator with root access to the server.

Site administrators using a Shared Hosting provider commonly lack this level of access and will find many of the methods that follow to be inaccessible due to insufficient privileges. Users of Shared hosting providers should consult that provider's documentation or seek professional assistance.

Automated Tools to Set Permissions #

The File permissions module allows you to automatically set up correct file permissions in your Drupal instance. It uses Drush commands. Most importantly it maintains correct permissions on those directories. That module requires a full Root access to your server. Because this is all about permissions. This module will likely work with most hosting suppliers such as dedicated or vps. It would not work on shared hosting without full Root access.

Automated Tools to Verify Permissions #

As a quick test to confirm whether your site is secure or not you can run the Security Review module. However, this module may from time to time report false positives. To avoid being confused by these you need to check the module's issue queue to see if there any open issues that is about false positives.

Configuration examples #

For example, on many systems the Apache process runs as a user called "www-data" that is in a group called "www-data". This user should be able to read all of the files in your Drupal directory either by group permissions or by "other" permissions. It should not have write permissions to the code in your Drupal directory. If you use features of Drupal which require the "files" directory, then give the www-data user the permission to write files only in that directory.

The following is an example file listing of a safe configuration showing two files in a site where uploaded files are stored in the "files" directory. In order to see the file permissions set for your setup, go to the command line and type: ls -al.

drwxrwx---  7 www-data    greg-group  4096 2008-01-18 11:02 files/
drwxr-x--- 32 greg-user   www-data    4096 2008-01-18 11:48 modules/
-rw-r-----  1 greg-user   www-data     873 2007-11-13 15:35 index.php

In the above example, the web server user has the ability to write in the files directory, any users in the group "greg" can read and write the data as well, but other users are not allowed to interact with that data. The "index.php" file (representative of all code files) can be edited by "greg" and can be read by the www-data group (we assume the www-data user is in the www-data group). No other users can read that file. This is a fairly secure method of configuring your site. You generally don't want random users who have the ability to read files on your server to see inside those files, hence the last three permissions are --- instead of r-x.

Below is an insecure setup:

drwxrwx---  7 www-data  www-data  4096 2008-01-18 11:02 files/
drwxrwx--- 32 greg-user www-data  4096 2008-05-16 11:48 modules/
-rw-rw-rw-  1 www-data  www-data   873 2007-11-13 15:35 index.php

This configuration allows the www-data user to edit the index.php file (and since it is representative of other files, we assume every file that makes up the Drupal site). This configuration is dangerous for the reasons outlined below. If you are using a configuration like this, please change it immediately to be more like the first version. Instructions for making the necessary changes are given below for Linux Servers.

Quick lesson in permission's numeric equivalents #

On Unix-like servers (including Linux), permissions for files and directories can be specified using letters (e.g., "r" for read, "w" for write, and "x" for execute bits) or numbers. The numbers 4, 2, and 1 are conversions of these bits from binary, and correspond directly to read, write, and execute. The instructions below use the alphabetic notation because it is easier to read, but you often will see permissions given in numeric notation in the Drupal Handbook and many other How-Tos. Below are examples of the alphabetic notation and the numeric equivalent:

rwx  ==  111 binary  ==  7
r--  ==  100 binary  ==  4
---  ==  000 binary  ==  0

Unix filesystems assign permissions to three different categories of possible users -- owners, members of a security group defined on the server (e.g., using the groupadd command in Linux, the pw command in FreeBSD or the dseditgroup command in OSX), and everyone else. Each of these categories is assigned permissions using the alphabetic or numeric notation explained above via the chmod command.

The permissions for a file or directory can be displayed using the ls -l command. The display shows file or directory permissions in the first column in alphabetic notation. The first character indicates the type of directory item, with a "d" whether the file system entity is a directory; a "-" is shown if the entity is a file. The next three letters show the permissions for the user-owner of the file or directory. The next three letters show permissions for the group-owner of the file or directory. The last three letters show the permissions for everyone who is not the user-owner or a member of the group that owns the file or directory. For example:

drwxr-x--- 10 joe     www-data    4096 Oct 15 14:15 ./
drwxr-xr-x 13 root    root        4096 Oct 11 14:50 ../
-rw-r-----  1 joe     www-data    6553 Aug  1 12:27 authorize.php
-rw-r-----  1 joe     www-data   70700 Aug  1 12:27 CHANGELOG.txt
-rw-r-----  1 joe     www-data    5267 Oct 12 22:47 .htaccess
drwxr-x---  4 joe     www-data    4096 Oct 15 14:23 includes/
-rw-r-----  1 joe     www-data     529 Aug  1 12:27 index.php

So, in the listing above, the index.php file is owned by the user joe and the group www-data (usually the name of the Apache user and group on Debian-based systems). The permissions allow joe to read and write the file but allow users in the www-data group only to read the file (except joe, of course, who has owner permissions).

Addendum on chmod Non-Numeric Permission Notation #

When used in the chmod command, the symbols below have the meanings given:

"+" = add a permission to the ones already assigned
"-" = revoke a given permission maintaining the others already assigned
"=" = ignores the already assigned permissions and assigns the permissions specified
"u" = user
"g" = group
"o" = others
"a" = everybody / all (user, group, others)

For files:

r = read
w = write
x = execute

For directories:
r = list (read directory contents)
w = write
x = can access the directory (i.e., cd to the directory)

Using chmod without numeric values makes it more human-readable. These are some examples of how to use it:

chmod commands and results for a file with permissions rwxrwx--- (770)
chmod human chmod numeric resulting permission
ugo=rwx 777 rwxrwxrwx
u-wx 470 r--rwx---
o+r 774 rwxrwxr--
g-wx,o+r 744 rwxr--r--
u-w,g-wx,o+r 544 r-xr--r--
g=,o=r 704 rwx---r--
a-wx 440 r--r-----

Note: Because of the way Unix filesystems work, all files and directories created by the Apache server will be created with an owner that is the same user as is running httpd. In Linux, you can handle this with a useful tool called fsniper. It uses iNotify to check for newly created/modified directories and files and you can apply whatever action you want through customized scripts on them in real time. So you can create scripts to change ownership of files and directories automatically. This solution doesn't work (and isn't required) in Microsoft Windows because of NTFS "permission inheritance".

TIP: The following command may also deal with different 'x' for files and dirs:

[root@localhost]chmod go+rX

The uppercase X will add the execute permission only to directories leaving regular files untouched. Both files and dirs would get read permission added.

How insecure permissions are a problem #

If you allow your site to modify the files which form the code running your site, you make it much easier for someone to take over your server.

Worst case scenario: a file upload tool in Drupal allows users to upload a file with any name and any contents. This allows a user to upload a mail relay PHP script to your site, which they can place wherever they want to turn your server into a machine to forward unsolicited commercial email. This script could also be used to read every email address out of your database, or other personal information.

Undesirable scenario: if the malicious user can upload a file with any name but not control the contents, then they could easily upload a file which overwrites your index.php (or another critical file) and breaks your site.

Undesirable scenario: if the code allows users to see the contents of files, attackers could see information which might reveal potential attack vectors.

If you are a sysadmin #

Disclaimer: Don't risk following these instructions blindly; each system has its own peculiarities and, because of that, the instructions here MUST be altered to suit your needs. All of the instructions here are aimed at people who are familiar with filesystem permissions and know exactly what all the commands written here mean. If you try to follow these instructions without paying full attention to what you are doing, you are very likely to get into trouble. These instructions are designed to alter filesystem permissions as a root-level user, so tread carefully!

Note for hosted Drupal installations #

This installation method presumes one shared drupal core and many subdomains installed under it. So suppose you are the owner of a hosting service that has a Drupal installation already pre-configured and you want to sell a hosted Drupal site with pre-configured Drupal core to your clients. So when you sell it, you create their user on the server and let them configure their own Drupal site just entering their site's address to open the install page. The way this document suggests the configuration, it will prevent customers from modifying and accessing the Drupal core files and other customer's sites files and directories.

It is important to notice that the user ownership of Drupal's core directories/subdirectories and files is given to the user who administers Drupal (usually root) and group ownership is given to the group your apache is running on. For files and directories inside the "sites" directory the user who is hosting the site on your server is their owner. One way to do this is to delete the "sites" directory under Drupal's root directory and make it a symbolic link to /home or another path you use to store user's home directories. That way, if you create user names that matches the customer's site URL, no permission will need to be changed. The only thing to be done is to change the directory group ownership to the group your apache is running on.

cd /path_to_drupal_installation
mv sites/* /home
rmdir sites
ln -s /home sites
chown -R /home/

The permission required by Apache is given through group permission and others have no access at all to any files and directories on a Drupal installation. Don't give any permissions to "others", otherwise if your system is hacked through a user's weak password, the hacker will be able to access all files and directories of all installed sites on your server, not only the one invaded. Even a read-only permission must be avoided. Remember that the user name and password to connect to the database for each site are stored in settings.php. Worse still, if you give write permission to others, the hacker will be able to alter files to damage your site or upload malicious scripts to your server.

The instructions in this guide assume a non-hosted installation, so modify the ownership to match your situation as necessary, e.g. where you see "greg" in these instructions, replace "greg" with the name of the user (often root, but not necessarily) who administers your installation of Drupal.

Linux servers #

Permissions for files and directories on a Linux system are adjusted using the chmod command. User-owner and group-owner identity for files and directories are adjusted using the chown command.

The code below demonstrates one method for changing the ownership and permissions of files and directories in the Drupal Root directory to confirm ownership and permissions to the recommendations above. We assume in the example below that the user greg is part of the greg group and that user greg is the site owner. We also assume that you are running Drupal on a server that is not in a hosting environment which provides website hosting services to multiple customers.

Make sure you run the following commands from inside Drupal's root directory! If you run these commands from any other directory, you either will not make changes to all of the Drupal installation's files and directories or you will make changes to files and directories other than those in the Drupal installation. Neither alternative is your goal.

[root@localhost]cd /path_to_drupal_installation
[root@localhost]chown -R greg:www-data .
[root@localhost]find . -type d -exec chmod u=rwx,g=rx,o= '{}' \;
[root@localhost]find . -type f -exec chmod u=rw,g=r,o= '{}' \;

Note: Within a CentOS environment, you may need to use the apache group instead of the www-data group, which will change your second line to:
[root@localhost]chown -R greg:apache .

The second command makes user greg the user-owner and group www-data the group-owner of all files and directories in Drupal's root directory and all subdirectories and files in those subdirectories (the -R switch means recursive). Note that in a multiple-customer hosting environment, the user-owner of the Drupal files and directories should be root.

The third command in the example finds all directories and subdirectories in Drupal's root directory and executes the chmod command on all of those directories and subdirectories (-type d means filesystem entities that are directories). The command changes the permissions to read, write and access for user greg and read and access for users in the www-data group. Users who are not greg and not in the www-data group cannot read, write, or access the directories or subdirectories in the Drupal root directory. In numeric notation, the permission assigned to these directories and subdirectories is 750.

The fourth command finds all files in the Drupal root directory and its subdirectories and changes the permissions on those files to read and write for the user greg and read only for the www-data group. Other users have no access to these files. The numeric notation for this set of permissions is 640.

For the "files" directory in the sites/default directory and any other site directories in a multi-site installation, the permissions are slightly different because the www-data user must have write permission to the directory:

[root@localhost]cd /path_to_drupal_installation/sites
[root@localhost]find . -type d -name files -exec chmod ug=rwx,o= '{}' \;
[root@localhost]for d in ./*/files
   find $d -type d -exec chmod ug=rwx,o= '{}' \;
   find $d -type f -exec chmod ug=rw,o= '{}' \;

The second command above finds all subdirectories named files below the sites directory and changes the permissions for the user-owner and the group-owner to read, write, and access. All other users cannot read, write to, or access these files subdirectories.

The "for" loop above is written for an sh-style shell (sh, bash,ksh). If you use csh or tcsh, type bash before executing the command. These commands in the loop give read, write, and access permissions to user greg and group www-data to all subdirectories and files within the files but not access to other users. The numeric permissions code is 770.

Remember that any newly installed module/theme or whatever add-on must have its permissions changed too. It's better to do this BEFORE installing the module, theme, or add-on in its appropriate Drupal directory.

Windows servers #

By default, Apache runs in the built in SYSTEM account. So all you have to do is change the permission in a way that only the SYSTEM account, the administrators group and the user greg have access to the Drupal root directory, subdirectories and files (assuming greg is the site owner).

You should exclude all other users and groups from the ACL. For the SYSTEM account give read only access to all Drupal directories, subdirectories and files except for the "files" directories which require write permission. For the user greg give read, modify and write permissions. And for the administrators group give complete access. Go to the Drupal root directory, right click on it, go to properties and then security.

Make use of permission inheritance to make things easier for yourself. And remember that any newly installed module/theme or add on must have its permissions changed too. It's better to do this BEFORE installing it in its appropriate Drupal directory. Permission inheritance is done automatically by Windows.

Special considerations for settings.php #

The settings.php file contains the database password and username in plain text and, once created, must be set so that only appropriate users can read it. That usually means removing read permissions for the "other" user.

Summarizing the permissions #

  • drupal_admin: the user on the server that administrates Drupal, not necessarily is the root.
  • site_admin: the owner of the hosted site (a customer)

Ownership #

Core modules/themes files and directories: drupal_admin:www-data
Hosted sites modules/themes/files files and directories: site_admin:www-data

Permissions #

Core modules/themes directories: rwxr-x---
Core modules/themes files: rw-r-----
Hosted sites modules/themes directories: rwxr-x---
Hosted sites modules/themes files: rw-r-----
Hosted sites "files" directory: rwxrwx---
Hosted sites files under "files" directories: rw-rw----
Hosted sites subdirectories under "files" directories: rwxrwx---

Follow this guide exactly as it is to make your Drupal installation as secure as possible. This guide was tested and works. If something goes wrong with your installation, review the steps — possibly you missed something. If it really doesn't work, post a comment with your issue, and someone will fix this guide.

Script based on guidelines given above #

If you need to fix permissions repeatedly then the following script will help you, it is based on the guidelines given above and performs some checks before any modification to ensure it is not applied on files/directories outside your Drupal installation.


# 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
exit 0

if [ $(id -u) != 0 ]; then
  printf "**************************************\n"
  printf "* Error: You must run this with sudo or root*\n"
  printf "**************************************\n"
  exit 1


# Parse Command Line Arguments
while [ "$#" -gt 0 ]; do
  case "$1" in
    --help) print_help;;
      printf "***********************************************************\n"
      printf "* Error: Invalid argument, run --help for valid arguments. *\n"
      printf "***********************************************************\n"
      exit 1

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"
  exit 1

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"
  exit 1

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= '{}' \;
echo "Done setting proper permissions on files and directories"

Copy the code above to a file, name it "" and run it as follows:
sudo bash --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.

If you have sufficient privileges on your server

  1. Place the file in /usr/local/bin
  2. sudo chown root:root /usr/local/bin/
  3. sudo vi /etc/sudoers.d/fix-permissions and enter the following line in the file
  4. user1, user2 ALL = (root) NOPASSWD: /usr/local/bin/
  5. Save the file and then sudo chmod 0440 /etc/sudoers.d/fix-permissions

Note: Substitute your desired comma separated list of users where you see user1, user2 above. Alternatively, you could enter an ALIAS for a user list. Run man sudoers for more information on formatting the line.

What the /etc/sudoers.d/fix-permissions accomplishes is making the script available to a set of users via the sudo command without having to enter a password.

Assuming that /usr/local/bin is in the user's path (and it should be), then the script can be run from anywhere using

sudo \
--drupal_path=/path/to/the/drupal/install \


Note: The "\" is being used here to help avoid bad line breaks in the display of the page.

See also:

- What are the recommended directory permissions in Drupal 7?

Looking for support? Visit the forums, or join #drupal-support in IRC.


Mike_Waters’s picture

re: find . -type d -name files -exec chmod ug+rwx,o-rwx {} \;

This only works for a new site (e.g. one that doesn't already have files in the /sites/[site]/files directory).
For the hardening of an existing site,`find . -type f -exec chmod u+r-wx,g+rw-x,o-rwx {} \;` sets all of the files inside sites/[site]/files to be read-only, and `find . -type d -name files -exec chmod ug+rwx,o-rwx {} \;` will only affect new files; anything currently existing in the /files folder will still be read-only. A `chmod -R ug+rwx,o-rwx /[path to site]/files/*` should fix this.

Thanks for the howto.

ridgerunner’s picture

chmod -R ug+rwx,o-rwx /[path to site]/files/*

This command gives execute permissions for both the owner and group to all regular files residing within the /[path to site]/files/* tree? Isn't this a bad thing?

jenlampton’s picture

I believe Drupal itself prevents files in the /files/ directory from being executable.

dman’s picture

The 'execute' (x) permission flag at the filesystem level is for allowing scripts and binaries to be processes that run on the server shell etc.
While the Apache rule in the .htaccess in /files/ (provided by Drupal) only tells the webserver not to evaluate PHP files in that directory as PHP.

These are different rules with different meanings. And yeah, you really do not want 'x' permissions on (any) web files, as it's just a mistake.

akishankar’s picture

Well, if you want to restrict the permissions to directory only please use capital x i.e 'X',

imoghis’s picture

I went into my ftp and changed permission as your recommendation and it created more problems. Please advise.

Warning: filemtime(): stat failed for themes/stark/ in _system_rebuild_theme_data() (line 2551 of /var/www/html/prod/modules/system/system.module).
Warning: filemtime(): stat failed for themes/garland/ in _system_rebuild_theme_data() (line 2551 of /var/www/html/prod/modules/system/system.module).
Warning: filemtime(): stat failed for themes/bartik/ in _system_rebuild_theme_data() (line 2551 of /var/www/html/prod/modules/system/system.module).
Warning: filemtime(): stat failed for themes/seven/ in _system_rebuild_theme_data() (line 2551 of /var/www/html/prod/modules/system/system.module).
Notice: Undefined property: stdClass::$owner in _drupal_theme_initialize() (line 209 of /var/www/html/prod/includes/
Warning: include_once(/var/www/html/prod): failed to open stream: Permission denied in _drupal_theme_initialize() (line 209 of /var/www/html/prod/includes/
Warning: include_once(): Failed opening '/var/www/html/prod/' for inclusion (include_path='.:/usr/share/pear:/usr/share/php') in _drupal_theme_initialize() (line 209 of /var/www/html/prod/includes/

Kami Petersen’s picture

It's nuts that web server should own the site. In all normal cases your regular user (that you do your regular ssh, scp or ftp access with) should should own the installation files.

Anyway, this regular user should normally own an html area exclusively, from which the web server serves the pages of the host/virtualhost, that is only readable and searchable to others (i.e. the web server). After ensuring that this is the case the user then normally copies or uploads the contents of the distribution into the html area, thus maintaining ownership and sane permissions. The user then grant the web server read, write and search permission to the files directory of the site by changing the group access. Optionally, the user also gives up his own write permissions for the files directory (in order not to tamper recklessly with this area as it should be managed exclusively by the web server).

There's really no need for recipes, if the user doesn't grasp this he simply needs to do UNIX permission homework or else he'll never be secure. If that is the case, enter the following into shell or Google:

man chown
man chgrp
man chmod

Mike_Waters’s picture

Thanks for the advice. After reading your post, I found the following relevant information:

"Apache runs as the user specified in the User directive. Files need to be readable by that user. Directories need to be +x (searchable) by that user. CGI programs need to be runnable (+x) by that user."

lsrzj’s picture

User apache is completely unpriviledged and has no login shell. A question, supposing that the file index.php in drupal's root have these file permissions: -r--r----- 1 apache apache 980 Mai 18 15:25 index.php. Would it be possible to an attacker explore an Apache vunerability to take advantage of the ownership by apache to execute a chmod on it and alter the permission in a way that it becomes writeable and then modify or overwrite it uploading a hacked one?

Kami Petersen’s picture

Shell or no shell, as long as the user the server and the php scripts runs as owns files and directories, it can also alter them. Owning files is a privilege. So if your server, or php, or Drupal has a hole, or most likely, if you accidentally allow someone to plant some php code, you're in trouble.

$ ls -l secretfile
---------- 1 www-data www-data 0 2009-08-20 21:10 secretfile
$ wget http://localhost/malign.php > /dev/null 2>&1
$ ls -l secretfile
-rwxrwxrwx 1 www-data www-data 0 2009-08-20 21:10 secretfile
lsrzj’s picture

Thank you for your clarification, when you told about this on the comment I became worried because I didn't think of this possibility. Now that you explained how it can be achieved I saw the problem and modified all my permissions in the server to u=rwx,g=rx,o= for directories and u=rw,g=r,o= for files and the ownership is site_owner:apache. This way only site_owner can chmod the files and directories.

kudak’s picture

Hi, permissions above look pretty tight. However it doesn't work ... at least for me (Ubuntu 8.04 x64 serv), I don't know which files have to be writable, executable etc ... but with these permissions I am getting only "Forbidden - u don't have permissions ....." to whole drupal subdirectory, when I set drupal directories to "chmod 1755" and files "chmod 1644" it's much better (perhaps sticky bit is not necessary), i can work in any case at it's very important for me (newbie in drupal). I would like to have secure site at the same time, therefore I tried permissions here from "wiki", i have experimented with them about 2 hours and ended up with working ones as I mentioned above. I haven't tried to edit .htaccess, perhaps there is also some possiblility to achieve usefull behavior.

as mike_waters points out directories need to be executable (x) so one has to # find . -type d -exec chmod u=rwx,g=rx,o=x {} \; this fix the problem, I should have read carefully his post. It could save me 3 hours of trying and googling ...

lsrzj’s picture

If you have read those 3 commands on the beginning of this howto carefuly you should have seen that for directories it has an x to permit access.

[root@localhost]cd /path_to_drupal_installation
[root@localhost]chown -R greg:www-data .
[root@localhost]find . -type d -exec chmod u=rwx,g=rx,o= {} \;
[root@localhost]find . -type f -exec chmod u=rw,g=r,o= {} \;

almalino’s picture

I needed to modify thos commands to
find . -type d -exec chmod u=rwx,g=rx,o=rx {} \; - that's 755
find . -type f -exec chmod u=rw,g=r,o=r {} \; - that's 644

Otherwise my Bluehost hosted Drupal site shows Forbidden when I try to access it.

rickoshay’s picture

I am using Hostgator and the documented code broke my site. Being a newbie I panicked because I didn't know what files and folders I just changed. Luckily, I found this post and was back up and running.

It is important to note that I could not execute the first line:
chown -R greg:www-data .

I omitted it when after realizing that chown was not permitted and gave me an invalid user error. Perhaps this is part of my problem.

Small side note, I experimented with:
find . -type d -exec chmod u=rwx,g=rx,o=r {} \;

and my site still worked but had no styling. I suppose there is a use for that down the road.

lsrzj’s picture

Hey, it's wrong giving to others any kind of permission and the user greg, if you read carefully, was used as an example and you MUST change it for the proper user that administrates Drupal(usually root or your user). Look at this: "To set the permissions all at once you can issue these commands from the Drupal root directory. I'm assuming that greg user is part of greg group and that greg is the site owner." It´s just an assumption just to explain a situation not an obligation. READ CAREFULLY AND REPEAT THE STEPS, YOU SURELY MISSED SOME STEP!

This documentation is for site administrators, newbies must know deeply about Linux permissions scheme before using this guide.

billyverde’s picture

Awesome, these two lines are just what I needed to get my site running up at GoDaddy. Sheesh, what a frustrating week it's been. Thanks!

elizabethcb’s picture

ACLs are an under-used feature of Linux. It's great for web development, as the server can have one set of permissions while the developers have their own. However, I don't think Drupal supports this, because it thinks that the server has write permissions to a directory that it doesn't have write permissions to.

Being brand new to Drupal, I could be wrong, but I believe I'm interpreting the example below correctly.

Status Report:
Configuration file	Not protected
The directory sites/default is not protected from modifications and poses a security risk. You must change the directory's permissions to be non-writable. 

Directory Permissions:
elizabeth:/home/www/$ ll
total 8.0K
drwxrwx---+ 2 elizabeth staff 4.0K 2009-09-16 12:40 all
drwxrwx---+ 3 elizabeth staff 4.0K 2009-11-17 15:27 default

elizabeth:/home/www/$ getfacl default/
# file: default/
# owner: elizabeth
# group: staff

elizabeth:/home/www/webroot/sites/default$ ll
total 28K
-rw-rw----+ 1 elizabeth elizabeth 9.3K 2009-11-17 15:27 default.settings.php
drwxrwx---+ 2 elizabeth staff     4.0K 2009-11-17 15:26 files
-r--r-----+ 1 elizabeth staff     9.3K 2009-11-17 15:27 settings.php

elizabeth:/home/www/webroot/sites/default$ getfacl files/
# file: files/
# owner: elizabeth
# group: staff
scor’s picture

Now for the "files" directories the permissions are slightly different because it must have write permission to the www-data group too [run the following from the Drupal root folder]:
find . -type d -name files -exec chmod ug=rwx,o= {} \;

This command is not so secure because some contrib modules contain 'files' directories such as feedapi, and in core we now have ./modules/simpletest/files

Also, it does not cover the case where you have existing files in it the 'files' directory. I can't find a better way other than running the following commands from each sites/SITENAME/files directory:

cd /sites/SITENAME/files
find . -type d -exec chmod ug=rwx,o= {} \;
find . -type f -exec chmod ug=rw,o= {} \;

I'm happy to update the documentation with this, but maybe someone has a better way of doing this?

amontero’s picture

I've added an uppercase x execute permission tip that applies only to directories.

two_wheeler’s picture

Many of us work through FTP programs, and not the command line.

Could you please post info on the numeric values corresponding to the CHMOD equivalents you listed here?


camhoward’s picture

Like two_wheeler, I need to use the numeric values.

As I read (and re-read) the instructions in this document under Linux servers, and combine that with the information from Mike_Waters, I think this is what they mean.

For all directories and subdirectories in Drupal's root directory:
Symbolic notation: rwxr-xr-x
Octal numbers: 755
Which means:
  • Owner: read, write, execute
  • Group: read, execute
  • Others: read, execute
For all files inside Drupal's directories:
Symbolic notation: rw-r--r--
Octal numbers: 644
Which means:
  • Owner: read, write
  • Group: read
  • Others: read
For the "files" directory inside Drupal's "sites" directory:
Path: /sites/files or /sites/default/files
Symbolic notation: rwxrwxr-x
Octal numbers: 775
Which means:
  • Owner: read, write, execute
  • Group: read, write, execute
  • Others: read, execute
For all files inside Drupal's "files" directory and inside any subdirectories with the "files" directory
Path: /sites/files/*.html and all other files in files directory
Path: /sites/files/subdirectories/*.html and all other files in subdirectories
Symbolic notation: rw-rw-r--
Octal numbers: 664
Which means:
  • Owner: read, write
  • Group: read, write
  • Others: read

If someone in-the-know could confirm or correct this it would be a big help.


alb’s picture

with folder files/images;
If I set rwxrwxr-x and owner root I cannot upload images
# Directory "iimages" is not accessible under file system!
# Unable to get a working directory for the file browser

If I set rwxrwxrwx and owner root I can upload images
but the module alert that
Some files and directories in your install are writable by the server

If I set rwx------ and owner apache I can upload Image but always same alert's message

why cannot use this
rwxrwxr-x owner root ?

os centos and imce with fckeditor

Security Review also alert regard html full, which html tags is preferable to remove from html full?

bitsman’s picture

Usually, server run as user www-data or nobody. So if you have your files owned by root, only if "others" with the relevant permissions will be accessing them. Check owner and groups in the "Configuration examples" at the beginning of this post.

kazar’s picture

on CentOS (which many cPanel servers use), cd to the public_html directory if necessary, then:

chown -R nobody:nobody /sites/default/files

This will make the apache web server able to upload images including with ckeditor/imce

Just figured this out after a day of struggles hope it helps someone.

FranCarstens’s picture

I've decided to update this post. The following leaves me problem free using Drush, CentOS, WHM and CPanel.

First, the basic ownership and permissions followed by a cut and paste list of commands.
*Note: update the paths in the commands to match your setup.

Example Setup

CPanel User: username
Site Root: /home/username/public_html


Files and folders username:nobody
Temporary Folder username:nobody


Core modules folders 750 RWXR-X---
Core modules files 640 RW-R-----
Core themes folders 750 RWXR-X---
Core themes files 640 RW-R-----
Contrib modules folders 750 RWXR-X---
Contrib modules files 640 RW-R-----
Contrib themes folders 750 RWXR-X---
Contrib themes files 640 RW-R-----
Files folders 770 RWXRWX---
Files files 660 RW-RW----
Temporary folder 770 RWXRWX---
Settings file 440 R--R-----
Settings folder 750 RWXR-X---

Update ownership for entire site and tmp folder

chown -R username:nobody /home/username/public_html
chown username:nobody /home/username/tmp


find /home/username/public_html/modules -type d -exec chmod 750 {} +
find /home/username/public_html/modules -type f -exec chmod 640 {} +
find /home/username/public_html/themes -type d -exec chmod 750 {} +
find /home/username/public_html/themes -type f -exec chmod 640 {} +
find /home/username/public_html/sites/all/modules -type d -exec chmod 750 {} +
find /home/username/public_html/sites/all/modules -type f -exec chmod 640 {} +
find /home/username/public_html/sites/all/themes -type d -exec chmod 750 {} +
find /home/username/public_html/sites/all/themes -type f -exec chmod 640 {} +
find /home/username/public_html/sites/default/files -type d -exec chmod 770 {} +
find /home/username/public_html/sites/default/files -type f -exec chmod 660 {} +
find /home/username/tmp -type d -exec chmod 770 {} +
chmod 440 /home/username/public_html/sites/default/settings.php
chmod 750 /home/username/public_html/sites/default

-- If no-one asked Drupal questions there would be no Drupal answers --

waqarit’s picture

This is really helpful.

hockey2112’s picture

My website was set up via the WHM panel that is installed on my GoDaddy VPS server. The server username is "myserver" and the cpanel username for my website is "mywebsite".

Update ownership for entire site and tmp folder
chown -R username:nobody /home/username/public_html
chown username:nobody /home/username/tmp

When I run these commands via command line, the website shows a "500 Internal Server Error". The commands I used are below:

chown -R mywebsite:nobody /home/mywebsite/public_html
chown mywebsite:nobody /home/mywebsite/tmp

What would cause this?

Also, when I revert back to a clean copy of the site and skip those two commands, and then I run these two commands...

find /home/mywebsite/public_html/sites/all/modules -type d -exec chmod 750 {} +
find /home/mywebsite/public_html/sites/all/modules -type f -exec chmod 640 {} + causes my admin toolbar to disappear and the Security Review module will no longer run. The rest of the website is displayed correctly. Again, what would cause this issue, and how can I fix it?

armanschwarz’s picture

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. This just confuses me and makes something really simple into a frustrating and confusing waste of time. Here is the permissions you should set for folders in Drupal 6.x, for those who don't feel like learning apache command line scripting just to set some permissions:

/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

suffering drupal’s picture

By the way Jason, I followed your indications (thank you!) and it worked :)

I started with Drupal in 2007 and then my life got stuck...

datarazor’s picture

Yes I would put this info at the top, rather than buried in the comments. This is the heart of what most people want to check, a very quick reference sheet, rather than a wall of text.

broadway’s picture

This page is classic The people who probably most need a tutorial are the people doing their first Drupal site. And these are the people (like me) who probably know how to use a ftp program to change settings and everything else on this page is Greek to them. Thanks Arman for some clear, concise, usable information.


protoplasm’s picture

I can't begin to tell you how quickly you resolved my can't write to files directory problem after migration with these simple cut and paste instructions:

[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

Since our community has thousands and thousands of image files, what a time saver! This information helped me understand how to more tightly reign in permissions on files and saved the day for me.

A big thank you.


herend’s picture

First of all: thanks for this doc, it's an important part of drupal's usage.

The 1st sentence is the heart of the matter: "system should be configured so that the webserver does not have permission to edit or write the files which it then executes".

'files' directory is an exception, that's why it is a security risk.
If someone can upload script (php) into that folder, your server is broken.

Let's minimize this case:
files/.htaccess should not be writeable by webserver

disable php in that folder, add this line to your files/.htaccess:

AddHandler default-handler php

(Your server will be safer, if a security hole or a weak admin password allows to upload php files.)

colbykd’s picture

A great idea, but if you grant write permission to the "files" directory itself, that permission could be used to overwrite any .htaccess file within, regardless of the permissions on that file (at least on some OSes). So to really accomplish this, you'll need to put the directives into your main Apache site config. Something like this:

<Directory /var/www/pathtositeroot/files>
  SetHandler default-handler
  Options None
  AllowOverride None
ThaboGoodDogs’s picture

I have some of my sites that have user and group set to apache for everything / all files and dirs. Is this bad? Under the impression that user apache is a special user with very limited permissions.

Also I run my own server so don't need a user for each site I host so I assume if I need to have the user not be apache I can just create a user eg webdude and use them for all my sites or is this less secure?

lsrzj’s picture

You must create it and you must use this user. If a user is the file and directory onwer all limitations go to the ground. You simply can put an script to change permissions and ownerships as you want, hackers will do it undoubtedly

dman’s picture

From a serverfault thread
To see if your webserver can write to anything that it's not supposed to, if you have sudo, you can run

 sudo -u www-data find . -type f -writable | grep -v sites/default/files

- this will list anything writable by the apache user - it should be empty after your site files dir is excluded.
It will just list, not fix it for you (the commands in the OP will do that for you).

joelbox-Mondial-IT’s picture

on centos would that be: sudo -u apache find . -type d -perm /u=w | grep -v sites/default/files ?

lal2017’s picture

Hi lsrzj, would you please confirm my translation of your documentation and your script per below:

I need to use the numeric values to apply permissions via FTP for a site hosted on Bluehost.

I reviewed the security script suggested in the document, under the heading: "Script based on guidelines given above". I am yet to test this but I believe it means:


  • All Core Drupal Folders and Sub-Folders = 750 (rwx,r-x,---)
  • All Core Drupal Files and Files in subfolders = 640 (rw-,r--,---)
  • Our own modules/themes/libraries use the same lockdown permissions.
  • /sites/all/themes = as above
  • /sites/all/modules = as above
  • /sites/all/libraries = as above


  • /sites/default Folder = 755 (rwx,r-x,r-x). Personally I think this should be = 750 (rwx,r-x,--) if it works.
  • /sites/default/settings.php = 444 (r--,r--,r--). Personally, I think this should be = 440 (r--,r--,---) if it works?

3. [@ /SITES/.../FILES] WRITEABLE FILES in /sites/all/default/files folders : (like images, file attachments etc)

  • All Folders in /sites/all/default/files and all its subfolders : 770 (rwx,rwx,---)
  • All Files in /sites/all/default/files and it subfolder files : 660 (rw-,rw-,---)

these folders/files are the exception so that Drupal can delete the files attached to posts when they are deleted etc.

Addtitional recommendation by: herend on February 24, 2011 at 11:57am
"...system should be configured so that the webserver does not have permission to edit or write the files which it then executes". So the server will be safer, if a security hole or a weak admin password allows to upload php files.

1. files/.htaccess should not be writeable by webserver
2. disable php in that folder, add this line to your files/.htaccess:
AddHandler default-handler php

I hope this is a correct translation and likely to work! I will investigate and attempt. Any confirmation from lsrzj or others, that this looks sensible, would be appreciated.

vivetech’s picture

I have a test site setup automatically with user=me group=me. How is www-data not even factored into this and the site works just fine. According to this tutorial. apache needs to have at least read access to the files but the www-data group/user has no permissions. Only my user/owner has permissions.

All I did was download drupal and extract it and the site works fine. Working with all the permission setting above gives me all types of problems. Such as the login page not being responsive.

dman’s picture

There are at least *three* pseudo-groups that have their own permission set. 'owner', 'group', and 'other'
It would be normal for a host that set your user up with a web directory to assume that you wanted stuff you put there to be world-readable, so the 'other' permissions probably include the normal 'read-only' permissions, and www-data can read and serve your files.

There are now a number of extended file system properties that allow even more control than that, but in this case, they are probably not an issue.

OnkelTem’s picture

I wonder why there were no a single word about Linux ACLs while they do really good job.
Recently I was trying to setup permissions/owns for a website to provide access to another developer and faced the fact that technically it is impossible to achieve the result that would secure enought and flexible using old-style linux fs security. I will demonstrate what I mean.

Suppose we have next setup:

customer - site's owner
dev1, dev2 - two developers
apache - apache user

1) customer, dev1, dev2 - should have r/w access to the site
2) apache - should have r/w access to the site's /sites/all/files dir only
3) any changes to site's files shouldn't break the requirement 1-2. For example, files created by apche should be r/w accessible to customer, dev1 and dev2 and vice versa.
4) nobody else should have access to the site

I'm gonna skip considering different "tranidional" approaches to achieve above, including running patched apache which can set specific owner/owning group (vie AssignUserID for example), for only reason - it would be either impossible or ugly to the extreme.

Now I'll show how to do this with simple native Linux ACLs. Let us assume the site root is under 'www' dir of the current directory and all the files are owned by the `customer` user and the `customer` group.

1) adding customer user and group to the "write list" for all the files:

setfacl -R -m user:customer:rwx, group:customer:rwx www
setfacl -R -d -m user:customer:rwx, group:customer:rwx www (-d stands for default perms - i.e. inherited)

2) adding dev1 and dev2 to the "write list" for all the files:

setfacl -R -m user:dev1:rwx, user:dev2:rwx www
setfacl -R -d -m user:dev1:rwx, user:dev2:rwx www

3) adding apache user to the "write list" for sites/default/files:

setfacl -R -m user:apache:rwx www/sites/default/files
setfacl -R -d -m user:apache:rwx www/sites/default/files

That's all. Note - not a single chmod/chown issued. Let us see file permissions now with example output from real setup (customer = mosaicum, dev1 = fantomas, dev2 = doka, apache as is):

-bash-3.2$ getfacl www
# file: www
# owner: mosaicum
# group: mosaicum
-bash-3.2$ getfacl www/sites/default/files
# file: www/sites/default/files
# owner: root
# group: root


1) You don't need chmod/chown. You don't even need chmod g+s'ing
2) You don't need to reissue commands (if only some outdated software which doesn't respect ACLs will break them) - acls are inherited (thanks to "-d")
3) You ain't depending on who creates files no more - you just don't care about owners - and they now go like the should - preserving names. So for example if it was you, who last uploaded a new version of a file - then it will be owned by you and your group, but the staff don't lose control over it.


* drush dl breaks ACLs since it moves (renames) downloaded and extracted files from /tmp to the site's dir (and while moving ACLs/owners/permissions never copied - this is "by design").
But thanks to drush developers, shell's rename is inside drush_move_dir() which has a workaround in case when system rename "doesn't work": files from /tmp are first copied to the site's dir and then unlinked from /tmp. When acting like this, ACLs are applied/preserved. To make system rename doesn't work you just add one line to either /etc/drush/php.ini or ~/.drush/php.ini:

disable_functions = rename

* tar zxf module.tar.gz would likely extract module which will become r/o for all the groups and named users. I think this is because tar preserves r/o for groups permission when creating archive and when extracting, this changes "mask" attribute of extended ACL to become "r-x" and not "rwx".

Well, I didn't find how to make tar to extract ignoring mode (or rather with mode changed to 666). But to change the mask back you can either use setfacl again, or just issue this right after tar zxf:

chmod -R g+w module
wipeout_dude’s picture

Great page but perhaps needs to make mention of the fact that any files uploaded will be owned by www-data:www-data..

Only ways I know of to get around this are..

1. Add the user to the www-data group - Problem here is that the user then has access to everything that www-data has access to potentially including other users files (if you have more than one user/site on the server.. If its a single site server this isn't such an issue)..

2. Set the UID on the writeable directories so that newly created files inherit the user..

3. Create a separate group and make www-data and the user members of the group.. Use this new group when setting permissions and set the GID of the writeable directories to the new common group..

Not sure which way is best or most secure but think that whichever is felt to be best is detailed on the page..

gor’s picture

It works only for dedicated servers or VPS.

It is not useful at all for shared hosting solutions.

Apache should work with client username or user will not be able to even change or delete uploaded via apache files!
Plus many other problems, specially when user try to move from one web hosting company to other.

And I don't even talk about newbies! They will go nuts.

lsrzj’s picture

It works for hosted installation. Note that the OWNER of each site's subdirectories and files will be each client's system logins so if you have a site and the hosting service create a user, the owner of all files and directories inside it will be your user. The OWNER can do whatever he wants to do with the directory, move files, delete files.... the Apache permission is given through GROUP permissions, Apache MUST NEVER be the owner of the files. The problem is that apache will try to create the files and directories with it's user. So I'm open to suggestions to solve this issue, there must be a way to do it. Is there a point to install one apache for a single user and run multiple instances of it, each apache running under the costumer's user name?

gor’s picture

If apache works from NOBODY (for example) - all created files and directories via drupal will be own by nobody.

User will not be able to chown it, because CHOWN restricted to root user only.
Also user will not be able to chmod it, because he is not own it.

When regular (no shell experience at all, no unix permissions knowledge ) user see this situation, he just put 777 on all dirs and files in sites/default/files.
And it is a big security hole.

For shared hosting there is no simple way to have php files owned by user, web host by apache and don't have any file permissions issues.

And don't forget. Shared hosting is mass hosting. Users pay only ~$5/month and they just need to see they own website online.
They don't want to play with permissions, owners and etc. Any limitations will push them away.

UPD: there is only way to process each request via user's id for apache. But it is mean, that user will be able to change any file.

lsrzj’s picture

I checked that there is a way to inherent parent directories permissions using POSIX ACLs, so using this, apache will create, automatically, files and directories owned by the users.

gor’s picture

Sounds good.
But if php under apache works with apache user and I have admin role with a right to write node with php code - I can read any settings.php on all websites and hack few more websites then.
Safe mode will save from it, but it will make problems -

wipeout_dude’s picture

Slight correction to your thinking..

Apache doesn't create or delete files.. PHP does..
Apache only needs read permission to files/directories..

Running PHP as the owner of the scripts can be accomplished in a number of ways.. Typically with apache modules like SuEXEC or SuPHP.. It can also be done with MPM-ITK or running PHP-FPM with individual pool configurations..

There are pro's and cons to all these options and some will need specific permissions and/or group memberships..

You will just have to research what is going to work for your particular setup..

delxpez’s picture

I read the previous comments, but I'm still confused... Is there a suggested configuration for files/folders permissions on a shared hosting?

Right now my files are on myusername:myusername ownership.
I don't have permissions to chown to myusername:nobody (nobody:nobody = apache), and I have set o+r (o+rw to 'files' directory), which doesn't make me feel comfortable.

lsrzj’s picture

Yes, there are. Look at If you are a sysadmin section.

gor’s picture

Hello delxpez

sysadmin - mean you have a right to be root and you don't.
Basically there is no way to follow this recommendation on any shared hosting! It is my personal opinion.
All this words just for Guys who has root access!

lsrzj’s picture

I understood you, but this install method presumes one shared drupal core and many subdomains installed under it. So suppose you are the owner of a hosting service that has a Drupal already pre-configured and you want to sell a hosted Drupal site with pre-configured Drupal core to your clients. So when you sell it, you create their user on the server and let them configure their own Drupal site just entering their site's address to open the install page. The way we suggested the configuration, you will prevent costumers from modifying and accessing the Drupal core files and other costumer's sites files and directories.

johns996’s picture

Just curious on this part but shouldn't all of the find commands, for consistency purposes, have their {} wrapped in single quotes? So instead of:

[root@localhost]find . -type d -exec chmod u=rwx,g=rx,o= {} \;
[root@localhost]find . -type f -exec chmod u=rw,g=r,o= {} \;

It would look like:

[root@localhost]find . -type d -exec chmod u=rwx,g=rx,o= '{}' \;
[root@localhost]find . -type f -exec chmod u=rw,g=r,o= '{}' \;

And then in the "for" loop the same thing would apply:

find $d -type d -exec chmod ug=rwx,o= '{}' \;
find $d -type f -exec chmod ug=rw,o= '{}' \;

According to the find manual, "[...] the braces are enclosed in single quote marks to protect them from interpretation as shell script punctuation."

lsrzj’s picture

Yes, it should be quoted with single quotes. Thank you, the commands were corrected

sir_squall’s picture

Hi guys,

if ever there is a way to recover the apache user automatically:

apache_group=$(ps axho user,comm|grep -E "httpd|apache"|uniq|grep -v "root"|awk 'END {if ($1) print $1}')

I have tested this in Ubuntu 11.04 and CentOS 6 and it works perfectly.

RKopacz’s picture

I've gone through this entire thread and am still lost. Is there a comprehensive guide to file / directory permissions? I work almost exclusively on shared hosting packages, and just need a guide to tell me:

Drupal core directories and files: I have everything set to 755. Good? Bad?
everything in sites/* is set to 755 except default.settings.php and settings.php. I tried to set them to 444 but they revert to 644 every time.

Using FileZilla to set the permissions, btw.

I have checked index.php and it is at 644, as is .htaccess.

The site is currently in a subdomain named 'beta' which points to a folder also called 'beta' inside the root. Both the beta directory and the root directory (public_html) are set to 750.

This is for a Drupal 7 site. Did anything change from 6 to 7?

I am using Drupal Security Review and it is telling me that every single directory and file is writable, including the settings.php file.

I would greatly appreciate it if someone could point me to a handbook, a page, something that explains it step by step, because it is not clear for me.

Thank you in advance!

PS QUICK UPDATE I tried to set the file permissions for the files directory and all subdirectories and files as proposed in the original post (rwxrws--- for directories and rw-rw---- for files within those directories, and none of my images displayed, except for a views slideshow on the front page which still seemed to be displaying. -- rek

Johnnie Walker’s picture

I found that people's Linux knowledge has passed from person to person all the way back to the bearded folk who built Unix. When Unix came out they wrote simple explanations that has become such 'common knowledge' that no one seems to justify re-writing it for Linux.

So. To really get to know Linux, pipes file permissions etc, I would hold back from reading reams and reams of 'Chinese Whispers' and recommend the following, choice, info 'From the Horse's Mouth':

* "The UNIX Time-Sharing System (1974) by Dennis M. Ritchie and Ken Thompson" - The paper (by its inventors) that introduce Unix to the world. Explains files, pipes and shell etc.
* "The Art of Unix Programming by Eric Steven Raymond" - The background to Unix and why everything is as it, crazy, is.
* "Filesystem Hierarchy Standard" - A standard to define and explain the directory layout of in Unix/Linux distributions.

Then, this book is a great, modern practical guide:

* "Beginning Ubuntu LTS Server Administration 2nd Edition (2008), by Sander van Vugt" - A modern book that explains (amongst other things) file systems, basic commands, permissions and ssh

Chapter 5 of that last book will explain a lot.

rdickert’s picture

When I implement these permissions on a VPS running a single site, it causes the GUI-based "install new module" capability of D7 to fail. Now, I understand that installing modules with drush may be "better", but I don't think my users will agree. I can restore that functionality by making www-data own everything, but seems to defeat the purpose of all of this. Is there a solution that enables gui-based module installation that leaves as much of the security intact as possible? Changing just permissions or ownership in sites/all doesn't do the trick. If I'm just running a single site with few users (all admin), do I need these permission changes at all?

jstuckle’s picture

Yes, you need write permission to the .../sites/all/modules directory to use the GUI based install. Then the module will be owned by your web server. You can either give write access to the group (and place the web server in the group owning the directory) or change the owner to the web server's id.

Both ways have security implications as noted above; On my servers I change the directory owner temporarily to the web server id (via SSH), load the modules then change the ownership of the directory and uploaded files/directories to the ftp upload id.

ayduns’s picture

It seems that as well as write permission to .... sites/all/modules, you also need the .../sites/default directory to be owned by the web server's id for the GUI installer to work. Write permission to .../sites/default is not sufficient. (7.15)

mpr’s picture

Taking the handbook script as a base, I cooked the following script a couple of weeks ago for a friend of mine, been using it myself with much bliss :)


trim () { read -rd '' $1 <<< "${!1}"; }

a=$1; trim a;  drupal_path=$(readlink -e  -- "$a" 2>&-)
b=$2; trim b; apache_group=$(id       -ng -- "$b" 2>&-)
c=$3; trim c;  drupal_user=$(id       -nu -- "$c" 2>&-)


if [[ "$#" != "3" ]]; then
  echo "ERROR: wrong parameter count." >&2
  if [[ "$drupal_path" == "" ]]; then
    if [[ "$a" == "" ]]; then
	  echo "ERROR: you must provide a non-blank path."
	  echo "ERROR: \`$a' is not a valid path." >&2
  elif [[ ! -d "$drupal_path/sites" || ! -f "$drupal_path/modules/system/system.module" ]]; then
    echo "ERROR: \`$a' is not a valid drupal installation." >&2

  if [[ "$apache_group" == "" ]]; then
    if [[ "$b" == "" ]]; then
	  echo "ERROR: you must provide a valid group name."
	  echo "ERROR: \`$c' is not a valid group." >&2

  if [[ "$drupal_user" == "" ]]; then
    if [[ "$c" == "" ]]; then
	  echo "ERROR: you must provide a non-blank user name."
	  echo "ERROR: \`$b' is not a valid user." >&2

if [[ "$errors" == "X" ]]; then
  echo >&2
  echo "This script is used to fix permissions of a drupal installation." >&2
  echo >&2
  echo "Usage:" >&2
  echo "  (sudo) ${0##*/} <drupal_path> <apache_group> <drupal_user>" >&2
  echo >&2
  echo "   <drupal_path>  path to your drupal installation (eg. \`/var/www/drupal')" >&2
  echo "   <drupal_user>  username of the user who \"owns\" this drupal installation (eg. \`you')" >&2
  echo "                    if no <drupal_user> is specified, the currently running user is used" >&2
  echo "  <apache_group>  group under which apache runs (eg. \`www-data')" >&2
  echo >&2
  echo "This script performs the following steps:" >&2
  echo >&2
  echo "  1. For every file NOT belonging to the given user and group, change its ownership to them," >&2
  echo "  2. Change the permissions of every non-conformant directory to \`rwxr-s---' (ie. u=rwx,g=srx,o=; or 02750)," >&2
  echo "  3. Change the permissions of every non-conformant file      to \`rw-r-----' (ie. u=rw,g=r,o=; or 0640)," >&2
  echo "  4. For every site in the \`sites' directory:" >&2
  echo "     4.1. If there's a \`files' directory:" >&2
  echo "          4.1.1. Change the permissions of every non-conformant directory in \`files' to \`rwxrws---' (ie. u=rwx,g=srwx,o=; or 02770)," >&2
  echo "          4.1.2. Change the permissions of every non-conformant file      in \`files' to \`rw-rw----' (ie. u=rw,g=rw,o=; or 0660)." >&2
  echo "     4.2. If there's a \`bin' directory:" >&2
  echo "          4.2.1. Change the permissions of every non-conformant directory in \`bin' to \`rwxrws---' (ie. u=rwx,g=srwx,o=; or 02770)," >&2
  echo "          4.2.2. Change the permissions of every non-conformant file      in \`bin' to \`r-xr-x---' (ie. u=rx,g=rx,o=; or 0550)." >&2
  echo >&2
  echo >&2

  exit 1

echo -n "Changing ownership of all contents to \`$drupal_user:$apache_group'..."
find -O3 $drupal_path \( -not -group "$apache_group" -o -not -user "$drupal_user" \) -exec chown "$drupal_user":"$apache_group" '{}' +
echo " done!"

echo -n "Changing the permissions of all non-\`file', non-\`bin' directories in \`$drupal_path' to \`rwxr-s---'..."
find -O3 $drupal_path -iregex "'$drupal_path/sites/[^/]*/files'" -prune -o -iregex "'$drupal_path/sites/[^/]*/bin'" -prune -o -type d -not -perm u=rwx,g=srx,o= -exec chmod u=rwx,g=srx,o= '{}' +
echo " done!"

echo -n "Changing the permissions of all non-\`file', non-\`bin' files       in \`$drupal_path' to \`rw-r-----'..."
find -O3 $drupal_path -iregex "'$drupal_path/sites/[^/]*/files'" -prune -o -iregex "'$drupal_path/sites/[^/]*/bin'" -prune -o -type f -not -perm u=rw,g=r,o= -exec chmod u=rw,g=r,o= '{}' +
echo " done!"

echo "Scanning sites..."
for site in $drupal_path/sites/*; do
  if [[ -d "$site" ]]; then
    echo "  Site \`${site##*/}':"

    if [[ ! -d "$site/files" ]]; then
      echo "    No \`files' directory found."
      echo "    Processing \`files' directory."

      echo -n "      Changing the permissions of all \`files' directories in \`$site' to \`rwxrws---'..."
      find -O3 $site/files -type d -not -perm u=rwx,g=srwx,o= -exec chmod u=rwx,g=srwx,o= '{}' +
      echo " done!"

      echo -n "      Changing the permissions of all \`files' files       in \`$site' to \`rw-rw----'..."
      find -O3 $site/files -type f -not -perm ug=rw,o= -exec chmod ug=rw,o= '{}' +
      echo " done!"

    if [[ ! -d "$site/bin" ]]; then
      echo "    No \`bin'   directory found."
      echo "    Processing \`bin'   directory."

      echo -n "      Changing the permissions of all \`bin'   directories in \`$site' to \`rwxrws---'..."
      find -O3 $site/bin -type d -not -perm u=rwx,g=srwx,o= -exec chmod u=rwx,g=srwx,o= '{}' +
      echo " done!"

      echo -n "      Changing the permissions of all \`bin'   files       in \`$site' to \`r-xr-x---'..."
      find -O3 $site/bin -type f -not -perm ug=rx,o= -exec chmod ug=rx,o= '{}' +
      echo " done!"
echo "done!"


Mind you, I've not tested this extensively, but haven't run into any problems so far... I find it's pretty useful as a "one shot" solution.

I sort of added to the directory structure by creating a "bin" directory under sites/<whatever>/bin, and giving execute permissions to that directory alone (an example of a module needing executables is Printer, email and PDF versions, with the wkhtmltopdf backend), if the executable files are needed somewhere else (as is the case for the above module, which needs them at sites/<whatever>/libraries) you can always soft-link (ln -s) them.

What do you think of it? Am I missing something? Could something be improved?

PS: my bash coding could use some (constructive) criticism :)

JordanMagnuson’s picture

I'd like to run this script, but have a ton of files in my sites/default/files folder with proper permissions, on s3fs, so I'd rather not touch those (I've tried running the script as is, and it just seems to take forever... eventually killed it).

Anyone with more knowledge know how to modify so that it leaves the sites/default/files directory alone?

Thanks in advance!

davidprush’s picture

Below is a bash script, name it whatever you want and make it executable, this seems easier than the proposal above....KISS!

I am open to any suggestions or changes because I use this script for every site I create.

# Script made by David Rush,
# Changes the permissions of the Drupal sites directory to secure after isntallation
# Very Important Put this script in the same directory as your site/ directory
# You are free to distribute, change, delete, or whatever the hell you want with this script

echo "Start fixing permissions..."

# Declare some DIR paths

# Paths for binary files

# Change permissions to be secure

$CHOWN -R apache:apache sites



$CHMOD g+w -R ${FILES}






echo "Done fixing permissions, verify all permisssions are correnct!"

# script done
afinoti’s picture

David, your script worked! Thanks!

As beginner, I have read comments and shouldn't the owner be other user than apache?

Something like:
$CHOWN -R greg:apache sites

OnkelTem’s picture

shouldn't the owner be other user than apache?

Actually, almost everything from above is about insecure/vulnerable setups and as you just noticed - the owner should never ever be an apache process since the foremost rule of web server security is to forbid the web server to write where it shouldn't write. This is the basics of web server configuration which doesn't depend on used CMS and I wonder how so many people advice all these flawed techniques.

If you are a beginner, then I encourage you to stand on the right path when securing filesystem of your web applications.
My first comment here was about exactly this - about using Linux ACLs:
It is a bit outdated and currently I'm using a set of scripts, but the idea is the same.

P.S. /me is thinking about providing his scripts to the community.

OnkelTem’s picture

So this is done. I created a child page where I documented my script:

JordanMagnuson’s picture

Just wanted to let people know that there's discussion on a drush command to fix permissions at

There's also information on fixing permissions/owner after drush dl at

jamesrgaede’s picture

i followed these instructions to a t (at least I think I did) but now the themes for the sites that are under the sites folder (i.e., the ones that got moved to home and a symlink) wont work -- the error log shows that it can't find various css files, even though they do exist at the path they are supposed to be in. I tried removing the theme, and reinstalling it, but same problem .

paulthed’s picture

I just had an issue with the index.php settings on a CentOS Server when doing a drupal 7 upgrade through drush, so hoping that this might help others. I was following the instructions above which said the index.php file should be:
-rw-r----- 1 greg-user www-data 873 2007-11-13 15:35 index.php

I replaced "www-data" with "nobody" since I thought that is the CentOS equivalent to Ubuntu's "www-data" which gives permission to the web server (apache). But I was unable to access the site after this. Turns out I had to set do the following:

chgrp greg-user index.php

and that did the trick. Replace "greg-user" with whatever the Linux username is on your server for that particular site.

Anyway, hope that helps someone who has a similar problem.

no_longer_active_19’s picture

CentOs doesn't have www-data and I am using root as my user while testing.

Cannot follow above directions, they are way too confusing.

I want to know what my drupal folder should be set to for ownership and permissions and down the line.

I have a subdomain where drupal resides.

At the moment I can't update modules not to mention other problems because nothing is set correctly.

Everything seems to be 6226 and then I read how that happened. Is everything 6226 supposed to be "apache"?

I am NOT on UBUNTU but CentOs vps so it is different and I'm completely lost on this most important set up.

I have read all of the above and it is not clear to me and I don't want to run a bash script.

Hope someone can help.

TechnoTim2010’s picture

Centos is a distro of Red Hat

You are correct in that there is no group 'www-data' on Centos.

As Far as I know the Centos equivalent of 'www-data' is 'apache' so for example the shell line to change ownership of all files in and below a directory would be chown -R user:apache .
(replacing user with your dev or sysadmin username.)

I hope that helps


adammalone’s picture

A reasonably effective method of ensuring file ownership after git pulls is to use git hooks.

If the following script is run on checkout and merge the entire drupal installation should use the correct ownership. This script will need to be extended for multisite installs to cover sites/*/files etc



cd ${GIT_DIR}/../

echo "Fixing user:group ownership ..."
chown --preserve-root ${owner}:${group} .
shopt -s dotglob
chown -R --preserve-root ${owner}:${group} *
shopt -u dotglob

echo "Fixing file/directory permissions in `pwd` ..."
find . -not -path "./.git*" -not -path "./sites/default/files*" -type d -exec chmod u=rwx,g=rx,o= '{}' \;
find . -not -path "./.git*" -not -path "./sites/default/files*" -type f -exec chmod u=rw,g=r,o= '{}' \;

cd ${GIT_DIR}/../sites
echo "Fixing file/directory permissions in `pwd` ..."
chmod ug=rwx,o= ./default/files
find ./default/files -type d -exec chmod ug=rwx,o= '{}' \;
find ./default/files -type f -exec chmod ug=rw,o= '{}' \;

exit 0
zzolo’s picture

I took the script above and put it up on Github (and enhanced a bit and fixed some minor issues). Pull requests welcome.


drupdan3’s picture

The beginning of the script contains the unused and highly misleading section

# Script arguments

You cannot, in fact, pass arguments that way. You need to use --drupal-user etc. I initially thought this was a problem with @zzolo's version of the script, when in fact the problem exists in the original (sorry, zzolo!)

filmoreha’s picture

For people running their own servers, I find setting a system wide UMASK of 027 simplies a lot of this. You could then chgrp your docroot (ie: public_html) to the shared group (www-data) and then setgid on it with chmod g+s. This would insure any new folders or files created would have 750 and 740 permissions respectively, and allow the web server access to the files. Then you just manually create your public files folder (and settings.php) and give the server permission to write to it with chmod g+w.

The only thing left to deal with is managing permissions on newly created folders and files WITHIN the files folder. In my testing, everything created within the files folder is owned by the server, and has 775 and 664 respectively, of course being a public space this shouldn't be too much of an issue.

Of course doing this on a brand new system may be easier than implementing on an existing system.

kalabro’s picture

The only thing left to deal with is managing permissions on newly created folders and files WITHIN the files folder.

Drupal calls drupal_chmod() after new directory is created. There is 'file_chmod_directory' setting which can be set in settings.php or with variable_set().
To make Drupal set 2775 permissions for new folders you can add this line to settings.php:

$conf['file_chmod_directory'] = octdec('2775'); // 1533

By default it is set to 0775. There is similar file_chmod_file for files (0644).

Of course you should additionaly configure your OS:

  1. Add user to www-data group (see more here)
  2. Set web-server umask (like this). In my case umask is 002. It is needed for recursively created folders.
  3. Set proper group and owner (www-data:kalabro) to already created files and folders.
swensor’s picture

After QUITE a bit of trial and error (I have 13 unused 'test' image styles in my drupal install now) I finally found instructions that WORK! Kalabro, thanks for setting us all straight here. Setting umask to 002 is a critical component here, and my site didn't created directories with 775 until I set that.

I too was having the issue where an image style would be created, with apache:apache ownership and 755 permissions. This worked fine until I did 'chown web:apache -R' to the file directory (to allow 'web' to edit files via sftp) and suddenly the owner was 'web' and not 'apache' ... Now I can re-apply the 'correct' ownership (web:apache) WITHOUT breaking image styles, since perms are set to 775, and both user 'web' and group 'apache' have write permissions.

Thanks @kalabro!! I only wish your comments weren't hidden at the bottom of a thread, but somehow front/center in drupal's documentation on file permissions and/or setting up image styles.

BWPanda’s picture

I got significantly better performance by changing all:
find [...] -exec chmod [...] \;
find [...] -exec chmod [...] \+
(~3 secs compared to ~2 mins)

Changing the ; (at the end of the exec action) to a + means matched files are appended onto the end of the one chmod command (as opposed to running chmod on each file individually).

Example: (echo simply displays the command, rather than actually running it)

$ find . -type f -exec echo chmod u=rw,g=r,o= '{}' \;
chmod u=rw,g=r,o= ./file1.txt
chmod u=rw,g=r,o= ./file2.txt
chmod u=rw,g=r,o= ./file3.txt
$ find . -type f -exec echo chmod u=rw,g=r,o= '{}' \+
chmod u=rw,g=r,o= ./file1.txt ./file2.txt ./file3.txt

See man find for more details.

If someone else can confirm this works and doesn't have any adverse side-effects, can we edit the script above?

danbohea’s picture

This script is great but any files in version control will be marked as changed if their permissions change. As a result, I prefer to separate out my scripts for handling tmp, public & private files (none of which I keep in version control) so that I can run them on a remote site without having to worry about what's happening to Git status etc. I've shared my work so far on GitHub (along with some other scripts):

filipes’s picture


as I am currently building a site with Drupal 7.26 and had issues with permissions on Apache 2 on Ubuntu 12.04, I tried to get all the info from this thread and make a solution for my sites. I ended up with a script, which I want to share now.

Note (Warning/Disclaimer):
I am a Linux and Ubuntu beginner and this is my first edit of a bash script, so I can't guarantee it is correct. Please comment, if something didn't work for you or could be done better.

1) I started here:
And among all other things, I have found this summary usefull:

2) As a base, I have taken a script from:
I call it "original script"

I didn't commit my script back to github. When the script proves to be correct, somebody experienced is welcome to update it.

3) Performance fix according to:

a) Changing find chmods like:
find ${x} -type d -exec chmod ug=rwx,o= '{}' \;
find ${x} -type d -exec chmod ug=rwx,o= '{}' \+

original "find ${x} -type d -exec chmod ug=rwx,o= '{}' \;" will call separate chmod for each found item (directory, ev. file), this could make performance issues.

Proposed "find ${x} -type d -exec chmod ug=rwx,o= '{}' \+" will first build a list of all found items (directories, ev. folders) '{}' and then perform chmod just once, this is faster.

b) Combining multiple files to one chmod like:
chmod u=rwx,go= CHANGELOG.txt
chmod u=rwx,go= COPYRIGHT.txt
chmod u=rwx,go= CHANGELOG.txt COPYRIGHT.txt

These performance fixes, when run on your (busy) live site, will minimize possible errors to your online users. During running the script (original or proposed), first all directories are set non writable (by server) u=rwx,g=rx,o= and all files are set non writable (by server) u=rw,g=r,o=. Then script (original or proposed) continues and makes sites/*/files writable. If there are performance issues (original script), i.e. making files writable takes too long or is performed too late, your online users could get errors e.g. when uploading images to your site. With proposed performance fix, this risk is reduced.

4) Added chmod for sites/*/settings.php and sites/*/default.settings.php:

5) Added chmod for sites/*/files/.htaccess
It was recommended to have .htaccess in sites/*/files directories without server write access, see:

To disable executing php scripts from files directory: - Suggestion adding: AddHandler default-handler php - Apache config suggestion

But in Drupal 7.26 I have found section starting with:
# Set the catch-all handler to prevent scripts from being executed.
so I think, this is already handled with default Drupal installation?
And I do not need to edit .htaccess, just to chmod it?
Let us know, if you think otherwise.

6) Script could be safely used on the live site,
it doesn't revoke permission of all files and directories in the beginning.

Original script sets permission on ALL directories an on ALL files.
Then it sets permission of some "sub files" and some "sub directories".
This could cause problems to your web visitors, because some file/directory permissions could be temporarily restricted before they are set later to correct values. Your visitors or Drupal could get permission denied errors in this moment.

So in the newest version of the script I have excluded setting permissions of "sub files" and "sub directories" from ALL or their parent files/directories. "sub files" and "sub directories" are only set once.

Every server, especially shared hosting can be different, so if some set of permissions work on one hosting, it might not work on another. I have experienced this problem some time ago, because some hidden load balancer was used by my shared hosting provider. See e.g.: - general permissions overview with proposed alternatives
(Adjust the permissions in the script accordingly, if the script permissions do not work for your server.)

I would rather prefer solution based on Linux ACL proposed at:
but it didn't fit my needs "out-of-the-box" and it is currently beyond my abilities and time restrictions to learn and adapt the ACL solution.

Personally I enjoyed the motto from user "suffering drupal" (
"I started with Drupal in 2007 and then my life got stuck..."

The newest version of the script is uploaded here:
(contains a fix for error reported by dhayalan_ms and doesn't support hardcoded arguments):

jnorell’s picture

Hey filipes

Thanks, that's a pretty decent and useful script. I had one issue where a library contained a "files" directory of its own, and ended up getting set with g=rwx permissions, when it should just be g=rx. That's due to the -not -path "./sites/*/files" matching ./sites/all/libraries/elfinder/files when you don't want that.

One fix is just add a second chmod, which worked for my case. Another might be to fix the -not regex, but I didn't try that route.

printf "Changing permissions of all directories inside \"${drupal_path}\" to \"rwxr-x---\"...\n"
#find . -type d -exec chmod u=rwx,g=rx,o= '{}' \+
find . -type d -not -path "./sites/*/files" -not -path "./sites/*/files/*" -not -name ".git" -exec chmod u=rwx,g=rx,o= '{}' \+
find ./sites/all -type d -not -name ".git" -exec chmod u=rwx,g=rx,o= '{}' \+
millionleaves’s picture


Thanks for this script - very helpful. Just one issue I found - it breaks Boost by making the Boost cache folder unreadable. This issue becomes apparent if you visit the Status report for the site after running this script.

I resolved it by tweaking the method for securing permissions in the files directory on a Linux server, as described above, to look like this:

[root@localhost]cd /path_to_drupal_installation/cache
[root@localhost]for d in ./*
   find $d -type d -exec chmod ug=rwx,o= '{}' \;
   find $d -type f -exec chmod ug=rw,o= '{}' \;

I provide Drupal, Drupal Commerce and CiviCRM development services for customers in New Zealand and beyond

hawkdavis’s picture

this script changes the .htaccess file in the */files directory to writable by Apache (http_user--)
The very end of the script...

for x in ./*/files; do
  find ${x} -type d -exec chmod ug=rwx,o= '{}' \;
  find ${x} -type f -exec chmod ug=rw,o= '{}' \;
echo "Done setting proper permissions on files and directories"

this line

find ${x} -type f -exec chmod ug=rw,o= '{}' \;

is making the .htaccess file in the files directory writable by apache

this should be added

for x in ./*/files; do
  find ${x} -type d -exec chmod ug=rwx,o= '{}' \;
  find ${x} -type f -exec chmod ug=rw,o= '{}' \;
  find ${x} -type f -name ".htaccess" -exec chmod u=rw,g=r,o= '{}' \;
echo "Done setting proper permissions on files and directories"

the .htaccess can be used to alter the configuration of the Apache Web Server this could be a potential security risk

cd-adam’s picture

Hello Drupal Community.

We'd like to follow these instructions as closely as possible, but we have 2 different user accounts on the server that need full read, write, and execute permissions. (We administer a client's website on their behalf, so we want a user at the client to have full permissions also.) How best to handle that?

A coworker's idea is to reverse the user and group and the associated permissions - grant the Apache user only read and write, and grant the users' group, which does not include the Apache user, read, write, and execute. AKA 570 and 460 permissions. (He also mentions 'The ONLY reason this works is because apache and php both run under the Apache user. If they were separate then either our client or we would not be able to have write permissions.')

Is there another solution that is preferred? Are there any security concerns with the proposed idea?

Thank you,

t_stallmann’s picture

On a site we maintain, there are a number of users with FTP access to sites/default/files *only*, which they use to upload large files for download. This is a less-than-ideal set-up, but moving to tracking all files from w/in Drupal and handling file uploads solely through the admin UI is more work than we're ready to take on at this point. Because of this the sites/default/files directory needs to have different group-level ownership from the rest of the drupalroot. I modified the above script to take an additional --sites_default_files_group argument and to set that group ownership specifically:’s picture

I suggest this script also deal with SELinux labels.
In Fedora 23 installation, Drupal code files (inside /usr/share/drupal7/ ) have labels:
while sites files (inside /etc/drupal7/sites ) have labels:
system_u:object_r:httpd_sys_rw_content_t:s0 Web site creation.

Turek’s picture

I've spent some time reading this extensive thread, and thought I would share my script as well, but as a Drupal Module, that I've created some time ago.
You can place it in ~/.drush subdirectory and simply execute drush file-permissions while being in any working Drupal site subdirectory.

It bootstraps your Drupal site, checks for file_private_path, file_public_path and sets them to correct Apache user and group. Apache user / group is being determined automatically, so it's not relying on the user specifying them manually or having them hard-coded in a script file.

Module page: