DevOps Fundamentals April 8, 2026 · 6 min read

File Permissions: The Silent Killer of Laravel and PHP Apps

File Permissions: The Silent Killer of Laravel and PHP Apps

You've built the perfect Laravel app. Your AI coding assistant helped you craft beautiful controllers, your models are pristine, and your frontend looks incredible. You deploy to production and... 500 error. White screen of death. Your logs are either empty or screaming about permission denied errors.

Welcome to the world of file permissions - the most frustrating, hair-pulling aspect of PHP deployment that can turn a 5-minute deploy into a 3-hour debugging nightmare.

Why File Permissions Matter More in PHP

Unlike Node.js apps that typically run as a single process, PHP applications run through web servers (Apache, Nginx) that need to read, write, and execute files on behalf of different users. This creates a complex permission dance between:

  • The web server user (www-data, apache, nginx)
  • Your deployment user (your SSH user)
  • The application itself (Laravel processes)

Get this dance wrong, and your app becomes a brick.

The Classic Laravel Permission Problems

Storage and Cache Directories

The most common culprit:

# This will haunt your dreams
laravel.ERROR: The stream or file "/var/www/html/storage/logs/laravel.log" could not be opened: failed to open stream: Permission denied

Laravel needs write access to:

  • storage/logs/ - For application logs
  • storage/framework/cache/ - For view and route caching
  • storage/framework/sessions/ - For file-based sessions
  • storage/framework/views/ - For compiled Blade templates
  • storage/app/ - For file uploads and app storage
  • bootstrap/cache/ - For configuration and route caching

The Wrong Way (Don't Do This)

# Nuclear option - gives everyone everything
sudo chmod -R 777 storage/
sudo chmod -R 777 bootstrap/cache/

# Slightly less nuclear but still bad
sudo chown -R www-data:www-data /var/www/html/

While chmod 777 might "fix" your immediate problem, you've just created a security nightmare. Any process on the system can now read, write, and execute files in your storage directory.

The Right Way: Proper Permission Setup

Step 1: Set Correct Ownership

# Set ownership to your deployment user, group to web server
sudo chown -R $USER:www-data /var/www/html/

# Alternative: Both user and group as web server
# sudo chown -R www-data:www-data /var/www/html/

Step 2: Set Base Permissions

# Directories: 755 (owner: rwx, group: r-x, others: r-x)
find /var/www/html -type d -exec chmod 755 {} \;

# Files: 644 (owner: rw-, group: r--, others: r--)
find /var/www/html -type f -exec chmod 644 {} \;

Step 3: Special Permissions for Laravel

# Storage and cache need group write permissions
chmod -R 775 storage/
chmod -R 775 bootstrap/cache/

# Ensure web server can write to these directories
chgrp -R www-data storage/
chgrp -R www-data bootstrap/cache/

Step 4: Set Group Sticky Bit

# New files inherit group ownership
chmod g+s storage/
chmod g+s bootstrap/cache/

Docker Deployment Considerations

Running Laravel in Docker? You've got additional permission headaches:

The Volume Mount Problem

# This creates permission issues
VOLUME ["/var/www/html/storage"]

When you mount volumes, the host filesystem permissions clash with container permissions.

Docker Solution

# In your Dockerfile
RUN chown -R www-data:www-data /var/www/html \
    && chmod -R 755 /var/www/html \
    && chmod -R 775 /var/www/html/storage \
    && chmod -R 775 /var/www/html/bootstrap/cache

# Or use a startup script
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/bash
# entrypoint.sh
chown -R www-data:www-data /var/www/html/storage
chown -R www-data:www-data /var/www/html/bootstrap/cache
exec "$@"

Debugging Permission Issues

Check Current Permissions

# See detailed permissions
ls -la storage/
ls -la bootstrap/cache/

# Check ownership
stat storage/logs/

Test Write Permissions

# Test if web server can write
sudo -u www-data touch storage/logs/test.log
sudo -u www-data rm storage/logs/test.log

Laravel Debugging Commands

# Clear all caches (might help if permissions were fixed)
php artisan cache:clear
php artisan config:clear
php artisan view:clear
php artisan route:clear

# Check if Laravel can write to storage
php artisan storage:link

CI/CD and Automated Deployments

If you're using automated deployments, bake permission fixes into your pipeline:

GitHub Actions Example

- name: Set permissions
  run: |
    sudo chown -R $USER:www-data /var/www/html
    sudo chmod -R 755 /var/www/html
    sudo chmod -R 775 /var/www/html/storage
    sudo chmod -R 775 /var/www/html/bootstrap/cache
    sudo chmod g+s /var/www/html/storage
    sudo chmod g+s /var/www/html/bootstrap/cache

Deployment Script

#!/bin/bash
# deploy.sh

set -e

echo "Deploying Laravel application..."

# Pull latest code
git pull origin main

# Install dependencies
composer install --no-dev --optimize-autoloader

# Fix permissions
chown -R $USER:www-data .
chmod -R 755 .
chmod -R 775 storage bootstrap/cache
chmod g+s storage bootstrap/cache

# Clear caches
php artisan config:cache
php artisan route:cache
php artisan view:cache

echo "Deployment complete!"

Prevention: Getting It Right From the Start

Use ACLs (Access Control Lists)

For more complex permission scenarios:

# Set default ACLs
sudo setfacl -R -m u:www-data:rwx storage/
sudo setfacl -R -d -m u:www-data:rwx storage/
sudo setfacl -R -m u:$USER:rwx storage/
sudo setfacl -R -d -m u:$USER:rwx storage/

Environment-Specific Configs

Different environments need different approaches:

# Development (more permissive)
chmod -R 777 storage/ bootstrap/cache/

# Staging/Production (restrictive)
chmod -R 775 storage/ bootstrap/cache/
chown -R deploy:www-data .

The Nuclear Option: When All Else Fails

If you're still fighting permission issues and need to ship:

# Last resort - but fix this properly later
sudo chmod -R 777 storage/
sudo chmod -R 777 bootstrap/cache/

# Then immediately add to your backlog:
# TODO: Fix file permissions properly

Managed Hosting: Skip the Headache

Honest talk? File permissions are a pain. If you're spending more time fighting chmod than building features, maybe it's time to let someone else handle this.

Managed Laravel hosting services handle permissions, server configuration, and deployment headaches so you can focus on what you do best - building amazing apps with your AI coding buddy.

Your time is better spent crafting user experiences, not debugging why www-data can't write to a log file.

Wrapping Up

File permissions don't have to be the boss of you. Get them right once, automate them in your deployment pipeline, and move on to more interesting problems.

Remember:

  • Never use 777 in production
  • Always test permissions after deployment
  • Automate permission fixes in your CI/CD
  • When in doubt, check ownership first

Now go forth and deploy with confidence. Your Laravel app (and your sanity) will thank you.

Alex Hackney

Alex Hackney

DeployMyVibe

Ready to deploy?

Stop reading about it. Start shipping.

View Pricing