Skip to main content

Laravel Queue Failed Jobs: Complete Troubleshooting Guide (2026)

Learn how to diagnose Laravel queue failed jobs, fix common worker and timeout errors, retry jobs safely, and prevent repeated failures in production.

Birendra Jung Rai Jul 25, 2026 14 min read

Facing the same issue?

This guide walks you through the exact fix — step by step.

Laravel Queue Failed Jobs: Complete Troubleshooting Guide (2026)

Quick answer: Do not begin with queue:retry all. First read the exception, fix the cause, restart long-running workers, and retry one safe job. Retry the remaining jobs only after the first one succeeds.

Why Laravel Queue Jobs Fail

A Laravel queue job fails when it throws an unhandled exception, exceeds its allowed attempts, or runs longer than its timeout rules allow. The failed job is usually the final symptom. The real problem may be stale code, missing data, a slow API, incorrect server permissions, exhausted memory, or a worker that was not restarted after deployment.

The correct response is not simply to retry the job. A blind retry can send the same email twice, charge a customer again, create duplicate records, or place an unstable third-party API under more pressure.

The Fast Troubleshooting Path

  1. Confirm that the application is using an asynchronous queue connection.

  2. Confirm that a queue worker is running and listening to the correct connection and queue.

  3. List the failed jobs and inspect the full exception.

  4. Group failures by job class, error message, and failure time.

  5. Fix the root cause before retrying anything.

  6. Restart long-running workers so they load the new code and configuration.

  7. Retry one job and verify its business result.

  8. Retry the remaining affected jobs in a controlled way.

1. Confirm the Queue Connection

Check the active queue connection in your environment file. The sync driver executes work inside the web request, so there is no background worker to inspect.

# .env
QUEUE_CONNECTION=database

# Or, for Redis:
QUEUE_CONNECTION=redis

After changing an environment value on a cached production application, rebuild the configuration cache and restart the workers.

php artisan config:cache
php artisan queue:restart

Important: Run these commands from the current release directory and as the same deployment user that normally manages the Laravel application.

2. Confirm That the Worker Is Running

For a short local test, start a worker in the foreground. The verbose flag makes the processed job names and results easier to see.

php artisan queue:work -v --tries=1

In production, the worker should normally be managed by Supervisor, systemd, Laravel Horizon, or another process monitor. If no worker is running, jobs remain pending. They do not become failed until a worker attempts them and the attempt fails.

Useful Supervisor checks on Ubuntu include:

sudo supervisorctl status
sudo supervisorctl tail -f laravel-worker:*

The exact Supervisor program name may be different on your server. Use the name shown by supervisorctl status.

3. List and Inspect Failed Jobs

Laravel can list the records stored in the failed_jobs table:

php artisan queue:failed

The output shows the job ID, connection, queue, class, and failure time. The most useful evidence is the exception text stored with the failed record and the matching application log.

tail -n 200 storage/logs/laravel.log

# Follow new log entries while testing:
tail -f storage/logs/laravel.log

Read the first meaningful application error, not only the final framework stack frames. Record the exception class, message, file, line, job class, payload identifiers, and failure time.

Security note: Do not paste full production payloads or stack traces into public tickets. They may contain customer data, internal paths, or credentials.

What If the failed_jobs Table Does Not Exist?

New Laravel applications usually include the migration. Older or customized projects may not. Generate and run it only after checking that an equivalent migration is not already present.

php artisan make:queue-failed-table
php artisan migrate

4. Find the Root Cause

Several failed rows may come from one incident. Group them before changing code. If fifty jobs failed with the same timeout during a ten-minute provider outage, investigate them as one failure pattern.

Symptom

Likely cause

First check

ModelNotFoundException

A referenced record was deleted or its ID is wrong.

Confirm the model ID and decide whether a missing record should fail or exit safely.

MaximumAttemptsExceededException

The job used all allowed attempts.

Find the earlier exception or repeated release that consumed the attempts.

Timeout or worker exit

