
August 12, 2026
10 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
Slow HTTP responses kill user experience and conversion rates, but moving heavy tasks to the background introduces operational complexity that breaks without proper infrastructure. This Laravel Queues with Redis: Production Setup Guide provides the exact configuration, supervision, and monitoring patterns required to run reliable asynchronous processing in 2026. Whether you are building a legal-tech portal or an eCommerce platform, getting this foundation right prevents silent data loss and stuck workflows.
config/queue.php, running dedicated workers via Supervisor or systemd, implementing automatic retry logic with exponential backoff, and deploying Laravel Horizon for real-time visibility into throughput and failures.Background processing is non-negotiable for modern applications. On a recent Laravel development project, we reduced average API response times from 4 seconds to under 200ms simply by offloading document generation and email dispatch to Redis-backed queues. However, the difference between a demo and a production system lies entirely in how you handle worker lifecycle, failure recovery, and observability. The default synchronous driver works locally, but Redis is the only viable choice for production workloads due to its atomic operations, persistence options, and native support for prioritized queues.
How do you configure Laravel Queues with Redis for production reliability?
The default Redis queue configuration in Laravel is functional but insufficient for production traffic. You must explicitly define connection parameters, separate your cache from your queue data, and set appropriate timeouts to prevent zombie jobs.
Separate Queue and Cache Connections
A common mistake I see on client projects is using the same Redis connection for both application caching and queue storage. When you run php artisan cache:clear, you risk wiping pending jobs. Always define a dedicated connection in config/database.php:
<?php // config/database.php 'redis' => [ 'client' => env('REDIS_CLIENT', 'phpredis'), 'default' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'username' => env('REDIS_USERNAME'), 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_DB', '0'), ], // Dedicated connection for queues - NEVER share with cache 'queues' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'username' => env('REDIS_USERNAME'), 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_QUEUE_DB', '1'), ], ],Configure Queue Connection Parameters
In config/queue.php, point the redis driver to your new connection and set explicit timeouts. The retry_after value must always exceed your longest expected job duration plus a buffer, otherwise Laravel will assume the job died and release it back to the queue while it is still executing.
<?php // config/queue.php 'redis' => [ 'driver' => 'redis', 'connection' => 'queues', // References database.redis.queues 'queue' => env('REDIS_QUEUE', 'default'), 'retry_after' => 900, // 15 minutes max job time + buffer 'block_for' => 5, // Blocking pop instead of polling 'after_commit' => true, // Dispatch only after DB transaction commits ],The after_commit flag is critical. Without it, jobs can be dispatched and processed before the database transaction that created them has actually committed, leading to "model not found" errors in workers. Setting this globally in 2026 is considered best practice for any Laravel application using database transactions alongside queues.
How do you supervise Laravel queue workers to prevent downtime?
Queue workers are long-lived processes that will eventually crash, run out of memory, or encounter fatal errors. Running php artisan queue:work manually in a terminal is never acceptable in production. You need a process supervisor that automatically restarts failed workers and manages concurrency.
Supervisor Configuration
Supervisor remains the most widely used tool for managing Laravel workers on Ubuntu servers. Install it via sudo apt install supervisor and create a 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/html/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600 --max-jobs=1000 --memory=256 autostart=true autorestart=true stopasgroup=true killasgroup=true user=www-data numprocs=4 redirect_stderr=true stdout_logfile=/var/log/laravel-worker.log stopwaitsecs=30Key flags explained from real deployment experience:
- --max-jobs=1000: Automatically restart the worker after processing 1000 jobs. This prevents memory leaks from accumulating over days of uptime.
- --max-time=3600: Restart after one hour regardless of job count. Acts as a safety net alongside max-jobs.
- --memory=256: Hard memory limit in MB. Workers exceeding this are gracefully terminated and restarted.
- --sleep=3: Seconds to sleep when no jobs are available. With
block_forenabled, this acts as a fallback timeout rather than active polling. - --tries=3: Default retry attempts per job. Individual jobs can override this with a
$triesproperty.
After creating the config, reload Supervisor:
sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl start laravel-worker:*Systemd Alternative for Modern Deployments
If your infrastructure uses systemd exclusively and you want to avoid installing additional packages, you can create a service template. This approach integrates better with journalctl logging and cgroup resource limits. Create /etc/systemd/system/laravel-queue@.service:
[Unit] Description=Laravel Queue Worker %i After=redis.service mysql.service [Service] User=www-data Group=www-data WorkingDirectory=/var/www/html ExecStart=/usr/bin/php /var/www/html/artisan queue:work redis --sleep=3 --tries=3 --max-jobs=1000 --memory=256 Restart=always RestartSec=5 MemoryMax=300M [Install] WantedBy=multi-user.targetEnable four instances with systemctl enable --now laravel-queue@{1..4}.service. In my experience managing deployments across multiple Nepal-based legal-tech portals, Supervisor offers simpler debugging for junior team members, while systemd provides tighter OS integration for experienced DevOps engineers.
How do you handle failed jobs and implement retry strategies?
Jobs fail. External APIs timeout, validation rules change, third-party services return unexpected responses. Your production setup must handle failures gracefully without losing data or flooding logs with noise.
Failed Job Storage
Always configure failed job storage. Redis works for high-throughput systems, but for most applications I recommend the database driver because it allows easy inspection via admin panels and SQL queries:
// config/queue.php 'failed' => [ 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), 'database' => env('DB_CONNECTION', 'mysql'), 'table' => 'failed_jobs', ],Run php artisan queue:failed-table followed by migration. The database-uuids driver is preferred over the legacy database driver because UUIDs prevent ID collision issues during replication or multi-environment restores.
Exponential Backoff and Conditional Retries
Flat retry intervals waste resources. Implement exponential backoff directly on your job classes:
<?php class SendLegalDocument implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public int $tries = 5; public function backoff(): array { // Retry after 1min, 5min, 15min, 30min, 1hr return [60, 300, 900, 1800, 3600]; } public function retryUntil(): DateTime { // Absolute deadline: 24 hours from initial dispatch return now()->addHours(24); } public function failed(?Throwable $exception): void { // Notify admin, update model status, log to Sentry Notification::route('slack', config('services.slack.alerts')) ->notify(new LegalDocumentFailed($this->documentId, $exception)); } }The retryUntil method takes precedence over $tries. This is essential for time-sensitive operations like court filing notifications where retrying after a deadline is pointless. On a notary service portal I maintain, we use this pattern to ensure attestation reminders stop retrying once the appointment window has passed.
Should you use Laravel Horizon or manual monitoring for Redis queues?
For any production system processing more than a few hundred jobs daily, Laravel Horizon is effectively mandatory. Manual Redis CLI inspection does not scale and provides no historical metrics.
| Criteria | Laravel Horizon | Manual / CLI Monitoring |
|---|---|---|
| Real-time throughput metrics | Built-in dashboard with graphs | Requires custom scripts or external tools |
| Worker auto-scaling | Configurable min/max processes per queue | Static Supervisor numprocs only |
| Failed job inspection | Web UI with payload viewer and retry button | php artisan queue:failed table output |
| Tag-based filtering | Track jobs by model, user, or tenant | Not possible without custom instrumentation |
| Historical performance data | Retained for configurable period (default 7 days) | None unless piping to external metrics store |
| Setup complexity | Composer require + publish config + deploy | Zero additional dependencies |
| Production overhead | Additional Redis keys + master process | Negligible |
Install Horizon with composer require laravel/horizon and publish its configuration. The key production setting is defining separate supervisors for different workload profiles:
// config/horizon.php 'environments' => [ 'production' => [ 'supervisor-1' => [ 'connection' => 'redis', 'queue' => ['default', 'emails'], 'balance' => 'auto', 'minProcesses' => 2, 'maxProcesses' => 10, 'maxTime' => 3600, 'maxJobs' => 1000, 'memory' => 256, 'tries' => 3, ], 'supervisor-reports' => [ 'connection' => 'redis', 'queue' => ['reports'], 'balance' => 'simple', 'processes' => 2, 'maxTime' => 3600, 'memory' => 512, 'tries' => 1, ], ], ],The auto balance mode dynamically adjusts worker count based on queue depth, which is invaluable during traffic spikes. For report generation queues that are memory-intensive but low-priority, I use simple balancing with fixed processes to prevent them from starving transactional jobs.
What are the common production pitfalls with Laravel Redis queues?
After maintaining queue infrastructure for over a decade, certain failure modes appear repeatedly. Avoiding these saves hours of 2 AM debugging.
- Deploying without restarting workers. Queue workers cache the entire application in memory. After every deployment, you must run
php artisan queue:restartor rely on Horizon's automatic restart signal. Forgetting this means old code continues processing jobs indefinitely. - Storing large payloads in Redis. Redis is memory-resident. Passing entire Eloquent models or base64-encoded files as job properties will exhaust memory under load. Always pass only IDs and re-fetch inside
handle(). Use Spatie Media Library's queued conversions for file processing instead of embedding binary data. - Missing idempotency guards. Network blips cause duplicate dispatches. Every job that modifies external state must check whether it has already been processed. A simple pattern is storing a processed flag keyed by job UUID before executing side effects.
- Ignoring Redis persistence configuration. If Redis restarts with default volatile-only persistence, all queued jobs vanish. Ensure
appendonly yes(AOF) or RDB snapshots are configured inredis.conffor production instances. For critical legal-tech workflows, I use AOF withappendfsync everysec. - Running workers on the same server as Redis under heavy load. Queue workers compete for CPU and memory with Redis itself. On budget-constrained Nepal hosting environments, this is sometimes unavoidable, but monitor Redis latency closely. If p99 latency exceeds 5ms, separate the services.
For teams building API-driven systems, understanding how queues interact with your broader architecture is essential. My article on Laravel API best practices covers patterns for returning immediate responses while queuing deferred work, including proper HTTP 202 Accepted semantics and status polling endpoints.
Conclusion
This Laravel Queues with Redis: Production Setup Guide covers the configuration, supervision, failure handling, and monitoring foundations that separate reliable background processing from fragile prototypes. Separate your Redis connections, supervise every worker with Supervisor or systemd, implement exponential backoff with absolute deadlines, and deploy Horizon for visibility. These are not optional optimizations — they are requirements for any system that processes real business transactions asynchronously.
If your team needs help auditing an existing queue setup, migrating from database drivers to Redis, or building a greenfield async architecture for a legal-tech or eCommerce platform, get in touch. I have shipped and maintained production queue infrastructure for Nepali businesses since 2010 and can help you avoid the costly mistakes that only surface under real traffic.

