Laravel file permissions

Published on

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.

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.

You should use an absolute path.
You can't use "~" in this path.

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.

If you want to automate your deployment, check out my Laravel deployment script. This script runs a perfect deployment, and guarantees that you won't get any file permissions problems.

Deploy Laravel with GitHub Actions

Check out my Laravel deployment script for GitHub Actions

Check it out