This guide covers best practices for deploying your Laravel application to production environments.
Pre-Deployment Checklist
Before deploying to production, ensure you have:
Environment Configuration
Proper environment configuration is critical for security and performance in production.
Production .env Settings
Update your .env file with production-appropriate values:
APP_NAME="Your Application Name"
APP_ENV=production
APP_KEY=base64:your-generated-key-here
APP_DEBUG=false
APP_URL=https://yourdomain.com
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
LOG_CHANNEL=stack
LOG_LEVEL=error
LOG_DEPRECATIONS_CHANNEL=null
# Database
DB_CONNECTION=mysql
DB_HOST=your-db-host
DB_PORT=3306
DB_DATABASE=your_production_db
DB_USERNAME=your_db_user
DB_PASSWORD=your_secure_password
# Session & Cache
SESSION_DRIVER=redis
SESSION_LIFETIME=120
SESSION_ENCRYPT=true
SESSION_PATH=/
SESSION_DOMAIN=.yourdomain.com
CACHE_STORE=redis
QUEUE_CONNECTION=redis
# Redis
REDIS_CLIENT=phpredis
REDIS_HOST=your-redis-host
REDIS_PASSWORD=your_redis_password
REDIS_PORT=6379
# Mail
MAIL_MAILER=smtp
MAIL_HOST=smtp.yourmailserver.com
MAIL_PORT=587
MAIL_USERNAME=your_smtp_username
MAIL_PASSWORD=your_smtp_password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="[email protected]"
MAIL_FROM_NAME="${APP_NAME}"
# Broadcasting
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
# AWS (if using S3)
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=your_bucket_name
AWS_USE_PATH_STYLE_ENDPOINT=false
Critical Security Settings
Must be set to false in productionWhen APP_DEBUG=true, detailed error messages expose:
- Database credentials
- File paths
- Environment variables
- Stack traces
Leaving debug mode enabled in production is a critical security vulnerability.
Must be set to productionThis ensures:
- Migrations require the
--force flag
- Error reporting is minimized
- Optimization is prioritized
Must be unique and securely generatedAPP_KEY=base64:your-unique-generated-key
Generate a new key for production:Never reuse development keys in production. Never commit keys to version control.
Deployment Steps
1. Server Preparation
Ensure your server has:
# PHP 8.2+ with required extensions
php -v
php -m # Verify extensions: openssl, pdo, mbstring, tokenizer, xml, ctype, json, bcmath
# Composer
composer --version
# Web server (Nginx/Apache)
nginx -v
# or
apache2 -v
2. Clone Repository
cd /var/www
git clone https://github.com/yourusername/your-repo.git your-app
cd your-app
3. Install Dependencies
composer install --optimize-autoloader --no-dev
The --no-dev flag excludes development dependencies, and --optimize-autoloader improves autoloader performance.
4. Environment Configuration
cp .env.example .env
nano .env # Edit with production values
5. Generate Application Key
6. Set File Permissions
chown -R www-data:www-data /var/www/your-app
chmod -R 755 /var/www/your-app
chmod -R 775 /var/www/your-app/storage
chmod -R 775 /var/www/your-app/bootstrap/cache
Replace www-data with your web server user (e.g., nginx, apache).
7. Run Migrations
php artisan migrate --force
The --force flag is required when APP_ENV=production.
8. Optimize Application
# Cache configuration
php artisan config:cache
# Cache routes
php artisan route:cache
# Cache views
php artisan view:cache
# Cache events
php artisan event:cache
These optimization commands significantly improve application performance by caching configuration, routes, and views.
9. Build Frontend Assets
npm install
npm run build
server {
listen 80;
listen [::]:80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
root /var/www/your-app/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
}
11. Start Queue Workers (Optional)
If using queues, set up a queue worker:
php artisan queue:work --daemon
For production, use a process manager like Supervisor:
/etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/your-app/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/your-app/storage/logs/worker.log
stopwaitsecs=3600
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*
Database Migration Strategy
Zero-Downtime Migrations
Backward Compatible Migrations
Write migrations that are backward compatible:
- Adding columns: Make new columns nullable or provide defaults
- Removing columns: Deploy code that doesn’t use the column first, then remove it
- Renaming columns: Add new column, copy data, deprecate old column, then remove
// Good: Backward compatible
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('phone')->nullable()->after('email');
});
}
Always test migrations in a staging environment:# Staging environment
php artisan migrate --pretend # Preview SQL
php artisan migrate # Apply
php artisan migrate:status # Verify
Have a rollback plan ready:# Rollback last migration batch
php artisan migrate:rollback
# Rollback specific steps
php artisan migrate:rollback --step=1
Optimization Commands
Clear All Caches
php artisan optimize:clear
This clears:
- Configuration cache
- Route cache
- View cache
- Event cache
- Compiled classes
Rebuild Caches
This caches:
- Configuration
- Routes
- Events
Run php artisan optimize after each deployment to ensure optimal performance.
Monitoring & Logging
Log Configuration
LOG_CHANNEL=stack
LOG_LEVEL=error
In production, set LOG_LEVEL to error to reduce log volume:
debug: Most verbose (development only)
info: Informational messages
notice: Normal but significant conditions
warning: Warning conditions
error: Error conditions (recommended for production)
critical: Critical conditions
alert: Action must be taken immediately
emergency: System is unusable
View Logs
tail -f storage/logs/laravel.log
Log Rotation
Configure daily log rotation:
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'error'),
'days' => 14,
],
Security Considerations
Security must be a top priority in production deployments.
1. Environment File Security
# Restrict .env file permissions
chmod 600 .env
chown www-data:www-data .env
2. Hide Sensitive Files
Ensure .env, .git, and other sensitive files are not accessible:
location ~ /\.(?!well-known).* {
deny all;
}
3. HTTPS Only
Enforce HTTPS:
app/Http/Middleware/TrustProxies.php
protected $headers = Request::HEADER_X_FORWARDED_ALL;
'secure' => env('SESSION_SECURE_COOKIE', true),
'same_site' => 'lax',
4. Rate Limiting
Implement rate limiting to prevent abuse:
Route::middleware(['throttle:60,1'])->group(function () {
// API routes
});
5. CORS Configuration
Configure CORS appropriately:
'allowed_origins' => ['https://yourdomain.com'],
'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE'],
Continuous Deployment
Deployment Script
Create a deployment script for consistent deployments:
#!/bin/bash
set -e
echo "Starting deployment..."
# Pull latest code
git pull origin main
# Install dependencies
composer install --optimize-autoloader --no-dev
# Run migrations
php artisan migrate --force
# Clear and rebuild cache
php artisan optimize:clear
php artisan optimize
# Build frontend assets
npm install
npm run build
# Restart queue workers
sudo supervisorctl restart laravel-worker:*
# Restart PHP-FPM (if needed)
sudo systemctl reload php8.2-fpm
echo "Deployment complete!"
chmod +x deploy.sh
./deploy.sh
Health Checks
Application Health
Create a health check endpoint:
Route::get('/health', function () {
return response()->json([
'status' => 'healthy',
'database' => DB::connection()->getPdo() ? 'connected' : 'disconnected',
'cache' => Cache::has('health-check') ? 'working' : 'not working',
]);
});
Monitor Application
curl https://yourdomain.com/health
Troubleshooting
500 Internal Server Error
- Check file permissions:
chmod -R 775 storage bootstrap/cache
- Check web server error logs:
tail -f /var/log/nginx/error.log
- Check Laravel logs:
tail -f storage/logs/laravel.log
- Clear caches:
php artisan optimize:clear
- Check PHP-FPM is running:
systemctl status php8.2-fpm
- Increase PHP memory limit in
php.ini
- Enable error reporting temporarily to see errors
Database Connection Failed
- Verify database credentials in
.env
- Check database server is accessible:
mysql -h host -u user -p
- Verify firewall rules allow database connections
- Check
DB_HOST - use 127.0.0.1 instead of localhost for TCP
1. Opcode Caching
Ensure OPcache is enabled:
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
2. Database Optimization
# Add indexes to frequently queried columns
# Use query caching with Redis
CACHE_STORE=redis
3. CDN for Assets
Use a CDN for static assets (CSS, JS, images) to reduce server load.
4. Queue Jobs
Move time-consuming tasks to queue workers:
Regularly monitor application performance and optimize bottlenecks using tools like Laravel Telescope or New Relic.