Introduction
Queues let you defer time-consuming work — sending email, generating PDFs, calling a third-party API — so your HTTP responses stay fast. Instead of making a user wait while a 2-second email send finishes, you push a job onto a queue and return immediately.
Key Concepts
- Queue: A FIFO list of pending jobs waiting to be processed.
- Driver: The backend that stores the queue — database, Redis, SQS, Beanstalkd, or
sync(which runs inline). - Worker: A long-running PHP process that pulls jobs off the queue and executes them.
- Job: A PHP class that encapsulates a unit of background work.
Real World Context
Every production Laravel app uses queues. A checkout flow might dispatch a payment-charge job, a receipt-email job, and an analytics-tracking job before returning the redirect — all in under 10ms, instead of the 5+ seconds those would take synchronously. Without queues, your app scales by the slowest downstream service you call.
Deep Dive
Why Use Queues?
Without Queues: With Queues:
┌──────────────────┐ ┌──────────────────┐
│ User Request │ │ User Request │
└────────┬─────────┘ └────────┬─────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Process Order │ │ Process Order │
│ (100ms) │ │ (100ms) │
└────────┬─────────┘ └────────┬─────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Send Email │ │ Queue Email Job │ ◄── Returns immediately!
│ (2000ms) │ │ (5ms) │
└────────┬─────────┘ └────────┬─────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Generate PDF │ │ Response: 105ms │
│ (3000ms) │ └──────────────────┘
└────────┬─────────┘
│ Background Worker:
▼ ┌──────────────────┐
┌──────────────────┐ │ Send Email │
│ Response: 5100ms│ │ Generate PDF │
└──────────────────┘ │ (No user wait) │
└──────────────────┘
Queue Benefits
| Benefit | Description |
|---|---|
| Faster Responses | Users don't wait for slow operations |
| Better UX | Immediate feedback, background processing |
| Scalability | Add more workers to handle load |
| Reliability | Retry failed jobs automatically |
| Resource Management | Control when heavy tasks run |
Queue Drivers
Laravel supports multiple queue backends:
php// config/queue.php 'connections' => [ 'database' => [ 'driver' => 'database', 'connection' => env('DB_QUEUE_CONNECTION'), 'table' => env('DB_QUEUE_TABLE', 'jobs'), 'queue' => env('DB_QUEUE', 'default'), 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), 'after_commit' => false, ], 'redis' => [ 'driver' => 'redis', 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), 'queue' => env('REDIS_QUEUE', 'default'), 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90), 'block_for' => null, 'after_commit' => false, ], 'sqs' => [ 'driver' => 'sqs', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 'queue' => env('SQS_QUEUE', 'default'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'after_commit' => false, ], 'sync' => [ 'driver' => 'sync', // Runs immediately (good for testing) ], ],
Choosing a Driver
| Driver | Use Case |
|---|---|
| sync | Local development, testing |
| database | Simple apps, getting started |
| redis | Production apps, fast performance |
| sqs | AWS deployments, high scale |
| beanstalkd | Alternative to Redis |
Database Queue Setup
Laravel 13 includes queue migrations by default:
bash# If you need to create them manually php artisan make:queue-table php artisan migrate
The jobs table structure:
phpSchema::create('jobs', function (Blueprint $table) { $table->id(); $table->string('queue')->index(); $table->longText('payload'); $table->unsignedTinyInteger('attempts'); $table->unsignedInteger('reserved_at')->nullable(); $table->unsignedInteger('available_at'); $table->unsignedInteger('created_at'); });
Configuring the Queue Connection
env# .env QUEUE_CONNECTION=database # For Redis QUEUE_CONNECTION=redis REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379
Running Queue Workers
bash# Start processing jobs php artisan queue:work # Process jobs from specific queue php artisan queue:work --queue=high,default,low # Process single job and exit php artisan queue:work --once # With configuration options php artisan queue:work --tries=3 --timeout=60 --memory=128
Note: the Laravel 13 docs only document
queue:workfor running jobs. Use it everywhere — in production under Supervisor, and in development by restarting the worker whenever you change job code.
The Development Server
Laravel 13's composer dev runs everything together:
bashcomposer dev # Runs: server, queue:work, pail (logs), and vite concurrently
Common Pitfalls
- Forgetting to start a worker — jobs dispatched with no
queue:workprocess running pile up in the queue forever. In development, usecomposer dev(or thesyncdriver) so background tasks actually run. - Using
syncin production — thesyncdriver runs jobs inline in the request, defeating the whole point of queues. It's meant for tests and local experiments, never for real traffic.
Best Practices
- Start with
database, upgrade torediswhen you have load —databaseneeds no extra infrastructure, but row contention and polling hurt above a few hundred jobs per minute. Redis is flat until tens of thousands per second. - Use
composer devfor local development — it starts the server, worker, log tail, and Vite bundler together so you never forget to boot a worker.
Summary
- Queues defer slow work from the HTTP request cycle to background workers.
- Laravel supports
sync,database,redis,sqs, andbeanstalkddrivers. - Jobs are PHP classes dispatched from controllers and executed by
queue:work. - Production:
queue:workunder Supervisor. Development:composer dev.