Laravel 500 Error After Deploy: Fix Permissions & .env Issues
You've just pushed your Laravel app to a live server - cPanel, a VPS, doesn't matter - and instead of your app, you get a blank page or a plain "500 Internal Server Error". No stack trace, no hint. Here's how to actually find out what broke and fix it, instead of guessing.
Symptom
You deploy (git pull, zip upload, or CI script), open the domain, and get one of these:
- A blank white page with nothing in it
- "500 Internal Server Error" with no detail
- "The stream or file could not be opened" mentioning
storage/logs/laravel.log - "No application encryption key has been specified"
- The site loads but CSS/JS is missing and links 404
Laravel hides errors in production by default (APP_DEBUG=false), which is correct for security - but it also means the generic 500 page tells you nothing. You have to go looking.
Step 1: Find the real error
Don't guess - read the log. SSH in (or use cPanel Terminal) and tail Laravel's own log first:
tail -n 50 storage/logs/laravel.logIf that file doesn't exist or is empty, the problem is happening before Laravel even boots - check the web server's error log instead:
# cPanel / shared hosting
tail -n 50 ~/logs/yourdomain.com/error_log
# VPS with Nginx + PHP-FPM
tail -n 50 /var/log/nginx/error.log
tail -n 50 /var/log/php8.2-fpm.logIf you need the actual error on screen instead of digging through logs, temporarily set APP_DEBUG=true in .env, reload, and read the trace. Turn it back to false immediately after - a debug page in production leaks your config, database credentials, and file paths to anyone who hits a broken route.
Common causes and fixes
1. Storage and cache folders aren't writable
By far the most common cause. Laravel writes logs, compiled views, and session/cache files into storage/ and bootstrap/cache/. If the web server user (usually www-data, nobody, or your cPanel account user) can't write there, you get exactly this kind of silent 500.
chmod -R 775 storage bootstrap/cache
chown -R $USER:www-data storage bootstrap/cache
# on a fresh clone, make sure these exist too
mkdir -p storage/framework/{sessions,views,cache}
mkdir -p storage/logsOn a VPS running PHP-FPM under a dedicated pool user, replace www-data with whatever user your pool actually runs as - check ps aux | grep php-fpm if you're not sure.
2. .env is missing or has stale values
.env should never be committed to git (it's in .gitignore by default), which means every fresh deploy needs one created manually or by your deploy script. If it's missing entirely, Laravel can't read the database connection, mail settings, or app key, and it fails before rendering anything.
cp .env.example .env
php artisan key:generateDouble-check the values that actually change between environments - APP_URL, DB_HOST, DB_DATABASE, DB_USERNAME, DB_PASSWORD. A leftover DB_HOST=127.0.0.1 pointing at your local machine is a classic copy-paste mistake.
3. No application key set
If the log says No application encryption key has been specified, the fix is one command:
php artisan key:generateThis writes a fresh APP_KEY into .env. If you already had one and it went missing, check that your deploy process isn't overwriting .env with a template on every push.
4. vendor/ folder missing or out of date
If you deployed by zipping your project folder and vendor/ wasn't included (or you deployed via git and never ran Composer on the server), Laravel's autoloader can't find any of its classes - you'll see Class 'X' not found errors.
composer install --no-dev --optimize-autoloaderUse --no-dev in production - it skips testing/debug packages you don't need live, and keeps the deployed footprint smaller.
5. Document root points at the wrong folder
Laravel's entire app lives outside the web root except for public/. If your domain's document root is set to the project's top-level folder instead of project/public, visitors either see a directory listing of your raw source code (a serious security problem) or a 500/404 because there's no working index.php at that level.
- cPanel: in WHM/cPanel, set the domain's document root to
/home/user/laravel-app/public, or symlinkpublic_htmlto point at the app'spublicfolder. - Nginx on a VPS: set
rootin your server block to the app'spublicdirectory, not the project root:
server {
listen 80;
server_name example.com;
root /var/www/laravel-app/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}6. Stale cached config after changing .env
If you ran php artisan config:cache at some point (common in deploy scripts), Laravel stops reading .env directly and uses the cached file instead. Change a value in .env after that and nothing happens until you clear it:
php artisan config:clear
php artisan cache:clear
php artisan route:clear
php artisan view:clear
# then re-cache for production performance
php artisan config:cache
php artisan route:cacheQuick reference
| Error message / symptom | Likely cause | Fix |
|---|---|---|
| Blank 500, nothing in laravel.log | Wrong document root or missing vendor/ | Point root at public/, run composer install |
| "stream or file could not be opened": storage/logs | storage/ not writable | chmod -R 775 storage bootstrap/cache |
| "No application encryption key has been specified" | Missing APP_KEY | php artisan key:generate |
| SQLSTATE connection refused | Wrong DB_HOST/credentials in .env | Fix .env, then config:clear |
| .env edits have no effect | Stale config cache | php artisan config:clear |
| CSS/JS missing, assets 404 | Wrong APP_URL or unbuilt assets | Set correct APP_URL, run npm run build |
Prevention
- Keep a deploy checklist (or script) that always runs:
composer install --no-dev,php artisan migrate --force, cache clear + re-cache, and a permissions fix onstorage/andbootstrap/cache. - Never commit
.env- keep a.env.productiontemplate outside git that you copy in during deploy instead. - Set
APP_DEBUG=falseon every production deploy, and rely onstorage/logs/laravel.logfor debugging, not the browser. - If you deploy often, script the cache-clear-then-recache sequence so a forgotten
config:cachenever masks a real .env change again.
Frequently asked questions
Why does my Laravel site show a blank page instead of an error?
With APP_DEBUG=false (the correct production setting), PHP fatal errors render as a blank page or generic 500 instead of a stack trace. Check storage/logs/laravel.log or your web server's error log to see what actually happened.
Is it safe to leave APP_DEBUG=true on my live site?
No. Debug mode exposes your full stack trace, environment variables, database credentials, and file paths to anyone who triggers an error. Only enable it briefly while diagnosing, then set it back to false.
Do I need a VPS to run Laravel, or does cPanel shared hosting work?
Laravel runs fine on cPanel shared hosting as long as PHP is 8.1+ and you can set the domain's document root to the app's public folder. A VPS gives you more control (custom PHP-FPM pools, queue workers, scheduled tasks via real cron) which larger Laravel apps eventually need.
What permissions should storage/ and bootstrap/cache/ actually have?
775 is the safe default - owner and group get write access, others get read/execute. Avoid 777; it's rarely necessary and it's an easy target if the server is ever compromised.
My composer install fails on the server - why does it work locally but not here?
Usually a PHP version mismatch (check composer.json's require.php against the server's active PHP version) or a memory limit hit during dependency resolution. Run composer install -vvv to see the real error, and bump memory_limit temporarily if it's an out-of-memory failure.