St. Louis Web Developer & User Interface Designer

Managing file and folder permissions when deploying with Git

Preface

I use Git as a version control and deployment system. When a website gets pushed to a server, all files get pulled into the web root (i.e. htdocs) by a user named git executing git pull in the post-receive hook.

By default, all files and folders git creates have 664 and 775 permissions, respectively, and are owned by that user. 664 translates to the user and group being able to read and write, and everyone else only being able to read, and 775 translates to the user and group being able to read, write and execute, and everyone else only being able to read and execute. (That’s a mouthful!)

Now, in an instance where you need a folder in htdocs writable by another user, like apache, for let’s say a caching system, you need to be able to set those particular permissions accordingly.

To accomplish this, you really only have two options:

Set permissions of files to 666 and folders to 777

Set the owner or group to apache (or a group that apache is a member of)

Personally, I favor restrictive permissions over convenience, so option #1 is out, which means we’re going to take a look at how to implement option #2.

Goals

Keep website files and folders owned by git, which essentially makes them read-only to apache and the public

Allow apache to create and modify files and folders only in specified folders

Maintain a streamlined deployment, meaning all permission modifications need to happen in the post-receive hook using only the permissions the git user already has

Note: I’m running CentOS/RedHat, meaning that some commands may differ if you’re using a different operating system.

1. Set group ID (setgid bit)

The first thing we need to do is ensure that everything inside htdocs remains owned by git, in other words, ensure only git can create and modify files and folders. This leaves us with having to change the group of the folders that apache needs to write to, but the problem is that git doesn’t have the proper permissions to use the chgrp command.

What we’ll do instead is create a new group called web, add git and apache as members of that group, and then enforce that all files and folders created in htdocs are automatically placed in the web group.

Going forward, any file or folder that’s created in htdocs will automatically set web as the group. If you already have files and folders in htdocs, you can recursively put them in the web group, but be conscious about the effect this will have on everything you have in that folder.

1

chgrp-R web /var/www/domains/domain.com/www/htdocs

chgrp -R web /var/www/domains/domain.com/www/htdocs

At first it my seem tempting to simply set the group to apache, however when git later modifies a folder to allow apache to create files and folders inside, the setgid bit is unset. The reason for this is that git does not belong to the apache group. To circumvent this particular issue, we created the new web group that both users could be a member of, which allows git to effectively give apache write permissions without unsetting the setgid bit.

2. Set umask to 022

By doing the above, we’ve violated one of our goals, and that’s the fact that apache will now have write permissions on all files and folders after git pull is run for the first time.

I don’t know if you’ve ever noticed, but when you create a new file or folder using root, the permissions are actually set to 644 and 755, respectively. Those default permissions are set via a command called umask, and we’ll now emulate this behavior for the git user.

You’ll notice that by default, all users with a user ID of 200 or greater will receive a umask of 002, and all users with ID 199 or less, a umask of 022. Looking at the chart below, you can look up what this translates to:

0: read, write and execute

1: read and write

2: read and execute

3: read only

4: write and execute

5: write only

6: execute only

7: no permissions

002 means rwx for owner, rwx for group, and r-x for everyone else. (The default for new system users).

022 means rwx for owner, r-x for group, and r-x everyone else. (The default for built-in system users).

We’ll use the git user’s .bashrc file to automatically set the umask to 022. The .bashrc file is executed every time a user logs in or opens a new terminal window. Here is a brief explanation from linux.die.net:

Today, it is more common to use a non-login shell, for instance when logged in graphically using X terminal windows. Upon opening such a window, the user does not have to provide a user name or password; no authentication is done. Bash searches for ~/.bashrc when this happens, so it is referred to in the files read upon login as well, which means you don’t have to enter the same settings in multiple files.

3. Use post-receive hook to update permissions

The last step, and this will be specific to your needs, we’ll use the post-receive hook to set permissions to allow apache to write to specified folders. I’ve pulled out and modified the relevant commands from my post-receive hook to provide an example:

Hi again Ryan!
I followed your guide and had it all working last night. I am not sure what I did but now for some reason I can’t get access into anything past /var/www/vhosts/ as the git user (eg I cannot cd into vhosts/domain.com). I can see everything in there and for some reason, the current setup is:

drwx--x--- 6 clientname psaserv 4096 Dec 5 12:49 domain.com

As the git user I cannot cd into this folder, nor can I cd into the httpdocs folder inside:

I tried changing permissions to git:web and that gave the git user permissions alright. Unfortunately that completely screwed up Plesk’s permissions meaning I couldn’t manage the server via Plesk anymore so I reset all of the settings for domain permissions. I am baffled here, I have added the git user to Plesk’s default groups – psacln and psaserv but this still does not give the git user any permissions to cd into the directories!

I run this on a server that doesn’t have any management software that tries to access and/or modify anything in those folders, which is why the setup described above works for me. My guess is that something like Plesk has a cron job or some other way to periodically ensure that all the permissions are properly set, which could explain why it stopped working.

This is a tricky situation, because the GIt user exists to prevent anything like a user (or Plesk) to modify version-controlled files, but if Plesk requires that it has those permissions, then creating a Git user defeats the purpose.

That said, you don’t have to create a Git user. You could also push your code as the clientname user, so that all the existing permissions setup by Plesk remain in place.

Lastly, if you still want to use something more universal like a Git user, you might just give the Plesk Forum a try, to see if someone there can help you with the Plesk permissions and provide better insight on how that is setup.

Happy New Year Ryan!
I have been looking for content on this subject and yours is hitting the nail best of all right now.
Except I have to twist it a bit for my own purposes and I too am a junior web developer type who is learning about 3 times as many things as is natural for a human… but anyway.

I am using this setup with Aegir (for managing Drupal sites). So Aegir is a user, which I’m pretty sure wants to be the main user in control of 99% of the files. In my instance, when I push changes to the server (gitolite), I am happy for Aegir to become the owner and group most of the time, being able to set an exception for some folders would be nice too, but I could get around that by having more specific git repositories.

So your comment above relating to Plesk, where the changes get pushed (or I guess checked-out?) as a specific user (Aegir in my case) sounds promising! Am I understanding that correctly? How do I do that? Or is there a better way for my particular situation?

Thanks! And thanks generally for the help you are providing me and the others here – it’s good stuff!

Yes, you understood that correctly. If you perform all of the Git operations as the Aegir user, that user becomes the owner of all the files in the repo. Using the method I describe, you can then give write permissions to the group, which another user could be a member of.

Now, in your case, you might create a remote user (git) that you can use to push your files, but then in the post-receive hook, you change the permissions to Aegir (or in case of the 1%, not). That way, you’re not logging into the system as Aegir over SSH (if that’s even possible).

Great post, lots of useful information!
I use github webhooks to trigger automatic site updating. The git pulls are run by the ‘apache’ user which is a restricted system user (UID 48) and owns all the files and dirs with umask of 022 and can’t run any shell commands. I need to make some dirs 775 so they can be written to by non apache users. Is there any way a scenario with git hooks like you outlined above will work in this case? (Otherwise I may have to just run a cron to make the change every minute and I hate that idea.)
Thanks!!

Hi Chakatz! Just to make sure I captured this right, you push your code to Github, which triggers a web hook, which makes a request to an endpoint on your site, which then runs a script (as Apache) to pull down the latest copy from Github? Is that right?