Are you an LLM? You can read better optimized documentation at /analyzers/reliability/directory-write-permissions.md for this page in Markdown format
Directory Write Permissions Analyzer
| Analyzer ID | Category | Severity | Time To Fix |
|---|---|---|---|
directory-write-permissions | ✅ Reliability | Critical | 10 minutes |
What This Checks
- Verifies that
storage/directory is writable - Verifies that
bootstrap/cache/directory is writable - Checks custom directories if configured (via published config file)
- Tests actual write permissions, not just existence
- Validates both relative and absolute paths from configuration
- Reports all failed directories with actionable fix commands
- Supports symlinked directories
- Verifies storage symlinks exist (from
config('filesystems.links')) - Detects broken symlinks (link exists but target doesn't)
- Validates symlink targets are directories
- Default: checks
public/storage→storage/app/public
Why It Matters
- Application crashes: Laravel requires writable storage for logs, sessions, cache, and compiled views - without it, your app will fail
- Silent failures: Missing write permissions can cause intermittent errors that are hard to debug in production
- Security logs: Without writable storage, security events and errors won't be logged, hiding potential attacks
- Performance degradation: Cache and compiled views require write access - without it, your app runs significantly slower
- Session management: User sessions require writable storage - login/authentication will fail without it
- File uploads: User file uploads to
storage/will fail silently or with cryptic errors - Deployment issues: Fresh deployments often fail due to incorrect directory permissions, especially in Docker/CI environments
- Broken file URLs: Without the storage symlink, publicly accessible files in
storage/app/publicreturn 404 errors - Image/file display: User-uploaded images and files won't display without proper symlinks
- Deployment failures: New deployments often forget to recreate symlinks after fresh clones
How to Fix
Quick Fix (5 minutes)
- Identify which directories are not writable:
bash
# Check current permissions
ls -la storage/
ls -la bootstrap/cache/- Fix permissions based on your environment:
Development (Local Machine):
bash
# Make directories writable
chmod -R 775 storage bootstrap/cacheProduction (Linux Server with www-data user):
bash
# Set correct owner and permissions
sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R 775 storage bootstrap/cacheDocker Container:
bash
# In your Dockerfile
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache
RUN chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cacheWindows:
powershell
# Right-click folders → Properties → Security tab
# Grant "Full Control" to your web server user
# Or run as Administrator:
icacls storage /grant Users:F /t
icacls bootstrap\cache /grant Users:F /tSymlink Issues:
bash
# Check if storage symlink exists
ls -la public/storage
# If missing or broken, recreate it
php artisan storage:link
# For custom symlinks, add to config/filesystems.php:
'links' => [
public_path('storage') => storage_path('app/public'),
public_path('uploads') => storage_path('app/uploads'),
],
# Then run:
php artisan storage:linkProper Fix (10 minutes)
- Configure deployment automation to set permissions:
yaml
# .github/workflows/deploy.yml
- name: Set Directory Permissions
run: |
chmod -R 775 storage bootstrap/cache
chown -R www-data:www-data storage bootstrap/cache- Add post-deployment script:
bash
#!/bin/bash
# deploy/fix-permissions.sh
# Set ownership
chown -R www-data:www-data storage bootstrap/cache
# Set directory permissions (775 = rwxrwxr-x)
chmod -R 775 storage bootstrap/cache
# Ensure new files inherit correct permissions
chmod g+s storage bootstrap/cache
echo "✓ Directory permissions configured"- Configure umask in your web server:
Nginx + PHP-FPM (/etc/php/8.1/fpm/pool.d/www.conf):
ini
; Set umask so new files are group-writable
php_admin_value[umask] = 0002Apache (.htaccess or VirtualHost):
apache
<IfModule mod_php.c>
php_value umask 0002
</IfModule>- Update your
.gitignoreto exclude storage files:
/storage/*.key
/storage/app/*
!/storage/app/.gitignore
/storage/framework/cache/*
!/storage/framework/cache/.gitignore
/storage/framework/sessions/*
!/storage/framework/sessions/.gitignore
/storage/framework/testing/*
!/storage/framework/testing/.gitignore
/storage/framework/views/*
!/storage/framework/views/.gitignore
/storage/logs/*
!/storage/logs/.gitignore- Configure custom writable directories (optional):
If you need to check additional directories beyond the defaults (storage and bootstrap/cache), publish the config:
bash
php artisan vendor:publish --tag=shieldci-configThen in config/shieldci.php:
php
'writable_directories' => [
'storage',
'bootstrap/cache',
'public/uploads', // If you store uploads here
'resources/compiled', // Custom compiled assets
],TIP
By default, the analyzer checks storage and bootstrap/cache directories. You only need to publish the config if you want to check additional directories.
- Include symlink creation in deployment:
yaml
# .github/workflows/deploy.yml
- name: Create Storage Symlinks
run: php artisan storage:link --forcebash
# deploy/post-deploy.sh
php artisan storage:link --force
echo "✓ Storage symlinks created"WARNING
The --force flag recreates symlinks even if they exist. Use with caution in production if you have custom symlink configurations.
- Configure symlink checking (optional):
If you want to disable symlink verification or check custom symlinks:
php
// config/shieldci.php
'check_symlinks' => true, // Set to false to disable
// Or use Laravel's filesystems config:
// config/filesystems.php
'links' => [
public_path('storage') => storage_path('app/public'),
public_path('media') => storage_path('app/media'),
],References
- Laravel Installation - Directory Permissions
- Laravel File Storage - The Public Disk
- Linux File Permissions Guide
- Docker Security Best Practices
- Nginx + PHP-FPM Configuration
Related Analyzers
- Environment File Existence Analyzer - Ensures .env file exists and is readable
- Cache Status Analyzer - Validates cache connectivity and functionality