Skip to main content

Cloudsoft Zone Build, deploy and scale with practical guides to PHP, Node.js, React, DevOps and AI automation. Explore the guides

Cloudsoft Zone
DevOpsLaravelPHPTutorial

Laravel queue workers in production: Redis, Supervisor and failed jobs

Run Laravel queue workers with Redis and Supervisor, aligned timeouts, safe retries, graceful release restarts and deliberate failed-job recovery.

Cloudsoft Zone

7 min read

Laravel queue workers: Redis and Supervisor. Sourced Laravel queues, Redis artwork with article-specific deployment checkpoints.
Image: Cloudsoft Zone layout; Laravel / Public domain; Redis Ltd / Public domain
In this article

A queued email, export or webhook can appear successful to the browser before any worker has performed it. Redis may hold the job, but a stopped process, mismatched queue name or unsafe timeout can leave it waiting indefinitely—or perform it twice. Production queues need a lifecycle, not an SSH terminal left open.

This guide sets up Laravel queue workers with Redis and Supervisor for a conventional Laravel 13 application. We will separate queue durability from cache eviction, align timeouts, supervise the correct release and handle failed jobs deliberately. If you choose Horizon for its dashboard and balancing features, configure it as the queue supervisor; do not add a competing worker fleet by accident.

Use an isolated staging application for the walkthrough. Record its actual PHP version, Redis client and job workload before tuning the numbers. A queue that sends customer messages or charges money needs application-level idempotency as well as process management.

Laravel commits business state and dispatches a stable job identifier through private Redis to a bounded worker.
Accepted work needs traceable completion and a policy for uncertain outcomes.

Step 1: Decide which work belongs in the queue

Background jobs work well for bounded tasks that can report progress or completion later. Examples include generating a small export, processing an uploaded image and notifying a customer after an order is committed. A queue does not make an unbounded task safe; split large work into resumable batches with stable identifiers.

For each job type, record its maximum expected duration, external request timeout, retry policy and business effect. A report that can be regenerated has different recovery requirements from a payment instruction. Specify whether a retry is safe before increasing the number of attempts.

Workload Important decision
Image processing Retain source input and bound decoded image resources
Export generation Limit rows per batch and persist progress
Notification Decide how duplicate delivery is detected
Payment or webhook effect Use a durable idempotency key and business transaction

Keep jobs small enough to understand and recover. Putting a slow HTTP request into a queue changes where it runs, but not whether it can hang, leak memory or expose secrets.

Step 2: Configure a private Redis connection for queue data

Redis should be reachable only through the intended private network and protected access policy. Separate queue storage from aggressively evicted cache storage where practical. Redis logical database numbers do not provide independent memory limits or failure isolation.

Configure Laravel's Redis connection and queue driver using your protected environment source:

INI
QUEUE_CONNECTION=redis
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_QUEUE=default

Supply authentication privately. The addresses are examples for the tutorial server, not this website's local services. Verify the PHP Redis extension if using PhpRedis; choosing a client in configuration does not install it.

In the existing config/queue.php Redis connection, make the visibility and transaction decisions explicit:

PHP
'redis' => [
    'driver' => 'redis',
    'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
    'queue' => env('REDIS_QUEUE', 'default'),
    'retry_after' => 90,
    'block_for' => 5,
    'after_commit' => true,
],

The 90-second interval is an example for short jobs whose worker timeout is 60 seconds. Check any job-specific timeout too. A finite blocking interval lets idle workers return to their lifecycle checks; an indefinitely blocking worker can delay handling a shutdown signal until another job arrives.

after_commit prevents dispatching a job before the surrounding database transaction commits. It does not guarantee exactly-once delivery or coordinate an external payment with the database. Use a durable outbox where reliable cross-system delivery is required.

Step 3: Align job timeouts, retries and shutdown grace

Think of the timing configuration as a set of ordered bounds. External calls should finish or fail within the job's allowed execution time. The worker timeout should be shorter than the connection's retry_after, and the process monitor should allow valid in-flight jobs enough time to finish during shutdown.

Text
External request timeout: 15 seconds
Worker timeout:           60 seconds
Redis retry_after:        90 seconds
Supervisor stopwaitsecs: 120 seconds

These values illustrate the relationship; they are not performance recommendations. A job can override timeout or attempt settings. Long-running exports may need a separate queue and worker policy, while repeated network failures need backoff rather than immediate retries.

Laravel's timeout handling depends on appropriate process-control support. Confirm the CLI environment used by the actual worker. Set explicit HTTP client timeouts: a process deadline does not substitute for bounded network operations and cancellation-aware application code.

Timing example orders a 15 second external call, 60 second worker timeout, 90 second retry interval and 120 second shutdown grace.
Example values explain the ordering; tune them against the actual job workload.

Step 4: Make the business operation safe to retry

Suppose a job creates a customer report. Give the report request a stable identifier and a unique database constraint. On retry, load that request, detect an already-completed artifact and return rather than creating a second report under a new ID.

