Your app runs perfectly on your laptop. You upload it to the server and get a blank page, a 500 error, or worse, a debug screen that shows your database password to every visitor. Most of these incidents are not caused by broken code. They are caused by a preparation step someone skipped.
This article is a checklist you can reuse every time you deploy Laravel source code, whether you wrote it yourself or bought it. It follows the real order of work: before upload, during deployment, and after the app goes live.
Why Local and Production Behave Differently
A development machine usually has a recent PHP version, every extension enabled, debug mode on, and you own every folder. A production server is often the opposite: a different PHP version, missing extensions, and a web server running as a separate user with limited permissions. That gap explains most deployment failures.
| Setting | Local | Production |
|---|---|---|
| APP_ENV | local | production |
| APP_DEBUG | true | false, always |
| Dependencies | includes dev packages | installed with --no-dev |
| Config/route/view cache | usually off | on, for faster boot |
| Errors | shown in the browser | written to logs, users see a generic page |
| HTTPS | optional | required |
Stage 1: Before You Upload
Match PHP version and extensions
Laravel 11 requires PHP 8.2 or newer. Also confirm the extensions most apps need: pdo_mysql (or pdo_pgsql), mbstring, openssl, tokenizer, xml, ctype, fileinfo, and bcmath if the app handles money. Run php -v and php -m on the server and compare with composer.json.
Create a production .env
Do not copy your local file. Start from .env.example and fill in production values:
APP_NAME="My Store"
APP_ENV=production
APP_DEBUG=false
APP_URL=https://mystore.com
LOG_CHANNEL=daily
LOG_LEVEL=warning
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=mystore_prod
DB_USERNAME=mystore_app
DB_PASSWORD=use-a-long-random-password
SESSION_DRIVER=database
SESSION_SECURE_COOKIE=true
QUEUE_CONNECTION=database
APP_DEBUG=false stops stack traces and environment values from leaking. A correct APP_URL keeps links in emails and redirects from pointing at localhost. SESSION_SECURE_COOKIE=true sends session cookies only over HTTPS. Generate APP_KEY once with php artisan key:generate. Never regenerate it on a live server, or encrypted data and existing sessions become unreadable.
Use a dedicated database user
Never run the app as root. Create a user with privileges on one database only, so a future SQL injection bug cannot reach anything else.
Stage 2: During Deployment
On a server with SSH access, keep the steps in a script so every deploy is identical:
#!/bin/bash
set -e
php artisan down --retry=60
git pull origin main
composer install --no-dev --optimize-autoloader --no-interaction
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
php artisan storage:link || true
php artisan queue:restart
php artisan up
- down shows a maintenance page so users never see a half-installed app.
- composer install --no-dev installs exactly what is in
composer.lock. Never runcomposer updatein production; it can pull package versions you have not tested. - migrate --force is needed because Laravel asks for confirmation in production.
- config:cache and route:cache compile configuration and routes into single files for faster boot.
- queue:restart tells workers to reload. Without it, they keep running the old code.
One trap with config:cache: once config is cached, any env() call outside the config/ folder returns null. Move such values into a config file and read them with config(). If you deploy to shared hosting without SSH, see deploying a Laravel app to shared hosting.
Set folder permissions
Only storage/ and bootstrap/cache/ need to be writable by the web server:
sudo chown -R deploy:www-data /var/www/mystore
sudo find /var/www/mystore -type f -exec chmod 644 {} \;
sudo find /var/www/mystore -type d -exec chmod 755 {} \;
sudo chmod -R 775 /var/www/mystore/storage /var/www/mystore/bootstrap/cache
Avoid chmod 777. It hides the error but lets any process on the server write into your app.
Point the document root at public/
If the project root is exposed, .env can be downloaded from a browser. Test by opening https://yourdomain.com/.env; you should get a 403 or 404.
Stage 3: Backups and a Rollback Plan
Take the backup before you deploy. At minimum, dump the database and copy storage/app, which holds user uploads:
mysqldump -u mystore_app -p --single-transaction mystore_prod | gzip > backup_$(date +%F_%H%M).sql.gz
Write the rollback plan in a few lines: which tag is the last stable release, the command to return to it, and whether the new migrations can be reversed. A migration that drops a column cannot be undone without a backup, so split risky schema changes across several releases. More on that in safe database migrations in Laravel.
Stage 4: After Go-Live
Before announcing the link, spend 15 to 30 minutes on the critical paths:
- Register a new account and confirm the verification email arrives.
- Log in, log out, and reset a password.
- Complete one full transaction in sandbox mode if payments are involved.
- Upload a file or image and make sure it displays.
- Open the admin panel and the most-used report.
Then watch storage/logs for the first few hours. Problems like timeouts to third-party APIs often appear only under real traffic. If the app uses the scheduler, install the cron entry: * * * * * cd /var/www/mystore && php artisan schedule:run >> /dev/null 2>&1.
Common Errors and Fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| 500 error with no message | storage not writable or missing APP_KEY | Fix permissions, set APP_KEY, read the log |
| .env changes ignored | config is cached | config:clear, then config:cache |
| CSS/JS missing | assets not built or wrong APP_URL | Run npm run build, upload public/build |
| Uploaded images return 404 | storage symlink missing | php artisan storage:link |
| 419 on form submit | session cookie issue over HTTPS | Check SESSION_DOMAIN and SESSION_SECURE_COOKIE |
Printable Checklist
- [ ] Server PHP version and extensions match the app
- [ ] Production .env: APP_ENV=production, APP_DEBUG=false, correct APP_URL
- [ ] APP_KEY set once and never rotated casually
- [ ] Dedicated database user, not root
- [ ] Document root is public/, .env is not reachable
- [ ] HTTPS enabled with HTTP redirect
- [ ] Default demo credentials changed
- [ ] composer install --no-dev, then config/route/view cache
- [ ] Correct permissions on storage and bootstrap/cache, no 777
- [ ] Database and storage/app backed up before migrating
- [ ] Scheduler cron and queue worker running
- [ ] Critical paths tested, logs watched for a few hours
For a broader security review, pair this list with 10 web application security best practices. The same checklist applies to any Laravel source code, including apps from GudangCode.