Laravel file permissions
Setting correct file permissions for Laravel is surprisingly tricky.
If you run Laravel on a server that you've provisioned yourself, then you've probably had your fair share of file permission errors.
I've followed other online guides on how to set correct file permissions in the past.
Every time I did, I still ended up with unpredictable errors.
As it turns out, all the solutions I can find online aren't completely correct.
They only solve some of the problems, but not all of them.
In this post, I've documented how to actually set correct file permissions for Laravel.
If you want to save yourself the hassle, the Deploy Laravel deployment script properly sets file permissions automatically as described in this post.
The variables in this post are dynamic. You can edit them below to match the values of your server. Once set, you can copy-paste the bash commands in this post straight into your terminal.
Why file permission errors happen
File permission errors happen because two different users create files and directories in your project.
You deploy your code with user "sjors".
You probably also run the cron and queue workers with user "sjors".
Your Nginx/Apache2 uses user "www-data" to serve web requests.
For example, user "sjors" creates the laravel.log
file.
This file now belongs to user "sjors", and also belongs to the "sjors" group.
Later, user "www-data" also wants to write to the log.
But "www-data" doesn't own the file, and isn't part of the group, so he isn't allowed to write to the file.
This causes the The stream or file "/var/www/project/storage/logs/laravel.log" could not be opened in append mode: Failed to open stream: Permission denied
error.
The bootstrap/cache directory is another source of problems.
User "sjors" creates this directory during deployments.
User "www-data" needs to create files inside that directory, but has no write permissions for the directory.
This causes Laravel to throw the The /var/www/project/bootstrap/cache directory must be present and writable
error.
How to fix file permissions problems
So, the problem stems from using two users that aren't in the same group.
The solution to this problem sounds easy enough, just add both users to the same group.
As usual, the solution isn't that simple.
Files and directories don't get group write permissions by default.
So even if both are in the same group, you'll still have the same problems.
The key to fixing these file permission errors is to make sure that files and directories get group write permissions by default.
The steps below will explain how to achieve this.
All the commands in this post use sudo.
Your "www-data" user probably has already created files and directories.
The only way to change their permissions is by using sudo.
Step 1: Add your user to the "www-data" group
First of all, we have to add our "sjors" user to the "www-data" group:
sudo usermod -aG "www-data" "sjors"
Step 2: Set defaults for new files using setfacl
New files don't have group write permissions by default.
We can use setfacl
to change these defaults.
If setfacl
isn't installed on your server, you can install it using sudo apt install acl
.
To give new files group write permissions by default, run the following command:
sudo find -L "/var/www/project" -type d -not -path "*/vendor/*" -not -path "*/node_modules/*" -exec setfacl --default -m g::rwX {} \;
Step 3: Set existing files to 664
The setfacl
command above gives 664 permissions to new files by default.
We also have the change the permissions of existing files to 664.
The 66 gives read and write permissions to the owner and group.
The 4 gives other users read permissions.
Files don't need execute permissions, so we don't give them any.
sudo find -L "/var/www/project" -type f -not -path "*/vendor/*" -not -path "*/node_modules/*" -exec chmod 664 {} \;
Step 4: Change the owner and group of existing files
All files should be owned by user "sjors". The group of all files should be set to "www-data".
sudo find -L "/var/www/project" -type f -not -path "*/vendor/*" -not -path "*/node_modules/*" -exec chown "sjors":"www-data" {} \;
Step 5: Set existing directories to 2775
We set the permissions of all existing directories to 2775.
The 2, also called the setgid bit, makes all new directories inherit the group of their parent directory.
Without the setgid, directories created by "sjors" won't be writeable by "www-data".
The 77 gives read, write, and execute permissions to the owner and the group.
The 5 gives read and execute permissions to other users.
Remember that execute permissions for directories means a user is allowed to "cd" into the directory.
sudo find -L "/var/www/project" -type d -not -path "*/vendor/*" -not -path "*/node_modules/*" -exec chmod 2775 {} \;
Step 6: Change the owner and group of existing directories
All existing directories should be owned by user "sjors". The group of all existing directories should be set to "www-data".
sudo find -L "/var/www/project" -type d -not -path "*/vendor/*" -not -path "*/node_modules/*" -exec chown "sjors":"www-data" {} \;
Step 7: Set default permissions in your filesystems.php
config file
This is the last piece of the puzzle.
We have to set permissions for the local
and public
disk in the filesystems.php
config file.
If we don't set these permissions, directories created by the Storage
facade won't get group write permissions.
Edit your filesystems.php
config file, and set the public permissions to 0775:
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'permissions' => [
'file' => [
'public' => 0775,
'private' => 0600,
],
'dir' => [
'public' => 0775,
'private' => 0700,
],
],
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'permissions' => [
'file' => [
'public' => 0775,
'private' => 0600,
],
'dir' => [
'public' => 0775,
'private' => 0700,
],
],
],
],
File permissions after a new deployment
After deploying new code, you have to set correct file permissions again. The newly deployed files won't inherit the defaults we set earlier. You should repeat steps 2 through 6 after each deployment.