Strict file ownership for your WordPress installation

WordPress requires write access to one directory, and that one directory only: the directory returned by wp_upload_dir(). By default, this is /wp-content/upload, but it can be configured to anything that is beneath your document root, like /media, if you want to.

But since you want WordPress itself, plugins etc, to be updatable you can’t just make everything but the uploads directory to be non-writable.

A better solution is to use separate users for the separate matters.

Setup PHP-FPM (or HHVM) to run scripts as an arbitrary user. The users “www-data” and “www-user” are common ones. It’s best to avoid using the same user as your web server runs as, which often is “nginx” (look in /etc/nginx/nginx.conf). I’m using “www-user” here, but note that this should be a separate user per site/pool, so the sites do not have write access to each others’ files.

Edit the user and group directives in your “pool” file (/etc/php5/fpm/pool.d/www.conf is the default on Ubuntu):

user = www-user
group = www-user

Then you make the upload folder owned by that user (replace path according to your WordPress setup):

$ chown -R www-user:www-user /path/to/webroot/wp-content/upload

All other files and folders should be owned by a regular user on your system – the user you are using to deploy updates to WordPress and plugins.

All folders should have the permissions 0755 and files should have 0644 as usual:

$ find /path/to/webroot -not -perm 0644 -type f -exec chmod 0644 {} \; $ find /path/to/webroot -not -perm 0755 -type d -exec chmod 0755 {} \;

Updates with strict file ownership

Now, it is not possible to write to any of the files outside of the uploads directory via the webserver. But you still need to be able to manage and update plugins, themes and WordPress itself.

Either you use:

  • Some kind of deployment system, like Capistrano or Deploybot
  • Pull manually with a version control system, like Git
  • Use WP-CLI, either manually or in a cronjob

You should go for the solution that suits your strategy. Here are two generalisations to help you define a strategy:

  1. You trust nobody. All code, even security fixes, should be examined before put in production.If this is the case, you certainly don’t want to run automatic updates, but use some kind of deployment system.
  2. The “better to risk untested code than leave production unpatched” strategy. You trust the authors of WordPress and the plugins you use.If this is the case, you can well use WP-CLI with a cronjob that runs every ten minutes that updates everything:
    */10 * * * * cd /path/to/webroot && wp core update --quiet && wp core update-db --quiet && wp plugin update --all --quiet && wp theme update --all --quiet

    (You probably also want to use a pre-upgrade backup, a post-upgrade health check, and a rollback if needed if you go this route.)

    Please note that some premium plugins does not support being upgraded via WP-CLI. E.g. I know BackupBuddy and Gravity Forms works fine, but the plugins from Yoast does not.

BTW, strategy 2 is basically the same as the automatic updates feature in WordPress.

A helpful WordPress config directive

To not confuse users in the WordPress dashboard to think they actually can do updates or install plugins directly, is is possible to add a constant to wp-config.php to disable plugin and theme update and installation.

define( 'DISALLOW_FILE_MODS', true );

This will remove links and buttons in the WordPress dashboard to actions that requires file modification permissions.


  1. Great Article as usual, thank you very mush,
    Small typo (an S letter missing at the end) :
    $ chown -R www-user:www-user /path/to/webroot/wp-content/upload,,


  2. There is an issue after changing the ownership of all folders/files from www-data to the ubuntu user ( non-root ;) ), and now entering the website gives me the 500 Error ( Blank Page :( ) unless i change the user of php5-fpm to ubuntu user.

    I believe the web-server www-data cannot read neither files(644) nor folders (755) because it has not permissions to do so :((.

    Can you please explain how you make it work ??

Comments are closed.