Skip to main content

Introduction

This guide covers deploying the Laravel backend API to production, including optimization, database setup, queue workers, and scheduled tasks.

Prerequisites

Before deploying, ensure your server meets these requirements:
  • PHP 8.2 or higher
  • Composer 2.x
  • MySQL 8.0+ or PostgreSQL 13+
  • Web server (Nginx or Apache)
  • SSL certificate installed
  • SSH access to server

Production Build

1. Install Dependencies

Install Composer dependencies without development packages:
composer install --no-dev --optimize-autoloader
The --optimize-autoloader flag generates optimized class maps for better performance.

2. Optimize Autoloader

After making code changes, regenerate the optimized autoloader:
composer dump-autoload --optimize --classmap-authoritative
Use --classmap-authoritative to prevent the autoloader from scanning the filesystem, improving performance significantly.

3. Environment Configuration

Set up your production .env file:
.env
APP_NAME="Your App Name"
APP_ENV=production
APP_KEY=base64:your-generated-key-here
APP_DEBUG=false
APP_URL=https://api.yourdomain.com

FRONTEND_URL=https://app.yourdomain.com

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=your_production_database
DB_USERNAME=your_production_user
DB_PASSWORD=your_secure_password

QUEUE_CONNECTION=database
CACHE_STORE=database

SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_DOMAIN=.yourdomain.com

SANCTUM_STATEFUL_DOMAINS=app.yourdomain.com
Never commit your .env file to version control. Always set APP_DEBUG=false in production.

4. Generate Application Key

If you haven’t already, generate a unique application key:
php artisan key:generate

5. Cache Configuration

Cache your configuration files for better performance:
# Cache configuration files
php artisan config:cache

# Cache routes
php artisan route:cache

# Cache views
php artisan view:cache

# Cache events
php artisan event:cache
After caching, changes to configuration files won’t take effect until you run these commands again or clear the cache with php artisan cache:clear.

Database Setup

1. Run Migrations

Execute database migrations in production:
php artisan migrate --force
The --force flag is required to run migrations in production environment.

2. Seed Production Data (Optional)

If you have production seeders:
php artisan db:seed --class=ProductionSeeder --force

3. Database Backups

Set up automated database backups using Laravel’s backup package or your hosting provider’s backup solution.
# Using Laravel Backup package
composer require spatie/laravel-backup
php artisan backup:run
Configure scheduled backups in your cron job.

Web Server Configuration

Nginx Configuration

Create a server block for your Laravel application:
server {
    listen 80;
    listen [::]:80;
    server_name api.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name api.yourdomain.com;
    root /var/www/yourdomain.com/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;
        fastcgi_hide_header X-Powered-By;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }

    ssl_certificate /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
}

Apache Configuration

If using Apache, ensure .htaccess is enabled:
<VirtualHost *:80>
    ServerName api.yourdomain.com
    Redirect permanent / https://api.yourdomain.com/
</VirtualHost>

<VirtualHost *:443>
    ServerName api.yourdomain.com
    DocumentRoot /var/www/yourdomain.com/public

    <Directory /var/www/yourdomain.com/public>
        AllowOverride All
        Require all granted
    </Directory>

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/api.yourdomain.com/privkey.pem
</VirtualHost>

Queue Workers

1. Configure Queue Connection

Set your queue connection in .env:
QUEUE_CONNECTION=database
# Create jobs table
php artisan queue:table
php artisan migrate

2. Supervisor Configuration

Create a Supervisor configuration file at /etc/supervisor/conf.d/laravel-worker.conf:
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/yourdomain.com/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/yourdomain.com/storage/logs/worker.log
stopwaitsecs=3600

3. Start Supervisor

# Reload Supervisor configuration
sudo supervisorctl reread
sudo supervisorctl update

# Start workers
sudo supervisorctl start laravel-worker:*

# Check status
sudo supervisorctl status

4. Restart Workers After Deployment

After deploying new code, restart queue workers:
php artisan queue:restart
Add this to your deployment script to ensure workers pick up the latest code.

Cron Jobs (Task Scheduling)

Laravel’s scheduler allows you to run scheduled tasks. Set up a single cron entry:

Add Cron Entry

# Edit crontab
crontab -e

# Add this line
* * * * * cd /var/www/yourdomain.com && php artisan schedule:run >> /dev/null 2>&1
This runs Laravel’s scheduler every minute, which then executes scheduled tasks defined in app/Console/Kernel.php.

Verify Scheduled Tasks

