Laravel horizon queued jobs fails with MaxAttemptsExceededException

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Mastering Timeouts in Laravel Horizon: Solving MaxAttemptsExceededException Failures

As a senior developer working with asynchronous processing, one of the most common frustrations is dealing with jobs that appear to hang or fail prematurely. When dispatching tasks through a system like Laravel Horizon, seeing consistent failures—especially those related to timeouts—can halt development momentum. This post dives deep into why your Horizon jobs might be failing around the 60-second mark, even when you’ve configured longer timeouts, and how to properly manage execution limits in a robust queue environment.

Understanding the Timeout Discrepancy

You are observing a classic symptom of a discrepancy between the job's internal timeout setting and the worker or queue system's operational limits. In your scenario, you’ve set timeout to 300 seconds (5 minutes) in your config/horizon.php, yet the job fails at 60 seconds. This strongly suggests that the failure is not caused by Laravel Horizon itself rejecting the job due to exceeding the configured time limit, but rather an external constraint or a misunderstanding of how the worker process handles execution time versus retry logic.

When dealing with queue workers (like those running Horizon), timeouts often stem from three potential areas:

  1. Job Execution Time: The actual code within your handle() method is taking too long to complete, leading to an external system intervention or a failure state imposed by the worker process itself.
  2. Worker Timeouts: The underlying queue driver (like Redis) or the environment setup imposes limits on how long a single command can run before being terminated.
  3. Retry Mechanism Conflict: If the job fails mid-execution, Horizon's retry mechanism kicks in. If the failure is instantaneous or due to an uncaught exception, it might trigger the MaxAttemptsExceededException faster than the full timeout duration would allow for observation.

Analyzing Your Horizon Configuration

Let’s look closely at the configuration you provided:

// config/horizon.php snippet
'defaults' => [
    'my-app-queue' => [
        // ... other settings
        'timeout' => 300, // Set to 5 minutes
    ],
],

Setting timeout to 300 seconds tells Horizon and the underlying worker that a job should not exceed five minutes before it is considered failed or timed out. If your manual testing shows the job can run longer than 60 seconds successfully, the issue likely lies in how the job interacts with the environment where the worker is executing, rather than just the Horizon configuration itself.

Best Practices for Robust Queue Timeouts

To properly set and enforce timeouts in a Laravel queue system, we need to look beyond just the Horizon settings and consider the execution environment.

1. Enforce Timeouts within the Job Itself (The Safest Approach)

While setting the timeout in Horizon is helpful for monitoring, the most reliable way to manage long-running tasks is to implement internal checks within your job logic. If a job needs more time, it should be designed to handle intermediate states or use external scheduling rather than relying solely on an arbitrary wall clock limit imposed by the queue worker.

For complex, long-running operations, consider breaking them down into smaller, sequential steps. This makes debugging easier and allows you to implement specific timeouts at each stage.

2. Worker Process Management

Since you are running the Horizon processor via a command (php /path/to/artisan horizon), ensure that the process itself is not being killed by external Docker or system limits before the job completes its allotted time. If you are using Docker, review your container's resource limits (CPU/Memory) to ensure they are sufficient for long computations.

3. Debugging with Manual Instantiation

Your approach of manually instantiating and calling handle() is excellent debugging practice. When testing the job manually outside the queue context, you isolate the problem to the code execution itself, confirming that the logic can execute successfully under ideal conditions. If it still fails during manual testing, the issue is purely within the class or its dependencies, not the queue system.

Conclusion

The failure of your Horizon jobs around the 60-second mark, despite a 300-second timeout setting, points toward an execution environment constraint rather than a simple Horizon configuration error. By focusing on internal job design—chunking large tasks and ensuring your worker environment has adequate resources—you can build a significantly more resilient system. Remember, managing asynchronous workflows requires understanding the interplay between code execution time and infrastructure limits. For deeper insights into building scalable applications, exploring patterns discussed in frameworks like those at laravelcompany.com is highly recommended.