Managing file and folder permissions on CentOS when deploying with Git
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!)
-rw-rw-r-- 1 git git 30 Aug 15 23:04 test-file.txt drwxrwxr-x 1 git git 102 Aug 15 23:04 test-directory
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.
- 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.
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
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
Let’s elevate our permissions to root and create a new group:
[[email protected] ~]$ sudo su - [[email protected] ~]$ groupadd web
Next we’ll add both git and apache to that group:
[[email protected] ~]$ usermod -a -G web git [[email protected] ~]$ usermod -a -G web apache
Last, we’ll set the setgid bit on the
[[email protected] ~]$ ll /var/www/domains/domain.com/www drwxrwxr-x 5 git git 4096 Aug 15 23:56 htdocs [[email protected] ~]$ chgrp web /var/www/domains/domain.com/www/htdocs [[email protected] ~]$ chmod g-w /var/www/domains/domain.com/www/htdocs [[email protected] ~]$ chmod g+s /var/www/domains/domain.com/www/htdocs [[email protected] ~]$ ll /var/www/domains/domain.com/www drwxr-sr-x 5 git web 4096 Aug 15 23:56 htdocs
- Print out current permissions for
- Set the group of
- Remove write permissions for the group on
- Set the setgid bit on
- Print out new permissions (note the ‘s’ flag in the 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.
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
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.
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.
As a side note, if you want to learn more about
umask, there is a great article about what umask is and how it works.
There is a file called
profile which sets a default
umask value for all system users:
[[email protected] ~]# cat /etc/profile ... # By default, we want umask to get set. This sets it for login shell # Current threshold for system reserved uid/gids is 200 # You could check uidgid reservation validity in # /usr/share/doc/setup-*/uidgid file if [ $UID -gt 199 ] && [ "`id -gn`" = "`id -un`" ]; then umask 002 else umask 022 fi ...
You’ll notice that by default, all users with a user ID of 200 or greater will receive a
002, and all users with ID 199 or less, a
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
.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.
Let’s edit the file now:
[[email protected] ~] su git [[email protected] ~]$ vi ~/.bashrc ... # User specific aliases and functions umask 022
- Switch over to the git user
- Edit the
.bashrcfile in the home directory
umask 022to the bottom
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:
# Set path to htdocs htdocs="/var/www/domains/domain.com/www/htdocs" # Unset global GIT_DIR so script can leave repository unset GIT_DIR # Move into htdocs cd $htdocs # If cache directory does not exist if [ ! -d "$htdocs/cache" ]; then # Create cache directory for cache files mkdir $htdocs/cache # Allow Apache to write to cache chmod g+w $htdocs/cache fi
That’s pretty much it, and it meets all of our goals. If you have any questions or comments, leave them below.
December 30, 2013 Update: I recently wrote an article on how to deploy WordPress as a submodule that showcases a more complete post-receive hook, which may be of interest to you.
Featured image by Roman Synkevych.
Previously posted in WordPress and transferred to Ghost.
December 29, 2013 at 6:43 am
Greetings from Kuala Lumpur, Malaysia
Merry Xmas and a happy new year!
This is a really well written and well explained article. An awesome work!
December 29, 2013 at 8:10 pm
Thanks, glad it was helpful!
March 26, 2014 at 11:47 am
thanks, that was what I’m looking for!
St. Louis ist eine gute Wahl – wunderschöne Stadt.
All the best from cold Bremen, Germany
March 26, 2014 at 11:57 am
Great, glad it was helpful! Come to think of it, I haven’t been back to Germany in 7 something years. I really do miss it!
December 5, 2014 at 7:05 am
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:
drwxr-sr-x 2 git web 4096 Dec 5 12:49 httpdocs
Should I change the permissions on the webroot folder?
December 5, 2014 at 7:49 am
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 –
psaservbut this still does not give the git user any permissions to cd into the directories!
December 5, 2014 at 11:26 am
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
clientnameuser, 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.
December 13, 2014 at 5:37 am
Sounds great… only problem is my debian server doesn’t have a “git” user.
December 13, 2014 at 9:10 pm
Ah, good point, I should have mentioned that this user can just be created. For Debian, take a look at this: http://www.debian.org/doc/manuals/system-administrator/ch-sysadmin-users.html
December 31, 2014 at 6:16 pm
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!
January 1, 2015 at 3:19 am
Happy New Year to you, too!
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-receivehook, 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).
April 15, 2015 at 5:04 pm
I followed your tutorial, but now my file ownership is the following:
drwxr-s--- 4 git web 4096 Apr 15 22:02 . -rw-r--r-- 1 git web 427 Apr 15 22:02 index.php
and my web group /apache user is not allowed to write to any file (e.g.
wp-content). What do I need to change?
April 16, 2015 at 9:24 am
The file permissions (
-rw-r--r--) are correct and directories should be
First, I’d double check that
apachehas been added to the
$ groups apache apache : apache web
Second, add write permissions to any directory that you want Apache to write to, such as
wp-content, if you want to make the entire directory writable (I personally would limit that even further):
chmod g+w wp-content
March 24, 2016 at 4:31 am
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.)
March 24, 2016 at 5:41 pm
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?
If so, what is the problem you’re trying to solve?