Skip to content

Instantly share code, notes, and snippets.

@macbleser
Created February 21, 2014 15:37
Show Gist options
  • Save macbleser/9136424 to your computer and use it in GitHub Desktop.
Save macbleser/9136424 to your computer and use it in GitHub Desktop.
WordPress Permissions Configuration Script
#!/bin/bash
#
# This script configures WordPress file permissions based on recommendations
# from http://codex.wordpress.org/Hardening_WordPress#File_permissions
#
# Author: Michael Conigliaro
#
WP_OWNER=changeme # <-- wordpress owner
WP_GROUP=changeme # <-- wordpress group
WP_ROOT=/home/changeme # <-- wordpress root directory
WS_GROUP=changeme # <-- webserver group
# reset to safe defaults
find ${WP_ROOT} -exec chown ${WP_OWNER}:${WP_GROUP} {} \;
find ${WP_ROOT} -type d -exec chmod 755 {} \;
find ${WP_ROOT} -type f -exec chmod 644 {} \;
# allow wordpress to manage wp-config.php (but prevent world access)
chgrp ${WS_GROUP} ${WP_ROOT}/wp-config.php
chmod 660 ${WP_ROOT}/wp-config.php
# allow wordpress to manage .htaccess
touch ${WP_ROOT}/.htaccess
chgrp ${WS_GROUP} ${WP_ROOT}/.htaccess
chmod 664 ${WP_ROOT}/.htaccess
# allow wordpress to manage wp-content
find ${WP_ROOT}/wp-content -exec chgrp ${WS_GROUP} {} \;
find ${WP_ROOT}/wp-content -type d -exec chmod 775 {} \;
find ${WP_ROOT}/wp-content -type f -exec chmod 664 {} \;
@macbleser
Copy link
Author

Save the file as wordpress-perms.sh and set appropriate permissions for that script file using the following command:

chmod +x wordpress-perms.sh

Run the script with the following command:

./wordpress-perms.sh

After successful execution delete wordpress-perms.sh script file and then you are done.

rm wordpress-perms.sh

@vancouverwill
Copy link

hey @macbleser this script was great super helpful. I would suggest returning the .git directory back to the sysadmin owner after processing as this directory shouldn't be controllable by the apache user.

see https://gist.github.com/vancouverwill/b409515938548497bc7e

thanks

Will

@jacksierkstra
Copy link

I made a minor alteration to this script namely:

#!/bin/bash
#
# This script configures WordPress file permissions based on recommendations
# from http://codex.wordpress.org/Hardening_WordPress#File_permissions
#
# Author: Michael Conigliaro
#
WP_OWNER=changeme # <-- wordpress owner
WP_GROUP=changeme # <-- wordpress group
WP_ROOT=$1 # <-- wordpress root directory
WS_GROUP=www-data # <-- webserver group

And then you can call this script like the following:
./wp-permissions-script /var/www/wordpress-directory

That was useful to me as I had more Wordpress installations on my webserver.

@bradbakerdx
Copy link

WS_GROUP makes sense - that's going to be www-data or apache depending on your distro
WP_OWNER makes sense - that's going to be whatever user needs to interact with the files

But what is WS_GROUP? What's that supposed to be set to?

@NeonMonk
Copy link

NeonMonk commented May 3, 2016

Good work @jacksierkstra, that means it can be run on all wordpress directories like this: find /var/www -maxdepth 1 -type d -exec wp-permissions-script {} ;

@bradbakerdx: WS_GROUP is generally the same as WP_GROUP. Some people may have a different setup, if it's not immediately obvious to you, you don't. :)

@tech4eleven
Copy link

Is it possible to run this script on a wordpress installation on a windows server 2008 server? if so, how exactly?

@s1037989
Copy link

Wow, this is helpful! I wish Wordpress would include this in the tarball! Every time I do a new installation I struggle to get the permissions right before handing it over to the developer and then it's constant back and forth: "try again!"

What do you think about this updated version:

#!/bin/bash
#
# This script configures WordPress file permissions based on recommendations
# from http://codex.wordpress.org/Hardening_WordPress#File_permissions
#
# Author: Michael Conigliaro (https://gist.github.com/macbleser/9136424)
#
WP_ROOT=${1:-.} # <-- wordpress root directory, current directory by default
[ -e "$WP_ROOT/wp-config.php" ] || { echo "Usage: $0 /path/to/wordpress"; exit; } # <-- detect that the directory is a wordpress root
WP_OWNER=$(id -u $(logname)) # <-- wordpress owner (This assumes the wordpress owner is the logged in user)
WP_GROUP=$(id -g $(logname)) # <-- wordpress group (This assumes the wordpress owner is the logged in user)
WS_GROUP=$(
     source /etc/apache2/envvars 2>/dev/null && # This works on debian-based systems at least
     echo "$APACHE_RUN_GROUP" ||
     echo nobody  
) # <-- webserver group
echo "Fixing permissions on $WP_ROOT"
echo "Wordpress owner.group: $WP_OWNER.$WP_GROUP"
echo "Web Server group: $WS_GROUP"

echo 'reset to safe defaults'
find ${WP_ROOT} -exec chown ${WP_OWNER}:${WP_GROUP} {} \;
find ${WP_ROOT} -type d -exec chmod 755 {} \;
find ${WP_ROOT} -type f -exec chmod 644 {} \;

echo 'allow wordpress to manage wp-config.php (but prevent world access)'
chgrp ${WS_GROUP} ${WP_ROOT}/wp-config.php
chmod 660 ${WP_ROOT}/wp-config.php

echo 'allow wordpress to manage .htaccess'
touch ${WP_ROOT}/.htaccess
chgrp ${WS_GROUP} ${WP_ROOT}/.htaccess
chmod 664 ${WP_ROOT}/.htaccess

echo 'allow wordpress to manage wp-content'
find ${WP_ROOT}/wp-content -exec chgrp ${WS_GROUP} {} \;
find ${WP_ROOT}/wp-content -type d -exec chmod 775 {} \;
find ${WP_ROOT}/wp-content -type f -exec chmod 664 {} \;

@jaysin586
Copy link

I want to thank you so much for this!

For those of you looking into a default AWS word press install please use the below variables:

WP_OWNER=apache # &lt;-- wordpress owner
WP_GROUP=apache # &lt;-- wordpress group
WP_ROOT=/var/www/html # &lt;-- wordpress root directory
WS_GROUP=apache # &lt;-- webserver group

@KontrivedMedia
Copy link

How would I get this working on my local machine (Mac running Mamp Pro) as the above script doesn't work for me and sets the group to nobody. Which renders my site a whitescreen.

@jult
Copy link

jult commented Oct 26, 2016

This assumes apache is the webserver. I have to admin several servers and sites that run solely using nginx and php5-fpm.

@phidomo
Copy link

phidomo commented Dec 10, 2016

How do I get the WP_OWNER, WP_GROUP and WS_GROUP of my current WordPress installation?

@michaelwdc
Copy link

This is great! I've been struggling with getting the correct file/folder permissions. This script makes it easy.

@burbridgeconsulting
Copy link

I can't tell you how helpful this is. Thanks!

@milesstewart88
Copy link

I needed this!

@KarlYee
Copy link

KarlYee commented Oct 4, 2018

Wonderful! Something that should come bundled w/ the WP install package.

@inventortechie
Copy link

Why is there a separate WordPress group, and a Server Group?

@kl3sk
Copy link

kl3sk commented Dec 28, 2018

To automatically find webserver user, symfony provide a command:

HTTPDUSER=$(ps axo user,comm | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\  -f1)

source: https://symfony.com/doc/3.3/setup/file_permissions.html#using-acl-on-a-system-that-supports-setfacl-linux-bsd

@pwil30
Copy link

pwil30 commented Jun 20, 2019

Brilliant, thank you!

@xpufx
Copy link

xpufx commented Dec 2, 2019

It would be useful if the script has a test or dry-run option. Most people would not be comfortable running it blindly.

@flowupuser
Copy link

#!/bin/bash

This script configures WordPress file permissions based on recommendations

from http://codex.wordpress.org/Hardening_WordPress#File_permissions