The task is slow, blocked, or the timeout values conflict.

Measure the slow operation and compare timeout with retry_after.

Permission denied

The worker user cannot read or write a required path.

Check storage, cache, export, and upload directory ownership.

Connection refused

Database, Redis, mail, or an external API is unavailable.

Test connectivity from the worker's runtime environment.

Class or method not found

A long-running worker still has old application code.

Deploy dependencies correctly and restart the workers.

Duplicate result

The job ran more than once and is not idempotent.

Add a business-level uniqueness check or safe lock.

Common Cause A: The Worker Has Old Code

Queue workers are long-running PHP processes. A normal code deployment does not automatically reload them. This explains many failures that appear only after a release.

php artisan queue:restart

The command asks workers to exit gracefully after their current job. A process monitor must start them again. Laravel stores this restart signal in the cache, so a valid cache configuration is also required.

Common Cause B: Timeout and retry_after Are Misaligned

The worker timeout controls when Laravel terminates a slow worker process. The retry_after value controls when a reserved job becomes available for another attempt. The worker timeout should be several seconds shorter than retry_after.

# config/queue.php
'retry_after' => 90,

# Worker command
php artisan queue:work --timeout=80 --tries=3

If timeout is longer than retry_after, a second worker may receive the same job while the first worker is still running. This can produce duplicate emails, orders, exports, or payments.

Increasing both values is not a complete fix. First measure why the job is slow. Reduce oversized queries, process large files in chunks, add HTTP client timeouts, and split heavy work into smaller jobs when practical.

Common Cause C: A Third-Party Service Is Unstable

Mail providers, payment gateways, SMS services, and external APIs can return temporary errors. Immediate repeated attempts can make the problem worse.

Use a retry delay that gives the provider time to recover:

use Illuminate\Queue\Attributes\Backoff;
use Illuminate\Queue\Attributes\Tries;

#[Tries(5)]
#[Backoff([10, 60, 300])]
class SyncOrderToProvider implements ShouldQueue
{
// ...
}

Laravel 13 supports queue-related PHP attributes such as Tries, Backoff, Timeout, and FailOnTimeout. Older Laravel applications can define equivalent job properties or methods.

Common Cause D: The Job Was Dispatched Before Commit

A job may start before the database transaction that created its model has committed. The worker then receives a valid-looking ID but cannot find the row.

ProcessOrder::dispatch($order)->afterCommit();

Use afterCommit when the job depends on data created or updated inside the current transaction. You may also enable after_commit for a queue connection when that behavior should apply broadly.

Common Cause E: Missing Models and Fragile Payloads

Jobs often contain model identifiers that are no longer valid by the time the worker runs. Decide what the business rule should be. Some missing records are true errors. Others mean the work is no longer necessary.

public function handle(): void
{
$order = Order::find($this->orderId);

if (! $order) {
return;
}

// Continue safely...
}

Do not silently ignore missing data when the work is financially important. Log enough context and alert the team when human review is required.

Common Cause F: Permissions or Different Runtime Environments

A web request and a queue worker may run as different Linux users or in different containers. A feature can work in the browser but fail in the queue because the worker cannot write an export, read an upload, or append to the log.

  • Confirm the worker's operating-system user.

  • Check that storage and bootstrap/cache are writable by the correct application group.

  • Confirm that the worker container has the same mounted files and environment variables.

  • Avoid solving the problem with broad 777 permissions.

5. Reproduce the Failure Safely

Reproduce the smallest failing case in a local or staging environment when possible. Use production data only when your access and privacy rules allow it.

php artisan queue:work -v --tries=1 --once

The once option processes one queued job and exits. Use a dedicated test queue when you must isolate the experiment from normal work.

Before the test, confirm:

  • The same job class and relevant application version are being tested.

  • The required database record and files exist.

  • The queue connection and queue name match the dispatched job.

  • External services are mocked or use safe test credentials.

  • The test cannot send a real duplicate notification or payment.

6. Retry Failed Jobs Safely

