Laravel development is pleasantly smooth — php artisan serve, everything works locally. Production is where things get interesting.
File permissions trip up most people first. Laravel needs to write to storage/ and bootstrap/cache/, but those folders need specific ownership to work with PHP-FPM. Get it wrong and you'll see 500 errors with no obvious cause.
Then there's the .env file, Composer dependencies, the application key, queue workers, scheduled tasks — each piece has a right way to set it up in production. I've debugged enough Laravel deployments to know where people get stuck, and this guide addresses each one.
The stack: Nginx, PHP-FPM, MySQL. Deploy from a GitHub repo. Production-ready in one go.
I run this on Tencent Cloud Lighthouse with Ubuntu 22.04. The 2 vCPU / 4 GB RAM plan handles a production Laravel app comfortably. Two Lighthouse features I use specifically for Laravel deployments: the snapshot feature lets me take a full server backup before running database migrations (Laravel migrations are reversible in theory, but having a full snapshot is better), and CBS cloud disk expansion means I can increase storage capacity as the app's uploaded files grow without provisioning a new server. For caching and queues, Redis runs efficiently on the same instance without needing a separate cache server.
Key Takeaways
- Set correct ownership:
www-data:www-dataforstorage/andbootstrap/cache/- Never commit
.envto version control — it contains secrets- Run
php artisan key:generateandphp artisan migrateafter deployment- Set up systemd services for queue workers to keep them running persistently
- Add one cron entry
* * * * * php artisan schedule:runfor all scheduled tasks
Internet → Nginx → PHP-FPM → Laravel
↓
MySQL database
Redis (optional, for cache/queue)
| Requirement | Notes |
|---|---|
| Cloud server | Tencent Cloud Lighthouse Ubuntu 22.04 |
| PHP 8.2+ | Laravel 11 requires PHP 8.2 |
| Composer | PHP dependency manager |
| MySQL | Or PostgreSQL — both supported |
| A Laravel project on GitHub | Or we'll create a fresh one |
ssh ubuntu@YOUR_SERVER_IP
sudo apt update && sudo apt upgrade -y
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw enable
Laravel 11 requires PHP 8.2 and specific extensions:
# Add PHP repository for latest versions
sudo apt install -y software-properties-common
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
# Install PHP 8.2 and required extensions
sudo apt install -y \
php8.2-fpm \
php8.2-mysql \
php8.2-xml \
php8.2-curl \
php8.2-mbstring \
php8.2-zip \
php8.2-gd \
php8.2-intl \
php8.2-bcmath \
php8.2-redis \
nginx
# Verify
php8.2 --version
Check PHP-FPM socket path:
ls /run/php/
# php8.2-fpm.sock
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
sudo chmod +x /usr/local/bin/composer
composer --version
sudo apt install -y mysql-server
sudo systemctl start mysql && sudo systemctl enable mysql
sudo mysql_secure_installation
Create database and user:
sudo mysql
CREATE DATABASE laravel_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY 'choose_strong_password';
GRANT ALL PRIVILEGES ON laravel_db.* TO 'laravel_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
cd /var/www
sudo git clone https://github.com/your-username/your-laravel-app.git mylaravel
sudo chown -R ubuntu:www-data /var/www/mylaravel
cd /var/www/mylaravel
# Install PHP dependencies
composer install --no-dev --optimize-autoloader
# Create and configure .env
cp .env.example .env
nano .env
Update the .env file:
APP_NAME="My App"
APP_ENV=production
APP_DEBUG=false
APP_URL=https://yourdomain.com
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_db
DB_USERNAME=laravel_user
DB_PASSWORD=your_password
CACHE_DRIVER=file
QUEUE_CONNECTION=database
SESSION_DRIVER=database
# Generate application key
php artisan key:generate
# Run migrations
php artisan migrate --force
# Optimize for production
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan optimize
sudo nano /etc/nginx/sites-available/mylaravel
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
root /var/www/mylaravel/public;
index index.php index.html;
access_log /var/log/nginx/mylaravel_access.log;
error_log /var/log/nginx/mylaravel_error.log;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
# Deny access to hidden files
location ~ /\.(?!well-known).* {
deny all;
}
# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
}
sudo ln -s /etc/nginx/sites-available/mylaravel /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Laravel needs write access to storage/ and bootstrap/cache/:
sudo chown -R ubuntu:www-data /var/www/mylaravel
sudo chmod -R 755 /var/www/mylaravel
sudo chmod -R 775 /var/www/mylaravel/storage
sudo chmod -R 775 /var/www/mylaravel/bootstrap/cache
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
After enabling HTTPS, update .env:
APP_URL=https://yourdomain.com
Clear cache:
php artisan config:clear && php artisan config:cache
sudo apt install -y supervisor
sudo nano /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/mylaravel/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=ubuntu
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/laravel-worker.log
stopwaitsecs=3600
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*
crontab -e
# Add:
* * * * * cd /var/www/mylaravel && php artisan schedule:run >> /dev/null 2>&1
The most common Laravel error after deployment is a 500 error with this in the logs:
The stream or file "storage/logs/laravel.log" could not be opened:
failed to open stream: Permission denied
This means Nginx (running as www-data) can't write to the storage directory.
Fix:
sudo chown -R ubuntu:www-data /var/www/mylaravel/storage
sudo chown -R ubuntu:www-data /var/www/mylaravel/bootstrap/cache
sudo chmod -R 775 /var/www/mylaravel/storage
sudo chmod -R 775 /var/www/mylaravel/bootstrap/cache
Also common: forgetting to run php artisan config:cache after changing .env. Laravel reads from the cache in production, so changes to .env don't take effect until you clear and rebuild the cache.
php artisan config:clear
php artisan config:cache
# Deploy a new version
cd /var/www/mylaravel
git pull origin main
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan optimize
sudo systemctl reload nginx
# Clear all caches
php artisan optimize:clear
# View logs
tail -f /var/www/mylaravel/storage/logs/laravel.log
# Queue status
sudo supervisorctl status
| Issue | Likely Cause | Fix |
|---|---|---|
| Connection refused | Service not running or wrong port | Check systemctl status SERVICE and verify firewall rules |
| Permission denied | Wrong file ownership or permissions | Check file ownership with ls -la and use chown/chmod to fix |
| 502 Bad Gateway | Backend service not running | Restart the backend service; check logs with journalctl -u SERVICE |
| SSL certificate error | Certificate expired or domain mismatch | Run sudo certbot renew and verify domain DNS points to server IP |
| Service not starting | Config error or missing dependency | Check logs with journalctl -u SERVICE -n 50 for specific error |
| Out of disk space | Logs or data accumulation | Run df -h to identify usage; clean logs or attach CBS storage |
| High memory usage | Too many processes or memory leak | Check with htop; consider upgrading instance plan if consistently high |
| Firewall blocking traffic | Port not open in UFW or Lighthouse console | Open port in Lighthouse console firewall AND sudo ufw allow PORT |
What PHP version does Laravel require?
Laravel 11 requires PHP 8.2+. Laravel 10 requires PHP 8.1+. Check composer.json in your project for the exact requirement. Use php -v on the server to verify the installed version.
Why do I get "permission denied" errors with Laravel?
Laravel needs write access to storage/ and bootstrap/cache/. Set correct ownership with sudo chown -R www-data:www-data /path/to/app and permissions with sudo chmod -R 775 storage bootstrap/cache.
How do I run Laravel queue workers in production?
Create a systemd service for php artisan queue:work. Set it to restart automatically. The queue worker must keep running — if it stops, queued jobs won't be processed.
What is APP_KEY in Laravel and how do I generate it?
APP_KEY is a 32-character encryption key used for secure cookies and encrypted data. Generate it with php artisan key:generate. Never commit it to version control.
* * * * * cd /path/to/app && php artisan schedule:run >> /dev/null 2>&1. Laravel handles the actual task scheduling logic internally.Deploy your Laravel app today:
👉 Tencent Cloud Lighthouse — PHP-ready Ubuntu VPS
👉 View current pricing and promotions
👉 Explore all active deals and offers