Author: Michael Conigliaro (https://gist.github.com/macbleser/9136424)

WP_ROOT=${1:-.} # <-- wordpress root directory, current directory by default
[ -e "$WP_ROOT/wp-config.php" ] || { echo "Usage: $0 /path/to/wordpress"; exit; } # <-- detect that the directory is a wordpress root
WP_OWNER=$(id -u $(logname)) # <-- wordpress owner (This assumes the wordpress owner is the logged in user)
WP_GROUP=$(id -g $(logname)) # <-- wordpress group (This assumes the wordpress owner is the logged in user)
WS_GROUP=$(
source /etc/apache2/envvars 2>/dev/null && # This works on debian-based systems at least
echo "$APACHE_RUN_GROUP" ||
echo nobody
) # <-- webserver group
echo "Fixing permissions on $WP_ROOT"
echo "Wordpress owner.group: $WP_OWNER.$WP_GROUP"
echo "Web Server group: $WS_GROUP"

echo 'reset to safe defaults'
find ${WP_ROOT} -exec chown ${WP_OWNER}:${WP_GROUP} {} ;
find ${WP_ROOT} -type d -exec chmod 755 {} ;
find ${WP_ROOT} -type f -exec chmod 644 {} ;

echo 'allow wordpress to manage wp-config.php (but prevent world access)'
chgrp ${WS_GROUP} ${WP_ROOT}/wp-config.php
chmod 660 ${WP_ROOT}/wp-config.php

echo 'allow wordpress to manage .htaccess'
touch ${WP_ROOT}/.htaccess
chgrp ${WS_GROUP} ${WP_ROOT}/.htaccess
chmod 664 ${WP_ROOT}/.htaccess

echo 'allow wordpress to manage wp-content'
find ${WP_ROOT}/wp-content -exec chgrp ${WS_GROUP} {} ;
find ${WP_ROOT}/wp-content -type d -exec chmod 775 {} ;
find ${WP_ROOT}/wp-content -type f -exec chmod 664 {} ;

some users using nginx instead apache2, not useable for them

@yupthatguy
Copy link

Hi there,

I just tried to run the latest version of the script above on my Ubuntu 18.04 LAMP dev environment and I got the following errors:

Fixing permissions on .
Wordpress owner.group: 1000.1000
Web Server group: nobody
reset to safe defaults
find: missing argument to `-exec'
find: missing argument to `-exec'
find: missing argument to `-exec'
allow wordpress to manage wp-config.php (but prevent world access)
chgrp: invalid group: ‘nobody’
allow wordpress to manage .htaccess
chgrp: invalid group: ‘nobody’
allow wordpress to manage wp-content
find: missing argument to `-exec'
find: missing argument to `-exec'
find: missing argument to `-exec'

I also made a post on wordpress.stackexchange.com regarding the original script at the top of this thread.
https://wordpress.stackexchange.com/questions/372449/permissions-script-not-working

@libre
Copy link

libre commented Oct 21, 2020

Warning Broken system
Small modification for the command line use with option.
On the other hand, important modification related to security: I add this condition because if the folder does not exist, you completely block your system until you can no longer start! (cron: "root" account)

#!/bin/bash
#
# This script configures WordPress file permissions based on recommendations
# from http://codex.wordpress.org/Hardening_WordPress#File_permissions
#
# Author: Michael Conigliaro
#
WP_OWNER=$1 # &lt;-- wordpress owner
WP_GROUP=$2 # &lt;-- wordpress group
WP_ROOT=$4 # &lt;-- wordpress root directory
WS_GROUP=$3 # &lt;-- webserver group

# Check value option
#
if [ -z ${1} ] || [ -z ${2} ] || [ -z ${3} ] || [ -z ${4} ] ; then
echo "Error, option not found"
echo "usage: $0 OWNER GROUP WEBSERVGROUP FOLDER"
echo "ex: $0 www-data www-data www-data /var/www/html"
echo ""
exit 1
fi

#I add this condition because if the folder does not exist, you completely block your system until you can no longer start.!
#I wanted to share the experience with you because I had the case with a cron job which blocked a system because the folder no longer ``existed ....
if [ ! -d ${WP_ROOT} ]; then
echo "STOP, The folder Wordpress not found !"
echo "Please check the path"
exit 1
fi

# reset to safe defaults
find ${WP_ROOT} -exec chown ${WP_OWNER}:${WP_GROUP} {} \;
find ${WP_ROOT} -type d -exec chmod 755 {} \;
find ${WP_ROOT} -type f -exec chmod 644 {} \;

# allow wordpress to manage wp-config.php (but prevent world access)
chgrp ${WS_GROUP} ${WP_ROOT}/wp-config.php
chmod 660 ${WP_ROOT}/wp-config.php

# allow wordpress to manage .htaccess
touch ${WP_ROOT}/.htaccess
chgrp ${WS_GROUP} ${WP_ROOT}/.htaccess
chmod 664 ${WP_ROOT}/.htaccess

# allow wordpress to manage wp-content
find ${WP_ROOT}/wp-content -exec chgrp ${WS_GROUP} {} \;
find ${WP_ROOT}/wp-content -type d -exec chmod 775 {} \;
find ${WP_ROOT}/wp-content -type f -exec chmod 664 {} \;
exit 0

@porthos-co
Copy link

porthos-co commented Nov 2, 2020

I have made a few additions:

  1. Allowed parameters to be added as the script is called so standard usage would be for example:
[scriptname] /var/www/sitename username groupname

I always set the groupname to that of the webserver (nginx/apache) which as far as I know is secure but let me know if you disagree

  1. Added SE Linux context changes using chcon. Mine are standard Redhat / Centos contexts which you may need to alter

  2. Speeded up the script by applying standard file permissions with chmod recursively (no find necessary) and then altering directory permissions using find and chmod as before for directories. Doing this means it executes 10-20x faster on large sites.

***** WARNING - I've only tested this on a couple of live sites so far with no issues it's been fine. Posting this in case it's useful but please do your own testing and be careful before using it in production or cronjobs. The original script by Michael is probably safer until this one has been tested more! **********

#!/bin/bash
#
# This script configures WordPress file permissions based on recommendations
# from http://codex.wordpress.org/Hardening_WordPress#File_permissions
#
# Author: Michael Conigliaro with tweaks to make it faster and SE Linux by Jonathan Gittos
#
# check if this script has been called with a first parameter and use it as the wordpress root directory
[  -z "$1" ] && WP_ROOT=$PWD || WP_ROOT=$1
# check owner and group of wp-config and use later to give same to rest of WP DIR unless parameter 2 or 3 set
[  -z "$2" ] && WP_OWNER="$(ls -ld $WP_ROOT/wp-config.php | awk '{print $3}')" || WP_OWNER=$2
[  -z "$3" ] && WP_GROUP="$(ls -ld $WP_ROOT/wp-config.php | awk '{print $4}')" || WP_GROUP=$3

# Make sure we're in what looks like a WP directory to avoid big snafus

if [ ! -f $WP_ROOT/wp-config.php ]; then
    echo ""
    echo "--------------------------------------------"
    echo "This doesn't look like a Wordpress directory!"
    echo "Current or chosen directory is " $WP_ROOT
    echo "Maybe change directory, or specify a directory as a parameter to this script and try again?"
exit

fi

echo -e "\nAbout to reset permissions on:\n " $WP_ROOT
echo "Changing owner and group to:" $WP_OWNER $WP_GROUP

read -p "Does that look right? " -n 1 -r
echo

if [[ $REPLY =~ ^[Yy]$ ]]; then

    echo -e "\nRestoring permissions on: " $WP_ROOT

# reset to safe defaults
chown -R ${WP_OWNER}:${WP_GROUP} $WP_ROOT;
chmod -R 644 $WP_ROOT;
chcon -R system_u:object_r:httpd_sys_content_t:s0 $WP_ROOT;
find ${WP_ROOT} -type d -exec chmod -R 755 {} \;

# allow wordpress to manage wp-config.php (but prevent world access)
chgrp ${WP_GROUP} ${WP_ROOT}/wp-config.php
chmod 660 ${WP_ROOT}/wp-config.php
chcon system_u:object_r:httpd_sys_rw_content_t:s0 ${WP_ROOT}/wp-config.php

# allow wordpress to manage .htaccess
touch ${WP_ROOT}/.htaccess
chgrp ${WP_GROUP} ${WP_ROOT}/.htaccess
chmod 664 ${WP_ROOT}/.htaccess
chcon system_u:object_r:httpd_sys_rw_content_t:s0 ${WP_ROOT}/.htaccess

# allow wordpress to manage wp-content
chmod -R 664 $WP_ROOT/wp-content;
chcon -R system_u:object_r:httpd_sys_rw_content_t:s0 $WP_ROOT/wp-content;
find ${WP_ROOT}/wp-content -type d -exec chmod -R 775 {} \;
echo -e "\nDoing it\n"

fi

@juanpgarciac
Copy link

If you are having "No such file or directory" problems and you use windows to upload the file a quick fix is changing the format to unix again:

#vim fix_permision.sh
:set fileformat=unix
:wq

#sh fix_permision.sh

@progonkpa
Copy link

progonkpa commented Feb 14, 2022

I'm on docker and had permission issues when trying to update or install a plugin.
I used this script with this config and want to leave some pointer as this issue might not be obvious to others, especially WS_GROUP. At least, it wasn't for me.

WP_OWNER=my_user_on_host_machine
WP_GROUP=my_user_group_on_host_machine # same name as previous in my case
WP_ROOT=/path/to/wordpress/src
WS_GROUP=http # docker web user on host machine

http is the apache process created by Docker in my host. I can see http in ps aux when the Docker WordPress container is running.

Curiously however, when I go inside the Docker container running WordPress (guest), I see the web user is www-data. The WordPress image I'm running is wordpress:5.4-php7.3-apache.

At any rate, my permission issues went away with the config above, with http.

@ErvinSabic
Copy link

The number of times I have come back to this page is insane lol.

@MassStash
Copy link

MassStash commented Jul 25, 2022

Landed here to fix a perm issue, so not sure if 1) I messed up script somehow haha, absolutely possible, 2) think the mess up may be still trying to wrap my head around litespeed entirely! Anyone have more wisdom than I? Seems nobody is WS_GROUP as default script has, but ran script and still issues with images not showing, but after script site has critical error... Setting back to basic 755 / 644 brings it back, but things are still not loading and running strange. I has suspicion it is a secret "easy" perms thing though since this is all after fooling around migrating sites somewhat.... 🤦 Any insights would helpful!