For external effects, pass the provider-supported idempotency key where available and record the operation durably. A process can fail after the provider accepted a request but before the local application recorded success. Treat that as an uncertain outcome requiring reconciliation, not proof that the effect never happened.

Laravel job uniqueness and overlap middleware can reduce concurrent work. They do not replace a database constraint or a provider idempotency key. Lock expiry, worker failure and delayed delivery must still be considered.

Keep payloads minimal. Persist a record identifier and reload the necessary state when the job runs. Avoid placing credentials or an entire sensitive customer document into queue payloads or failed-job logs. For jobs that depend on a record still existing, define what deletion or cancellation means.

Step 5: Supervise workers under the application identity

Use a deployment or dedicated worker account with access to the intended release and writable storage. Keep source code deployment-owned and avoid running jobs as root. This Supervisor example starts one process for a bounded default queue:

INI
[program:laravel-default]
directory=/srv/laravel-app/current
command=/usr/bin/php8.3 artisan queue:work redis --queue=default --sleep=3 --tries=3 --timeout=60 --max-time=3600
user=deploy
numprocs=1
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
stopwaitsecs=120
redirect_stderr=true
stdout_logfile=/srv/laravel-app/shared/storage/logs/queue-default.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=5

Replace PHP, paths and queue names with the actual application values. The --max-time option periodically exits a worker after it finishes work; Supervisor's restart policy starts a fresh process. It is not a substitute for finding a memory leak.

Shell
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status laravel-default

Inspect startup errors before changing process counts. Multiple workers increase database connections, memory usage and downstream load. Queue order can prioritize work, but a constantly busy high-priority queue may starve lower-priority jobs; separate worker groups where service requirements differ.

Step 6: Deploy without leaving stale workers alive

After preparing compatible code and configuration, request a graceful worker restart:

Shell
php artisan queue:restart
sudo supervisorctl status laravel-default

Run the restart command against the cache configuration the running workers actually share. Verify new process start times and the release path, not just Supervisor's status label. Switching the current symlink does not rewrite code already loaded in a process.

Keep old and new job payloads compatible during the transition. A queued serialized class may outlive the release that created it. Removing a job class or changing required payload fields before draining old work can break a healthy deployment.

For larger changes, pause new dispatch where necessary, let in-flight work reach a known state, then use a documented resume procedure. Never delete queue keys to “clean up” a deployment; those keys may contain accepted customer work.

Step 7: Handle failures and prove restoration

Shell
php artisan queue:failed

Inspect a failed job's cause and business state before retrying its specific identifier. Retrying all failed jobs can repeat messages, payments or expensive work. Preserve failure evidence, redact private payloads and define a retention policy. Clearing a failure table is not a repair.

Failed-job recovery inspects state, reconciles uncertain external effects and retries selected idempotent work.
Restarting a process cannot establish whether an external effect already happened.

Symptom First check
Jobs wait indefinitely Worker connection, queue name and Redis reachability
Same effect happens twice Timeout relationship and business idempotency
New release still runs old logic Worker restart signal and process start time
Queue fails after deployment Serialized payload/class compatibility
Redis memory is full Memory policy, retained jobs and workload growth
Process is RUNNING but nothing happens Actual dispatched job and completion evidence

Monitor oldest-job age, failures, throughput, worker restarts and dependency latency. Queue length alone can be misleading when a single important job is stuck. Define an alert that reaches someone responsible for recovery.

Redis persistence and tested off-host backups belong to the design if losing queued work is unacceptable. AOF and snapshots have different loss and resource trade-offs; neither promises zero loss under every failure. In staging, dispatch a synthetic job, restart the worker mid-lifecycle and prove the intended durable effect occurs once. Record that result before approving production use.

For release acceptance, use our Laravel release checklist. If you need help reviewing queue reliability, discuss your application with Cloudsoft Zone.

Featured image: Cloudsoft Zone explanatory layout with sourced product artwork: laravel — Laravel (Public domain); redis — Redis Ltd (Public domain). Product marks resized and arranged for identification; no endorsement implied. Layout: CC BY-SA 4.0.

Frequently asked questions

  • Does Redis guarantee exactly-once job execution?

    No. Design business effects to tolerate retries and uncertain completion; use durable constraints and supported provider idempotency.

  • Why must the worker timeout be shorter than retry_after?

    A job should not become available to another worker while the original worker can still execute it. Review job-specific timeout overrides too.

  • Can Redis database numbers isolate queue memory from cache eviction?

    No. Logical databases share the instance's resources and memory policy. Use appropriate service separation where needed.

Advertise on CloudSoftZone

Your next customer could start right here.

Introduce your tool, product or business to readers exploring technology.

Advertise with us

Explore placements and request a private quote

Sources

  1. Laravel queues — Laravel
  2. Laravel deployment — Laravel
  3. Supervisor configuration — Supervisor
  4. Redis persistence — Redis

Comments

No comments yet. Be the first to join the conversation.

Leave a comment

Every comment is read by a moderator before it appears. Your email address is never published; it is used only to contact you about this comment.

Never published. Used only to contact you about this comment.

Loading the security check…

Comments are moderated before publication.