# List scheduled tasks
php artisan schedule:list

# Test scheduler
php artisan schedule:work

Security Best Practices

Set proper permissions for Laravel directories:
# Set ownership
sudo chown -R www-data:www-data /var/www/yourdomain.com

# Set directory permissions
sudo find /var/www/yourdomain.com -type d -exec chmod 755 {} \;

# Set file permissions
sudo find /var/www/yourdomain.com -type f -exec chmod 644 {} \;

# Storage and cache must be writable
sudo chmod -R 775 /var/www/yourdomain.com/storage
sudo chmod -R 775 /var/www/yourdomain.com/bootstrap/cache
# Restrict .env file access
chmod 600 .env
chown www-data:www-data .env

# Ensure .env is not publicly accessible
# (Should be outside public directory)
In Nginx, add to server block:
autoindex off;
In Apache, add to .htaccess:
Options -Indexes
Configure rate limiting in app/Http/Kernel.php:
protected $middlewareGroups = [
    'api' => [
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];
Adjust limits in config/sanctum.php or RouteServiceProvider.php.

Performance Optimization

1. OPcache Configuration

Enable OPcache in php.ini:
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.fast_shutdown=1

2. PHP-FPM Tuning

Optimize PHP-FPM pool configuration:
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 500

3. Database Indexing

Ensure proper database indexes:
# Review slow queries
php artisan telescope:install  # Optional: for debugging

# Add indexes in migrations
Schema::table('users', function (Blueprint $table) {
    $table->index('email');
});

Deployment Script

Create a deployment script deploy.sh:
deploy.sh
#!/bin/bash

set -e

echo "🚀 Starting deployment..."

# Navigate to project directory
cd /var/www/yourdomain.com

# Enable maintenance mode
echo "📦 Enabling maintenance mode..."
php artisan down

# Pull latest code
echo "⬇️ Pulling latest code..."
git pull origin main

# Install/update dependencies
echo "📚 Installing dependencies..."
composer install --no-dev --optimize-autoloader --no-interaction

# Clear and cache config
echo "🔄 Clearing caches..."
php artisan config:clear
php artisan cache:clear

echo "💾 Caching configuration..."
php artisan config:cache
php artisan route:cache
php artisan view:cache

# Run migrations
echo "🗄️ Running migrations..."
php artisan migrate --force

# Restart queue workers
echo "🔄 Restarting queue workers..."
php artisan queue:restart

# Disable maintenance mode
echo "✅ Disabling maintenance mode..."
php artisan up

echo "🎉 Deployment completed successfully!"
Make it executable:
chmod +x deploy.sh

Monitoring and Logging

Configure Logging

In config/logging.php, set up appropriate channels:
'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['daily', 'slack'],
        'ignore_exceptions' => false,
    ],

    'daily' => [
        'driver' => 'daily',
        'path' => storage_path('logs/laravel.log'),
        'level' => env('LOG_LEVEL', 'error'),
        'days' => 14,
    ],
],

Error Tracking

Integrate error tracking services:
composer require sentry/sentry-laravel
php artisan sentry:publish --dsn=your-sentry-dsn

Health Check Endpoint

Create a health check endpoint for monitoring:
routes/api.php
Route::get('/health', function () {
    return response()->json([
        'status' => 'ok',
        'timestamp' => now()->toISOString(),
        'services' => [
            'database' => DB::connection()->getPdo() ? 'ok' : 'error',
            'cache' => Cache::has('health-check') ? 'ok' : 'error',
        ],
    ]);
});

Troubleshooting

  • Check Laravel logs: storage/logs/laravel.log
  • Verify file permissions
  • Ensure .env exists and is readable
  • Check web server error logs
  • Check Supervisor status: sudo supervisorctl status
  • View worker logs: storage/logs/worker.log
  • Restart workers: sudo supervisorctl restart laravel-worker:*
  • Check queue connection in .env
  • Verify FRONTEND_URL in .env
  • Check config/cors.php configuration
  • Clear config cache: php artisan config:clear
  • Ensure SSL is properly configured
  • Verify database credentials in .env
  • Check database server is running
  • Test connection: php artisan tinker then DB::connection()->getPdo();
  • Verify firewall rules allow database access

Next Steps

Frontend Deployment

Deploy your Next.js frontend application

Environment Variables

Configure production environment variables

Backend Testing

Learn about testing your Laravel backend

API Routes

Review your API endpoint structure

Build docs developers (and LLMs) love