“Permission denied” is a common error I’ve come across in many web applications after the initial server setup. This would often require the devs to get help from the ops team to fix the issue. Updating the ownership/permission of specific files/folders resolves the issue.
Improper user permissions can also leave the door wide open to security vulnerabilities and lead to serious security consequences. This blogpost deals with finding a solution proper for the question: how should the ownership and permissions be setup for files/folders in the application root directory in a shared hosting environment?
The solution proposed here should be applicable to all kinds of PHP projects – both core and/or ones based on specific frameworks. For this post, we’ll be referring to the Laravel framework structure for reference, as it is the most common framework used at LiteBreeze.
Consider the following scenario. We have a website “bob.com”, with application root at “/var/www/bob” on the server. We also have a deployment user “bob” who is the owner of the application root and is the user who deploys code changes. So user “bob” needs read and write permissions to the files and folders in the root directory.
There is a PHP-FPM process setup to execute PHP scripts, which runs as user “www-data”. Hence, user “www-data” also needs read access to all files, and write access to a few files and folders. So in all, users “bob” and “www-data” would both need read and write access to files and folders in the application root directory.
Since we need multiple users to have similar permissions, we can set up a common group to which both the users can be added. We can also set the SGID permission for the application root folder so that all new files created will inherit the same common group.
One common solution used is to either add user “bob” to the group “www-data”, making “www-data” the common group, or vice versa, making “bob” the common group. The downside with the practice is that now user “bob” also has access to all files and folders that the “www-data” group has access to, instead of just the application root directory. This violates the Principle of Least Privilege and hence is not good from a security standpoint.
A good practice would be to create a new group (let’s say “www-bob”) just for this purpose, and set it as the group when creating deployment users like “bob”. We can also add user “www-data” to the group.
Another aspect to consider is the umask of the deployment users and the PHP-FPM user. This is to ensure that the group always has both read and write access to the files and folders. The default umask for non-root users in Ubuntu is 0002, but it may as well be 0022 if it has been tweaked by the server provider or a server administrator.
In such a setup, let’s say user “bob” runs a “php artisan” command on the CLI, and it throws an error. This will create a new laravel.log file if it doesn’t already exist. If the umask of “bob” were 0022, then the permissions of the laravel.log file would be 644, which means the group (“www-bob”) only has read permissions. So if the PHP-FPM process now runs a script (as user “www-data) which throws an error, it would need to write to the laravel.log file, which it does not have permission to. This would raise a “Permission denied” exception.
The umask of the PHP-FPM process is usually set to 0022 by default, which means if the laravel.log file was initially created by the application, the group “www-bob” would only have read access. So now, user “bob” cannot write to it, and his php artisan commands on the CLI can raise “Permission denied” exceptions.
To handle this issue, we will need to make sure that the umask of all deployment users (like “bob”) and the PHP-FPM user “www-data” are set such that the group always has both read and write permissions.
Summarising the solutions proposed above, we will need to implement the following:
a. rw- permissions on files
b. rwx permission on all folders
a. all directories/files inside have their group set to the common-group
b. setup SGID permission so that any new folders/files created would inherit the common-group
c. modify file and folder permissions to 660 for all files and 770 for all folders
The following is a listing of the specific commands to implement the solution proposed above. It has been tested on Ubuntu 16.04 with Nginx and PHP7.1-FPM.
1. Create a common group, to which all users can be added
sudo groupadd www-bob
2. Creating deployment users
# When creating deployment users, set their group to common-group
sudo useradd -g www-bob bob
3. Setup umask for deployment user
# Login as user and edit the ~/.profile file
sudo su - bob
vi .profile
#Edit line “#umask 022” to
umask 007
# Reload .profile after saving changes
source .profile
4. Modify PHP-FPM user group and umask
# Add the PHP-FPM user (www-data by default) to the common-group
sudo usermod -aG www-bob www-data
# Setup umask for PHP-FPM user;
# the systemctl edit command creates an override.conf file, so that changes are not lost on upgrades
sudo systemctl edit php7.1-fpm.service
# Add following
[Service]
UMask=0007
# Reload systemctl and php7.1-fpm for changes to take effect
sudo systemctl daemon-reload
sudo systemctl restart php7.1-fpm.service
5. Setup application root directory
sudo chgrp -R www-bob </path/to/public_html>
sudo chmod g+s </path/to/public_html>
find </path/to/public_html> -type d -exec chmod 770 {} \;
find </path/to/public_html> -type f -exec chmod 660 {} \;
The solution proposed above holds good until we have a server hosting multiple sites. Let’s say another project “alice.com” was also running on the same server, with users “alice” and “www-data” added to a common group “www-alice”. Now, the user “www-data” is part of both the groups “www-alice” and “www-bob”. This is a very common setup and is often the default setup on most server configurations.
This means that if a script in “alice.com” is written to get contents of a file in the application root of “bob.com” (for eg: /var/www/bob/public/.env), it would run successfully. This is because the user executing the script (“www-data”) is part of both the groups (www-bob and www-alice), and hence has read access to both the files. Security implications of such a setup can be very grave.
To handle this issue, we can use multiple PHP-FPM pool configurations. We can use separate PHP-FPM resource pools for each project, with a different user configured in each pool. This would mean there is no common “www-data” user that runs the php scripts in multiple projects.
I’m listing down the steps implemented for setting up a new PHP-FPM pool and user. I’ve tested this on Ubuntu with Nginx and PHP-FPM 7.1.
# Create new PHP-FPM pool user
sudo useradd -g www-bob php-bob
# Setup new pool
cd /etc/php/7.1/fpm/pool.d
sudo vi bob.conf
# Add the following content
[bob]
user = php-bob
group = www-bob
listen = /var/run/php7.1-fpm-bob.sock
listen.owner = www-data
listen.group = www-data
# Update Nginx – fastcgi_pass setting in virtual host configurations
server {
[...]
location ~ \.php$ {
[...]
fastcgi_pass unix:/var/run/php7.1-fpm-bob.sock;
[...]
}
[...]
}
# restart PHP-FPM
sudo service php7.1-fpm restart
# restart Nginx
sudo nginx -t
sudo service nginx restart
Process management and performance tuning using PHP-FPM pools call for a full blog post in itself (like this). But I’d like to stress on the memory usage aspect though; as laid out very well in this Stack Overflow Q&A.
Based on the complexity and traffic pattern for each site, we can define resource limits for each pool using PM configuration directives. However, we should ensure that the sum of the resources allocated to individual pools doesn’t exceed the system’s resources.