Skip to main content
← All use cases

How to Monitor php artisan queue:retry in Production

Your deploy went out at 2 PM. By 4 PM, 3,200 jobs are sitting in the failed_jobs table because a third-party payment API changed its response format. You fix the parsing logic, deploy the patch, and run php artisan queue:retry all. The jobs flood back onto the queue. Half of them fail again because the original payloads contain malformed data that your fix didn’t account for. Now you have 1,600 new entries in failed_jobs, your queue workers are burning cycles on doomed retries, and legitimate jobs dispatched by real users are waiting behind the wreckage.

This is the queue:retry problem in production. The command itself is simple. Knowing whether it actually helped is the hard part.

What queue:retry Actually Does

queue:retry takes jobs from the failed_jobs table (or DynamoDB if you configured that in Laravel 11+) and pushes them back onto their original queue. It resets the attempt counter, so the job gets a fresh set of tries as defined by your --tries flag or the job’s $tries property.

In Laravel 10, 11, and 12, the command accepts a job UUID, multiple UUIDs, or all:

# Retry a single job
php artisan queue:retry 9b1c4a80-1e2d-4f3a-8b5c-6d7e8f9a0b1c

# Retry multiple specific jobs
php artisan queue:retry 9b1c4a80-... 7a2f3e60-... 4c8d1b90-...

# Retry everything in the failed_jobs table
php artisan queue:retry all

When you run queue:retry all, Laravel doesn’t filter or validate. Every failed job goes back on the queue, regardless of why it failed. If 500 jobs failed because of a bug you fixed and 200 failed because of corrupt input data, all 700 get retried. The 200 with corrupt data will fail again, and you’re back where you started with those.

The Retry Storm Problem

Running queue:retry all on a large failed_jobs table creates a burst of work. If you have 10,000 failed jobs and four Supervisor workers, those workers are suddenly competing with fresh user-dispatched jobs for processing time. Queue latency spikes. Password reset emails get delayed. Webhook deliveries back up.

The smarter approach is to retry in batches or filter by job class:

# Retry only jobs that failed in the last hour
php artisan queue:retry $(php artisan queue:failed --json | \
  jq -r '.[] | select(.failed_at > "2026-04-07 13:00:00") | .id' | \
  tr '\n' ' ')

This is a common pattern, but it still doesn’t answer the critical question: did those retried jobs actually succeed the second time around?

Tracking Retry Outcomes

Laravel fires the JobRetryRequested event (available since Laravel 10) when a job is pushed back from the failed table. You can listen for this and correlate it with subsequent JobProcessed or JobFailed events to build a retry success rate.

// app/Listeners/TrackRetryOutcome.php
use Illuminate\Queue\Events\JobRetryRequested;
use Illuminate\Queue\Events\JobFailed;
use Illuminate\Support\Facades\Log;

class TrackRetryOutcome
{
    public function handle(JobRetryRequested $event): void
    {
        Log::info('Job retry initiated', [
            'job_id' => $event->job->getJobId(),
            'connection' => $event->job->getConnectionName(),
            'queue' => $event->job->getQueue(),
        ]);
    }
}

Pair this with a JobFailed listener, and you can detect when a retried job fails again. If the same job UUID appears in your retry log and then in your failure log within minutes, you have a recurring failure that queue:retry won’t solve.

Knowing When to Stop Retrying

Some jobs should not be retried at all. A payment capture that failed because the customer’s card was declined will fail again on retry. An image processing job that failed because the source file was deleted from S3 will fail forever.

Laravel 10+ lets you define retryUntil() on individual job classes, but this only controls automatic retries, not manual queue:retry invocations. When you manually retry a job, it bypasses retryUntil entirely and gets a fresh attempt window.

The gap here is visibility. After running queue:retry, you need to know:

  1. How many jobs were re-queued?
  2. How many succeeded on the second pass?
  3. How many failed again, and with what exceptions?
  4. Did the retry batch cause latency increases for other queues?

Without answers to these questions, queue:retry is just a hope-based recovery strategy.

Automated Retry Monitoring

Manually checking php artisan queue:failed after every retry is not sustainable. What you need is a system that watches the failed jobs table continuously and alerts you to patterns: the same job class failing repeatedly, a spike in failures after a retry batch, or retried jobs failing with a different exception than the original.

Crontinel tracks retry outcomes alongside your normal queue metrics. When you run queue:retry, it correlates the re-queued jobs with their processing results and flags jobs that enter a fail-retry-fail loop. Instead of discovering at 6 PM that your 2 PM retry made things worse, you get an alert within minutes of the second failure.

The queue:retry command is a recovery tool, not a monitoring tool. It puts jobs back on the queue and walks away. Production systems need the follow-through: confirmation that retried jobs succeeded, early warning when they don’t, and enough context to decide whether the next step is another retry or a code fix.

See also

Start monitoring in minutes

Free for one app. No account needed to install and test locally.

composer require crontinel/laravel
php artisan crontinel:install
Get early access