Retry one failed job by its UUID:

php artisan queue:retry ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece

Retry selected jobs:

php artisan queue:retry ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece 91401d2c-0784-4f43-824c-34f94a33c24d

Retry every failed job from one queue:

php artisan queue:retry --queue=emails

Laravel also supports retrying all failed jobs:

php artisan queue:retry all

Production warning: Use queue:retry all only when the root cause is fixed, the jobs are safe to run again, and you have reviewed the size and age of the failed set.

Before a bulk retry, answer these questions:

  • Could the job charge money, send a message, or create an external record twice?

  • Does the job check whether the intended business action already happened?

  • Are old jobs still valid, or should some be discarded?

  • Can the downstream service handle the sudden load?

  • How will you verify success and stop the retry if a new problem appears?

Deleting Failed Job Records

Remove one failed record:

php artisan queue:forget 91401d2c-0784-4f43-824c-34f94a33c24d

Remove all failed records:

php artisan queue:flush

Remove records that failed at least 48 hours ago:

php artisan queue:flush --hours=48

Deleting a failed record does not fix or re-run the business action. Export or retain the evidence first if your support, audit, or compliance process needs it. Horizon users should use horizon:forget for individual failed jobs.

7. Make Jobs Safer Before They Fail Again

Set Clear Attempts, Backoff, and Timeout Rules

use Illuminate\Queue\Attributes\Backoff;
use Illuminate\Queue\Attributes\FailOnTimeout;
use Illuminate\Queue\Attributes\Timeout;
use Illuminate\Queue\Attributes\Tries;

#[Tries(5)]
#[Backoff([10, 60, 300])]
#[Timeout(120)]
#[FailOnTimeout]
class GenerateMonthlyReport implements ShouldQueue
{
// ...
}

Choose values from actual job behavior. A fast email job and a large report export should not share the same limits simply because they use the same worker.

Make the Job Idempotent

An idempotent job can be attempted more than once without creating a second business effect. This is one of the most important queue safety rules.

  • Use a unique business key for external requests when the provider supports idempotency keys.

  • Store a processed timestamp or external reference and check it before repeating the action.

  • Use database unique constraints for records that must exist only once.

  • Wrap related local database changes in a transaction.

  • Use locks for overlapping work, but give locks a sensible expiration.

Add a failed Method

The failed method is useful for alerts, cleanup, and status updates after Laravel marks the job as failed.

use Illuminate\Support\Facades\Log;
use Throwable;

public function failed(?Throwable $exception): void
{
Log::error('Monthly report job failed', [
'report_id' => $this->reportId,
'error' => $exception?->getMessage(),
]);
}

Laravel creates a fresh job instance before calling failed. Changes made only to job properties during handle are therefore not available inside failed. Persist important progress in the database instead.

8. Use a Production Process Manager

A foreground queue:work command stops when the terminal closes or the process crashes. A process manager keeps workers alive and restarts them after a timeout or deployment.

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/example/artisan queue:work redis --sleep=3 --tries=3 --timeout=80 --max-time=3600
directory=/var/www/example
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/example/storage/logs/worker.log
stopwaitsecs=3600

Replace the paths, user, connection, queue names, process count, and time limits with values that match your server. The process-manager stop timeout should be longer than the longest job so a normal restart does not kill valid work too early.

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status

9. Restart Workers During Every Deployment

A production deployment is incomplete until long-running queue workers are told to load the new release. Add the graceful restart to your deployment process after the code, dependencies, configuration, and migrations are ready.

php artisan queue:restart

Watch the process manager and application logs after deployment. A successful command response does not prove that new workers started correctly.

10. Monitor Queues Before Users Report the Problem

Good queue monitoring should answer three questions: Are workers alive? Is the pending queue growing? Are failure rates increasing?

  • Alert when failed jobs appear or exceed a normal threshold.

  • Monitor the age of the oldest pending job, not only the number of jobs.

  • Track execution time and memory use by job class.

  • Separate urgent queues from heavy background work.

  • Use Laravel Horizon when Redis queue visibility, balancing, and failed-job inspection fit the project.

  • Keep logs centralized and searchable in production.

Production Checklist

  • An asynchronous QUEUE_CONNECTION is configured.

  • The failed_jobs storage exists and is migrated.

  • A process manager keeps workers running.

  • Workers listen to the correct connection and queue names.

  • Worker timeout is shorter than retry_after.

  • Long jobs are measured, chunked, or separated.

  • Jobs define sensible attempts and backoff.

  • Important jobs are idempotent.

  • Workers restart during deployment.

  • Failures and queue delay produce alerts.

  • Retries are verified against the real business result.

Frequently Asked Questions

Why is my Laravel queue job not running?

The most common reasons are that no worker is running, the worker is listening to another queue, QUEUE_CONNECTION is wrong, the job is delayed, or the application is using the sync driver.

Where does Laravel store failed jobs?

By default, Laravel can store them in the failed_jobs database table. The failed-job configuration in config/queue.php controls the driver and storage details.

How do I see the real queue error?

Run php artisan queue:failed, inspect the stored exception, and match its failure time with storage/logs/laravel.log or your centralized production logs.

Is it safe to run queue:retry all?

Only after the cause is fixed and the jobs are safe to execute again. Review jobs that can charge money, send messages, or create external records before a bulk retry.

Why did the same Laravel job run twice?

A common cause is timeout being longer than retry_after. Network failures and worker crashes can also cause repeated delivery. Design important jobs to be idempotent.

Why does a queue job use old Laravel code after deployment?

Queue workers are long-running processes. Run php artisan queue:restart during deployment and make sure a process manager starts fresh workers.

Should I increase the timeout for every failed job?

No. Measure the slow operation first. A larger timeout may hide a slow query, blocked request, oversized file, or missing HTTP timeout.

What is the difference between jobs and failed_jobs?

The jobs table holds database-queue work waiting or reserved for processing. The failed_jobs table stores jobs that Laravel has marked as failed after an exception, timeout rule, or exhausted attempts.

Final Advice

A failed queue job is evidence, not clutter. Read it before deleting or retrying it. The safest sequence is simple: inspect, group, reproduce, fix, restart, retry one, verify, and then retry the remaining valid jobs.

When attempts, timeout rules, deployment restarts, idempotency, and monitoring are designed together, Laravel queues become predictable production infrastructure instead of a recurring source of hidden failures.

Official References

  • Laravel 13 Queues: https://laravel.com/docs/13.x/queues

  • Laravel 13 Horizon: https://laravel.com/docs/13.x/horizon

  • Laravel 13 Release Notes: https://laravel.com/docs/13.x/releases

Publishing Details

Recommended URL slug: laravel-queue-failed-jobs-complete-troubleshooting-guide-2026

SEO title: Laravel Queue Failed Jobs: Troubleshooting Guide 2026

Meta description: Fix Laravel queue failed jobs step by step. Inspect exceptions, retry safely, correct timeouts, restart workers, and prevent repeat failures.

Excerpt: Laravel queue jobs can fail because of stale workers, missing data, timeouts, permissions, or unstable external services. This guide shows how to find the real cause, retry jobs safely, and prevent the same failure in production.

Primary keyword: Laravel queue failed jobs

Related keywords: Laravel queue job failed; Laravel queue retry; queue:failed; queue worker not running; Laravel queue timeout; Supervisor Laravel queue

Suggested category: Laravel

Suggested tags: Laravel, Queues, Failed Jobs, Troubleshooting, Supervisor, Redis, Production

Want this fixed in your project?

I solve real Laravel production issues — quickly and properly.

Get Help →

💼 Real Work

I’ve solved similar issues in real production Laravel systems.

View Case Studies →
Profile

Birendra Jung Rai

Laravel Developer • System Architect • Debugging Specialist

Still stuck?

Let’s fix it properly — no trial and error.

Get Professional Help →