Thanks for the work on the script, and the comments everyone!

Addon: Oh yea, other issue I believe, is the .htaccess couldn't be accessed by litespeed... So setting chgrp on .htaccess to WS not good fro litespeed? Or I possible have wrong WS group... right?

@MrBenjaminRay
Copy link

MrBenjaminRay commented Dec 2, 2024

​# reset to safe defaults
chown -R ${WP_OWNER}:${WP_GROUP} $WP_ROOT;
chmod -R 644 $WP_ROOT;
chcon -R system_u:object_r:httpd_sys_content_t:s0 $WP_ROOT;
find ${WP_ROOT} -type d -exec chmod -R 755 {} ;

​# allow wordpress to manage wp-content
chmod -R 664 $WP_ROOT/wp-content;
chcon -R system_u:object_r:httpd_sys_rw_content_t:s0 $WP_ROOT/wp-content;
find ${WP_ROOT}/wp-content -type d -exec chmod -R 775 {} ;

porthos-co, I really like your updated version, but I found an issue in two places. See the two bolded/italicized lines above. Under the headings "reset to safe defaults" and "allow wordpress to manage wp-content", you set the permissions on all files and directories recursively to 644 and 664, then you use find to identify directories and change their permissions to 755 and 775. However, the -R flag on the chmod command results in 755 and 775 being applied recursively, which includes all files. This overwrites the 644 and 664 permissions set two lines above.

If you remove the -R flag from the chmod commands, the directories are correctly set to 755 and 775 while leaving the files at 644 and 664, which is what we want.

Updated code:

# reset to safe defaults
chown -R ${WP_OWNER}:${WP_GROUP} $WP_ROOT;
chmod -R 644 $WP_ROOT;
chcon -R system_u:object_r:httpd_sys_content_t:s0 $WP_ROOT;
find ${WP_ROOT} -type d -exec chmod 755 {} \;   # REMOVED -R FROM THIS LINE
# allow wordpress to manage wp-content
chmod -R 664 $WP_ROOT/wp-content;
chcon -R system_u:object_r:httpd_sys_rw_content_t:s0 $WP_ROOT/wp-content;
find ${WP_ROOT}/wp-content -type d -exec chmod 775 {} \;   # REMOVED -R FROM THIS LINE

Thanks to everyone for the various versions of this helpful script!

@jult
Copy link

jult commented Dec 3, 2024

So, what is the final version of this? Someone forked it so that all improvements are included?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment