I have a Ubuntu 16.04 Nginx server environment with several CMS (WordPress) websites under document root.

All the site directories have www-data as owner and group, recursively.

According to some security consultation I got, this is problematic because data from one site could alter any other adjacent site (for example, a malicious module installed in one the CMSs of one of the sites).

I assume that the solution would be to change this command which I always run after adding a new site (and also in cron after updates that change owner and group):

chown -R www-data:www-data /var/www/html/*

To some conditional command that changes these characteristics not just to www-data but to the actual name of the site dir.

To clarify, the owner of each site dir should be domain.tld and the group should also be domain.tld (I always name site dirs as per the site's domain.tld).

What could be such a conditional command?

Note: The Nginx user as well as the dir /var/www/html/ will both stay with www-data:www-data.

Update for G-Man:

I have yet to test but I think this is good if you pass ${domain} as the first argument for a variable ($1):

chown -R ${domain}:${domain} ${domain}/*

Though I'd keep it:

chown -R ${domain}:www-data ${domain}/*

Of course, adduser ${domain} --disabled-password --disabled-login is needed before, if wasn't done.

  • 2
    First I want to thank you for the answer and comment. Second, I've just edited the question to clarify what should be the end state. Jan 15, 2018 at 18:29
  • 1
    By the way, I may be missing something, but I believe that changing the owner is at best a half solution to the security problem (if there really is one).  Data doesn’t alter data, processes alter data.  If the websites (can) include code (e.g., JavaScript or PHP?) that can run on your server, then you need to ensure that code from the domain1 site runs as the domain1 user.  Have you addressed that? Jan 16, 2018 at 22:12
  • 1
    (1) ${domain}:www-data is probably good enough if you use modes 644 and 755.  If you use 664 and 775, you need ${domain}:${domain}. (2) As I said, I may be missing something; I don’t know Nginx or CMS.  But I still don’t see anything that causes module(s) from the domain1 site (directory tree) to execute as the domain1 user. Jan 17, 2018 at 6:13
  • Why does 775:644 and 755:644 require different owner:group set? Jan 17, 2018 at 9:43
  • 1
    Because 644 and 755 give write access only to the owner (user), so having a distinct UserID is sufficient to protect the files of one domain from the actions of another; but 664 and 775 give write access to the owner and the group, so you need to have distinct UserIDs and GroupIDs to get separation. Jan 18, 2018 at 1:36

1 Answer 1


I may be missing something, but this looks simple:

$ cd /var/www/html
$ for dir in *
    chown -R "$dir:$dir" "$dir"

If you’re concerned about the correctness of these commands, you can insert the word echo before chown, e.g.,

$ cd /var/www/html
$ for dir in *
    echo chown -R "$dir:$dir" "$dir"

to have it tell you what chown commands it is going to do, without actually doing them.  This trick applies throughout this answer, and, in fact, to most shell script solutions (i.e., to other questions).

If you want to do it in one line,

$ cd /var/www/html  &&  for d in *; do chown -R "$d:$d" "$d"; done

I did the cd to avoid the complexity that the other answer embraced.  If you do

$ for dir in /var/www/html/*

then dir will be set to /var/www/html/domain1, /var/www/html/domain2, /var/www/html/domain3, etc., which is fine for manipulating the directories, but doesn’t give you the user and group names that you need for chown.  The other answer uses a rather convoluted (and not entirely reliable) awk procedure to separate the last level of the directory path (i.e., the directory name, which is also the user name; e.g., domain1) from the rest of the path.  Fortunately, we can do the equivalent thing (but somewhat better) entirely in the shell, like this:

$ for dir in /var/www/html/*
    chown -R "$owner:$owner" "$dir"

The ${dir##*/} is a special form of parameter expansion that takes the value of $dir and removes everything up through (and including) the last / (slash) character.  This, of course, leaves only the directory base name, i.e., the user name, so we assign that to owner and use it in the chown command.  Note that this code is POSIX-compliant and should work in any POSIX-compliant shell; unlike some of the answers on this site, it does not depend on bash.

If you want to do this in one line,

$ for d in /var/www/html/*; do o="${d##*/}"; chown -R "$o:$o" "$d"; done

Of course, if your concern is only that you don’t want your main shell’s working directory to be changed, then you can just do the first command in a subshell:

$ (cd /var/www/html  &&  for d in *; do chown -R "$d:$d" "$d"; done)

It goes without saying (but I’ll say it anyway) that this requires that you have users (and groups) named domain1, domain2, domain3, etc., defined before you run any of these commands.  If you have a script to add a new site, it’s a simple matter to add a command to create a new user whenever you make a new directory, before you run chown.  If you do run chown with a name that is not a defined user name, it will give you an invalid user error message.

  • Deep thanks! I can't test right now but without cd isn't good? for dir in /${drt}/ do; chown -R "$dir:$dir" "$dir" done. Jan 16, 2018 at 8:13
  • I’m not sure exactly what you’re suggesting.  for dir in /var/www/html will set dir to /var/www/html, which doesn’t do you any good.  for dir in /var/www/html/domain42 will set dir to /var/www/html/domain42, which is potentially useful — but if you’re going to process only one directory, you don’t need a loop. See the updated answer for more responses. Jan 16, 2018 at 22:11
  • G-Man. I would appreciate your review for my update in the question. Does my approach look okay to you? Jan 16, 2018 at 23:01

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .