πŸ’» wp-cli executed by www-data user to avoid Linux permission issues

In this article I’m going to share with you a trick that I use to run wp-cli as the www-data user on my Linux development environment.

wp-cli and file owner issues

I can’t imagine doing any serious WordPress development without wp-cli. It lets you automate the WordPress installation and configuration, lets you install and manage themes and plugins, post articles, and even post file-based media such as images. Whether you run it from grunt, from shell scripts, from inside docker containers, or just from the plain old shell, wp-cli rocks!

One issue I often face is that when I run wp-cli as my default Linux user, naturally all the file operations write files owned by the current user. Sometimes this is an issue, as my web files are normally owned by user www-data (in the www-data group).

In the past that meant that I had to do this a lot, in order to avoid various permission issues:

sudo chown -R www-data:www-data /var/www/wordpress

It quickly becomes tedious. There’s a better way.

finding your wp-cli installation

If you’ve followed the installation instructions, then you probably have wp-cli.phar installed as wp at /usr/local/bin/wp. In any case, you can do

which wp

or

whereis wp

to find its location.

running as www-data

Now on to put a setuid flag on that file and change its owner to that of your web server (usually www-data), right?

Not so fast! The astute Linux aficionado will notice that wp-cli is not a binary, even though it’s placed under a bin dir. Actually it’s a PHP CLI script:

$ head -n 1 `which wp`
#!/usr/bin/env php
$

Here’s what you do instead. First, run visudo with elevated privileges:

sudo visudo

This will let you edit your sudoers file. You want to allow your current user to run your script as www-data. So, add the following line: (here I’m logged in as alex, the local user I use for development)

alex ALL=(www-data) NOPASSWD: /usr/local/bin/wp

Save the file and exit. Now you can do stuff like this:

mkdir /tmp/t
cd /tmp/t
sudo -u www-data wp core download
ls -l

You should see a WordPress installation owned by the www-data user, without ever having to enter a password for www-data (by default there shouldn’t be any password for that user anyway).

Of course, having to type sudo -u www-data wp every time is also tedious. So add this to your ~/.bashrc, or better yet to your ~/.bash_aliases if you have one.

alias wp="sudo -u www-data wp"

That should do it, assuming bash is your shell. For other shells, YMMV.

wp-cli cache permission issue

But now you might notice some warnings of the form:

Warning: copy(/home/alex/.wp-cli/cache/plugin/XXXXXXXX.zip): failed to open stream: Permission denied in phar:///usr/local/bin/wp/php/WP_CLI/FileCache.php on line 164

This just means that the script, which now runs as the www-data user, cannot write to its cache directory, because that directory is owned by you. You can fix this with a simple:

sudo chown -R www-data:www-data ~/.wp-cli/

Now you can use commands such as wp plugin install foo.zip --activate and wp plugin uninstall foo --deactivate in your dev-testing lifecycle without ever worrying about your web server choking on permission issues.

12 thoughts on “πŸ’» wp-cli executed by www-data user to avoid Linux permission issues

  1. Thanks for sharing, Alex!

    In your example, user “alex” is sudo, correct? And with your settings, “alex” is running WP-CLI as “www-data” (e.g. the Nginx user).

    So, what do you chown/chmod for: /usr/local/bin/wp

    Also, what about: /usr/local/bin/

    Overall, what users & groups do you use in this environment?

    I saw some related discussion on GitHub:

    https://github.com/wp-cli/wp-cli/issues/3181
    https://github.com/wp-cli/wp-cli/issues/1241

    Anyway cheers!

    1. Hello,

      Yes user “alex” is in the sudoers list in my system.

      $ ls -l /usr/local/bin/wp 
      -rwsrwsr-x 1 www-data www-data 4967063 Sep  30  2016 /usr/local/bin/wp
      

      Did you run into some problem?

      Thanks for sharing the links I will have a look.

  2. Awesome, thanks Alex! WordFence is preventing my comment from being posted so trying again with generic terms….

    Is your /usr/local/bin/ also the same? Or have you ever seen permissions issues with updating WP-CPI?

    We are playing with a small auto-installer for LEMP stack so were wondering about WP-CLI best permissions.

    https://github.com/littlebizzy/slickstack

    Would love to mention your name in Credits? Thanks!

    1. Jesse, you are correct, this interferes with wp cli update:

      $ wp cli update
      Error: /usr/local/bin is not writable by current user.
      

      If you want to mention me in some credits please do, but I don’t have a good solution about this problem at the moment. If you do find a solution, I would love to hear about it.

  3. Hi there. Thanks for this post.

    Regarding this issue:

    $ wp cli update
    Error: /usr/local/bin is not writable by current user.

    The way I got around this was to create a bash alias like this:

    alias wp-cli-update="sudo chown -R MYUSER:MYUSER ~/.wp-cli/ && sudo wp cli update && sudo chown -R www-data:www-data ~/.wp-cli/"

    I can now use wp-cli-update to change the permissions on ~/.wp-cli/ to what’s required, update wp-cli, and change the permissions back to what otherwise makes things work for wp commands in general.

    1. Great Tutorial…

      Having the same issue.

      Error: /usr/local/bin/wp is not writable by current user.

      I created the alias in my .bashrc file as you instructed, but getting the same error (even after reboot).

      Any suggestions?

  4. sudo -u www-data wp ai1wm backup
    returns an error:
    Error: Strange wp-config.php file: wp-settings.php is not loaded directly.
    ls-las
    6344 -rwxr-xr-x 1 www-data www-data 6494444 Feb 24 23:26 wp

    My wp-config.php has
    /** Absolute path to the WordPress directory. */
    if ( !defined(‘ABSPATH’) )
    define(‘ABSPATH’, dirname(__FILE__) . ‘/’);

    /** Sets up WordPress vars and included files. */
    require_once(ABSPATH . ‘wp-settings.php’);

Leave a Reply

Your email address will not be published. Required fields are marked *