
September 09, 2026
11 min read
By Kokil Thapa | Last reviewed: September 2026
Your checkout page should not wait for a payment gateway, three SMS providers, and a PDF generator to finish before showing a thank-you screen. That is where Laravel Queues and Jobs: Background Processing Explained becomes practical architecture, not optional polish. On real client projects — booking systems, eCommerce carts, legal-tech portals with document workflows — I move slow or failure-prone work off the HTTP request path. Laravel 12 and 13 ship a mature queue layer backed by Redis production queue setup patterns most teams already run. This guide walks through job classes, drivers, workers, retries, and the mistakes that break deployments.
What are Laravel queues and jobs used for in background processing?
A job is a PHP class that implements work you want deferred. A queue is the transport layer that stores serialized jobs until a worker picks them up. The controller dispatches; the worker executes. That split is the core of background processing in Laravel.
Typical candidates include sending transactional email, calling third-party APIs, generating invoices, resizing uploads, syncing inventory, and pushing webhook payloads. On a production enterprise Laravel application, anything that can fail independently or take more than a few hundred milliseconds belongs off the request thread.
Laravel serializes the job payload — class name, constructor arguments, and metadata — into the configured driver. Workers poll the driver, deserialize the job, and call the handle() method. If the job throws, Laravel applies your retry policy or records a failure.
Do not confuse queues with the scheduler. Cron-driven schedule:run tasks fire on a timetable. Queues react to application events. The distinction matters when you design idempotent jobs versus periodic cleanup. Read Laravel cron job vs queue worker before mixing both on the same server without a plan.
Jobs vs events vs notifications
Events broadcast that something happened. Listeners may dispatch jobs. Notifications can implement ShouldQueue and become queued mail or SMS automatically. Keep one job per bounded unit of work — "send booking confirmation" beats "process entire order lifecycle".
How do you create and dispatch a Laravel job class?
Start with an Artisan generator. Laravel 12 and 13 both support the same pattern on PHP 8.2 or higher (PHP 8.3+ for Laravel 13).
php artisan make:job SendBookingConfirmation
<?php
namespace App\Jobs;
use App\Models\Booking;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class SendBookingConfirmation implements ShouldQueue
{
use Queueable;
public function __construct(public Booking $booking) {}
public function handle(): void
{
/* mail, SMS, PDF generation */
}
} Dispatch from a controller, listener, or service:
SendBookingConfirmation::dispatch($booking);
SendBookingConfirmation::dispatch($booking)
->onQueue('notifications')
->delay(now()->addMinutes(5)); The ShouldQueue interface tells Laravel not to run synchronously when a non-sync driver is active. Without it, the class still dispatches but executes inline — a common surprise during local development with QUEUE_CONNECTION=sync.
Pass only serializable data
Eloquent models work because Laravel stores the model ID and rehydrates via SerializesModels. Closures, open file handles, and raw PDO connections do not survive serialization. Pass IDs or DTOs when jobs cross long delays.
Form requests and validation still belong on the web layer
Validate input before dispatch. Jobs should assume the payload is already clean. Re-validate inside handle() only when stale data is a real risk — for example, rechecking stock before fulfilment an hour after checkout.
Which queue driver should you use for Laravel background jobs?
Your driver choice affects throughput, operational cost, and failure visibility. Most production Laravel apps I maintain use Redis 8.10 with a dedicated queue database index. Database queues are fine for low volume or shared hosting without Redis.
| Driver | Best for | Trade-offs |
|---|---|---|
sync | Local dev, tests | No background processing; blocks HTTP |
database | Small apps, no Redis | Polling load on MySQL 9.7; slower at scale |
redis | Most production Laravel apps | Needs Redis + worker supervision |
sqs | AWS-hosted, multi-region | Vendor lock-in, IAM setup, per-request cost |
beanstalkd | Legacy stacks | Less common in new 2026 projects |
Configure in .env and config/queue.php:
QUEUE_CONNECTION=redis
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379 Run migrations for database-backed queues and failed-job storage:
php artisan queue:table
php artisan queue:failed-table
php artisan migrate For high-traffic booking platforms like Adventure Third Pole Trek, Redis plus Horizon gives visibility into wait times and throughput. See Laravel Horizon monitoring guide when you outgrow a single worker process.
How do you run Laravel queue workers in production?
Dispatching jobs is half the system. Without a running worker, Redis lists grow and users never receive email. Every deployment I handle includes worker supervision alongside PHP-FPM.
- Install and configure the queue driver (Redis recommended).
- Run
php artisan queue:work redis --queue=default,notifications --tries=3 --timeout=90. - Wrap the worker in Supervisor or systemd so it restarts on crash or deploy.
- Reload workers after code deploy — stale opcache plus old code causes silent mismatches.
- Monitor queue depth, failed jobs, and worker memory.
Example Supervisor program block on Ubuntu 24:
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/app/current/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopwaitsecs=3600
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/app/shared/storage/logs/worker.log After Deployer symlink swap, run php artisan queue:restart. Workers finish the current job, then exit; Supervisor starts fresh processes with new code. I use this same pattern on sister legal-tech sites sharing a GitLab CI pipeline. Details sit in Linux system administration and Horizon vs Supervisor comparison.
Multiple queues and priority
Split traffic by concern: default, notifications, webhooks, reports. Workers read queues left-to-right:
php artisan queue:work redis --queue=critical,default,low Payment callbacks and OTP SMS belong on critical. Monthly CSV exports belong on low. Without separation, a bulk export can delay password-reset email.
Horizon for Redis at scale
Laravel Horizon adds a dashboard, balancing, and metrics on top of Redis queues. It fits apps processing thousands of jobs per hour — eCommerce order hooks, directory sync, or payment gateway callbacks. Horizon is Redis-only; do not install it if you stay on the database driver.
How do you handle failed jobs, retries, and timeouts?
Background processing fails — APIs time out, SMTP rejects mail, disks fill up. Laravel retries thrown exceptions up to your $tries property or CLI flag. After exhaustion, the job lands in failed_jobs.
class ProcessWebhook implements ShouldQueue
{
public int $tries = 5;
public int $timeout = 120;
public array $backoff = [10, 30, 60, 120, 300];
public function handle(): void
{
/* call external API */
}
public function failed(?\Throwable $e): void
{
/* alert ops, log context */
}
} Inspect and replay failures:
php artisan queue:failed
php artisan queue:retry all
php artisan queue:flush Make jobs idempotent. A retry must not double-charge a card or send duplicate SMS. Store a processed flag, use gateway idempotency keys, or wrap state changes in database transactions. For bulk imports, combine queues with Laravel job batching so you track partial completion.
Unique jobs and overlap prevention
Laravel supports ShouldBeUnique to prevent duplicate concurrent jobs for the same resource — useful when users double-click "Resend receipt". Combine with WithoutOverlapping middleware when two workers must not process the same entity simultaneously.
When should you queue work instead of running it synchronously?
Queue when the user does not need the result immediately. Keep synchronous code for auth checks, cart totals, and anything that gates the next screen.
- Queue: email, SMS, webhooks, image processing, search indexing, PDF generation, analytics pings.
- Stay synchronous: login, permission checks, stock reservation at checkout, payment authorization response.
- Either: small cache warming — queue if it touches remote services; inline if it is a local Redis read.
On a legal-tech portal, document virus scanning and thumbnail generation belong in queues. Form validation and fee calculation stay on the request. The same split applies to grocery delivery order routing and REST API endpoints that must return within gateway timeout limits. Compare with background jobs vs cron design when work is time-triggered rather than event-triggered.
For debugging payloads during development, paste job JSON into the JSON formatter tool before blaming production Redis. Structured logs beat guessing serialized shape.
Testing queued jobs
Use Queue::fake() in feature tests to assert dispatch without running workers:
Queue::fake();
$response = $this->post('/orders', $payload);
Queue::assertPushed(SendBookingConfirmation::class); Run integration tests with QUEUE_CONNECTION=sync sparingly — it hides race conditions that only appear with real async workers.
Performance and database load
Queued jobs still hit your database. Eager-load relationships inside handle() to avoid N+1 queries. Offload heavy reads to read replicas only when your infrastructure supports it. PostgreSQL 18 and MySQL 9.7 both behave well as job payload stores when indexed correctly — see PostgreSQL for Laravel developers for connection pooling notes under worker load.
Official reference: the Laravel queue documentation covers drivers, workers, and failure handling. Redis client details live in the Redis PHP client docs.
Key Takeaways
- Implement
ShouldQueueon job classes and dispatch early — keep HTTP responses under gateway timeouts. - Use Redis as the default production driver; reserve the database driver for low-volume or Redis-less hosting.
- Supervise workers with Supervisor or systemd and run
queue:restarton every deploy. - Design jobs to be idempotent with explicit
$tries,$timeout, and$backoffvalues. - Split named queues by priority so bulk work cannot block OTP or payment callbacks.
- Monitor
failed_jobsand queue depth — silent worker death is the most common production queue failure.
People Also Ask
What is the difference between dispatch() and dispatchSync() in Laravel?
dispatch() sends the job to the configured queue connection for asynchronous processing. dispatchSync() runs handle() immediately inside the current process, bypassing the queue — useful for tests or rare cases where you need inline execution of a job class.
Do Laravel queues require Redis?
No. Laravel supports sync, database, Redis, SQS, Beanstalkd, and other drivers. Redis is the most common production choice because it is fast and pairs with Horizon, but the database driver works for smaller apps without extra infrastructure.
How many queue workers do I need?
Start with one worker per CPU core for CPU-bound jobs, or two to four workers for I/O-bound API calls. Watch queue wait time and scale horizontally. Horizon auto-balancing simplifies this for Redis deployments.
What happens if no queue worker is running?
Jobs accumulate in the queue store but never execute. The web app still returns success to users, yet email, webhooks, and exports silently stop. Always supervise workers and alert on queue depth thresholds.
Ship background processing you can trust in production
Laravel Queues and Jobs: Background Processing Explained comes down to a simple contract: dispatch bounded work, supervise workers, and design for retries. That pattern has kept checkout flows fast on eCommerce builds and document pipelines reliable on legal-tech portals I maintain. If your app dispatches jobs but workers die after deploy, or failed webhooks pile up unnoticed, the fix is usually operational — not another package. For scaling patterns, read mastering Laravel queues for high traffic. Need queue architecture reviewed on an existing Laravel 12 or 13 codebase? Contact us or explore ongoing support and maintenance. Browse the portfolio for production apps that rely on background jobs daily, or review Laravel API best practices for timeout-safe endpoint design.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

