Laravel File Permissions & Security Best Practices for Shared Hosting and VPS

Nawjesh Soyeb .htaccess File Permissions Laravel Security Shared Hosting VPS

Wrong file permissions are one of the top two ways Laravel apps break or get breached in production — either too locked-down to run, or wide open enough for anyone on the server to read your .env. This guide gives exact permission levels for every key folder, separate command sets for VPS and shared hosting, and the .htaccess rules that keep sensitive files out of reach

A misconfigured permission or an exposed .env file is one of the most common ways Laravel apps get compromised in production. This guide covers correct permissions for every important folder, how to lock things down differently on shared hosting vs a VPS, and the .htaccess rules that stop people from reaching files they shouldn't.

Why This Matters

Two opposite mistakes happen constantly:

  • Too permissive (777 everywhere) — anyone on a shared server, or any compromised process, can read/write/execute your files, including .env with your DB password and API keys
  • Too restrictive — Laravel can't write to storage/ or bootstrap/cache/, causing 500 errors

The goal is the minimum permission each folder actually needs.

Correct Permission Levels

Path Recommended Why
Project root (app/, routes/, config/, etc.) 755 (dirs), 644 (files) Readable/executable, not writable by others
storage/ (all subfolders) 775 on VPS, 755 on shared hosting* Laravel writes logs, cache, sessions, compiled views here
bootstrap/cache/ 775 on VPS, 755 on shared hosting* Laravel writes cached config/routes here
.env 600 (VPS) or 644 (shared, if 600 breaks it) Only the owner/web server should read it
public/ 755 (dirs), 644 (files) Publicly served, but not writable
vendor/, .git/ Not publicly accessible at all Should sit outside the web root, or be blocked via .htaccess

*Shared hosting often runs PHP as the same user that owns the files (suPHP/FastCGI), so 755/644 is usually enough and safer than 775. On a VPS where PHP-FPM runs as www-data and you deploy as another user, 775 with correct group ownership is typically needed. Never use 777 in either case — it means "anyone can write," including other tenants on shared hosting.

Commands: VPS (Linux, SSH access)

Set ownership first — replace youruser and www-data with your actual deploy user and web server user:

# Set ownership: your user owns files, web server group can write to storage/cache
sudo chown -R youruser:www-data /var/www/your-project

Set base permissions across the whole project:

cd /var/www/your-project
sudo find . -type f -exec chmod 644 {} \;
sudo find . -type d -exec chmod 755 {} \;

Then open up write access only where Laravel needs it:

sudo chmod -R 775 storage bootstrap/cache
sudo chgrp -R www-data storage bootstrap/cache

Lock down .env:

sudo chmod 600 .env
sudo chown youruser:youruser .env

Make artisan executable if you run it directly:

sudo chmod +x artisan

Commands: Shared Hosting (Namecheap, Hostinger — cPanel/File Manager or limited SSH)

If you have terminal access via cPanel's "Terminal" feature:

find . -type f -exec chmod 644 {} \;
find . -type d -exec chmod 755 {} \;
chmod -R 755 storage bootstrap/cache
chmod 644 .env

If you only have File Manager (no terminal):

  1. Right-click storage/ folder → Permissions → set to 755 → check "Recurse into subdirectories"
  2. Right-click bootstrap/cache/ → same, 755
  3. Right-click .env → Permissions → 644
  4. If you get "permission denied" errors in storage/logs/laravel.log after setting 755, bump only the specific subfolder that's failing to 775 rather than opening the whole project

Protecting .env and Sensitive Files via .htaccess

Even with correct file permissions, if your document root is misconfigured, someone could potentially request yourdomain.com/.env directly. Block it explicitly in your root .htaccess:

# Block access to .env and other sensitive files
<FilesMatch "^\.env">
    Order allow,deny
    Deny from all
</FilesMatch>

<FilesMatch "\.(env|log|sqlite|sql|json|lock|yml|yaml|md|gitignore|gitattributes)$">
    Order allow,deny
    Deny from all
</FilesMatch>

# Block access to sensitive folders
RewriteRule ^(app|bootstrap|config|database|resources|routes|storage|tests|vendor)/ - [F,L]

For Apache 2.4+ syntax (some newer shared hosts require this instead of Order allow,deny):

<FilesMatch "^\.env">
    Require all denied
</FilesMatch>

<FilesMatch "\.(env|log|sqlite|sql|json|lock|yml|yaml|md)$">
    Require all denied
</FilesMatch>

Inside public/.htaccess, also block direct access to config-like files that shouldn't be there:

<FilesMatch "\.(env|log)$">
    Require all denied
</FilesMatch>

Additional Security Hardening

Never commit .env to Git. Confirm .gitignore includes it:

.env
.env.backup
/storage/*.key
/vendor
/node_modules

Disable directory listing so people can't browse folders that lack an index.php:

Options -Indexes

Force HTTPS in .htaccess (root or public/):

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Set security headers (add to public/.htaccess):

Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "SAMEORIGIN"
Header set X-XSS-Protection "1; mode=block"
Header set Referrer-Policy "strict-origin-when-cross-origin"

Turn off debug mode in production — always confirm in .env:

APP_DEBUG=false
APP_ENV=production

Leaving APP_DEBUG=true in production exposes full stack traces, file paths, and sometimes .env values directly in error pages — this alone has led to real breaches.

Rotate APP_KEY and credentials if you ever suspect .env was exposed:

php artisan key:generate

Then update your database password, API keys, and mail credentials, since the old key and secrets should be treated as compromised.

Quick Checklist

  • .env set to 600 (VPS) or 644 (shared), never 777
  • storage/ and bootstrap/cache/ writable only where needed (775/755, not 777)
  • .env, .git, vendor, config files blocked via .htaccess
  • ✓ Directory listing disabled (Options -Indexes)
  • ✓ HTTPS forced
  • ✓ Security headers added
  • APP_DEBUG=false and APP_ENV=production confirmed
  • .env never committed to Git

Getting permissions and .htaccess rules right up front avoids both the "500 error because storage isn't writable" problem and the far worse "database credentials leaked" problem — the two failure modes usually come from overcorrecting for each other.