Laravel 13 Queues Explained: Jobs, Workers, and Failed Jobs

January 18, 2024 · 7 min read

2026 UPDATE - LARAVEL 13
We’re excited to announce that we have updated all of our blog post examples to reflect the new Laravel 13 version! Our previous examples were based on Laravel 10, but with the release of Laravel 13, we wanted to ensure that our readers have access to the most up-to-date information and examples.
Improved app performance with queues
Your site's performance can be just as important as the quality of your content. Slow load times or a sluggish interface can quickly turn readers away. That's where Laravel's queue system comes into play, helping you defer the execution of time-consuming tasks such as sending emails or processing images.
What are Laravel queues?
Laravel Queues provide a unified API for deferring tasks to a later time, allowing you to perform resource-intensive tasks in the background without affecting the user experience. By moving tasks such as email sending or feed processing to a queue, you can respond to user requests quickly, while the server works through the queued jobs at its own pace.
Setting up a queue
Laravel supports various queue backends like Redis, Amazon SQS, and database-driven queues. For beginners, the database queue driver is a good starting point as it doesn't require additional services.
To set up a database queue, first, create a migration for the jobs table:
php artisan queue:table
php artisan migrateNext, update your .env file to use the database queue driver:
// Other options
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
SESSION_DRIVER=file
SESSION_LIFETIME=120
// Other optionsCreating jobs
In Laravel, queued tasks are represented as "Jobs." You can generate a new job class using the command:
php artisan make:job SendNewPostNotification
This command will create a new job class in the app/Jobs directory. Within this class, you define the task you want to perform. Here's an example job that sends an email notification when a new post is published:
namespace App\Jobs;
use App\Models\Post;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendNewPostNotification implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $post;
public function __construct(Post $post)
{
$this->post = $post;
}
public function handle()
{
$mailable = new Mailable(...); // Your mailable class with post details
Mail::to('subscribers@example.com')->send($mailable);
}
}We explain the email process in detail in another article.
Laravel 13: PHP attributes for jobs
Laravel 13 introduces optional PHP 8 attributes as a modern alternative to defining job properties as class properties. Instead of setting $tries, $timeout, and $backoff as properties, you can annotate the job class directly:
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\Attributes\Timeout;
use Illuminate\Queue\Attributes\Backoff;
use Illuminate\Queue\Attributes\FailOnTimeout;
#[Tries(3)]
#[Timeout(60)]
#[Backoff(30)]
#[FailOnTimeout]
class SendNewPostNotification implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
// No need for $tries, $timeout, $backoff properties
// The attributes above replace them
public function handle(): void
{
// job logic here
}
}This is entirely optional. The traditional class properties still work and there is no functional difference. The attribute syntax is simply cleaner when working in modern PHP 8 codebases.
Dispatching jobs
Once you've created your job, dispatching it is simple. You can dispatch a job from anywhere in your application, such as from a controller method when a new blog post is created:
namespace App\Http\Controllers;
use App\Jobs\SendNewPostNotification;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function store(Request $request)
{
// Other stuff
// Store the blog post...
$post = Post::create($request->all());
// Dispatch the job to send the notification
SendNewPostNotification::dispatch($post);
// Other stuff
}
}Running the queue worker
To process the jobs in your queue, you need to run the queue worker. You can start the worker with the command:
php artisan queue:work
For production environments, you should configure a process monitor to ensure that the queue worker does not stop running.
queue:work vs queue:listen
Laravel provides two commands for processing queued jobs. Understanding the difference is important for both local development and production deployments.
queue:work starts a long-lived process that loads the application once and keeps it in memory. Each job is processed by the same in-memory instance. This is faster and is the recommended option for production. The downside is that if you deploy new code, the worker must be restarted to pick up the changes:
php artisan queue:workqueue:listen starts a new PHP process for each job. This is slower because the framework bootstraps on every job, but it means code changes are picked up automatically without restarting the worker. It is useful during local development:
php artisan queue:listenFor production, always use queue:work and restart workers after each deployment with php artisan queue:restart.
Handling failed jobs
Sometimes jobs fail. Laravel provides a way to handle failed jobs by inserting them into a failed_jobs table. To create this table, run:
php artisan queue:failed-table
php artisan migrateYou can then define the maximum number of times a job should be attempted before being logged as failed:
php artisan queue:work --tries=3
Once a job has exceeded its retry limit, it is inserted into the failed_jobs table with the exception message and stack trace. You can inspect and retry failed jobs using Artisan commands:
# List all failed jobs
php artisan queue:failed
# Retry a specific failed job by its ID
php artisan queue:retry 5
# Retry all failed jobs
php artisan queue:retry all
# Flush (delete) all failed jobs
php artisan queue:flushYou can also define a failed method on the job class to run custom logic when the job ultimately fails, such as sending an alert or rolling back a database operation:
public function failed(\Throwable $exception): void
{
// Send user notification, log the error, etc.
\Log::error('SendNewPostNotification failed: ' . $exception->getMessage());
}Running queue workers in production with supervisor
In production, the queue worker must keep running continuously. If it crashes, no jobs will be processed. The recommended tool for this is Supervisor, a process control system for Linux servers.
Install Supervisor on your server:
sudo apt-get install supervisorCreate 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/your-app/artisan queue:work database --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/your-app/storage/logs/worker.log
stopwaitsecs=3600After creating the config, reload Supervisor:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*Supervisor will restart the worker automatically if it exits. After deploying new code, run php artisan queue:restart to gracefully restart all workers so they pick up the new code.

Queue drivers: Which one to use?
Laravel supports several queue drivers. Here is a quick comparison to help you choose:
database - Stores jobs in a database table. No extra services needed. Good for small apps and beginners. Can become a bottleneck under high load.
redis - Fast, in-memory queue. Recommended for medium to large applications. Requires a Redis server.
sqs - Amazon Simple Queue Service. Highly scalable and managed. Best for AWS-hosted applications.
sync - Executes jobs immediately in the same process. Useful for local development and testing when you don't want a background worker running.
null - Discards all queued jobs. Useful in tests when you want to prevent jobs from running.
Testing queued jobs
Laravel provides a Queue::fake() helper that prevents jobs from actually being pushed to the queue during tests. You can then assert that a job was dispatched:
use Illuminate\Support\Facades\Queue;
use App\Jobs\SendNewPostNotification;
public function test_notification_job_is_dispatched_on_post_creation(): void
{
Queue::fake();
// Trigger the action that should dispatch the job
$this->post('/posts', ['title' => 'Hello World', 'content' => 'Content here']);
// Assert the job was dispatched
Queue::assertDispatched(SendNewPostNotification::class);
// Assert the job was dispatched with specific data
Queue::assertDispatched(SendNewPostNotification::class, function ($job) {
return $job->post->title === 'Hello World';
});
}You can also use Queue::assertNotDispatched() to verify a job was not pushed, and Queue::assertNothingDispatched() to assert no jobs were dispatched at all during the test.
Conclusion
Laravel Queues are a powerful feature that can significantly improve the responsiveness and efficiency of your application. By offloading time-consuming tasks to the background, you can ensure that your readers enjoy a fast and seamless experience on your site.
Upcoming Articles in the Series
This article is part of our series Laravel for Beginners: A Step-by-Step Guide to Learn the Concepts.
Bring Your Ideas to Life 🚀
If you need help with a Laravel project let's get in touch.
Lucky Media is proud to be recognized as a leading Laravel Development Agency
Frequently Asked Questions
What is the difference between queue:work and queue:listen?
queue:work boots the application once and keeps it in memory for all subsequent jobs, it is faster and recommended for production. queue:listen spawns a fresh PHP process for every job, which is slower but automatically picks up code changes without restarting. Use queue:work in production and queue:listen during development if you need auto-reload behaviour.
How do I handle failed jobs in Laravel?
Run php artisan queue:failed-table && php artisan migrate to create the failed_jobs table. Laravel will store failed jobs there automatically once a job exceeds its retry limit. Use php artisan queue:failed to list them, php artisan queue:retry {id} to retry one, and php artisan queue:retry all to retry all. You can also define a failed(Throwable $exception) method on the job class to run custom cleanup logic.
Which queue driver should I use for a small Laravel app?
The database driver is the easiest to get started with - it requires no extra infrastructure and stores jobs in your existing database. Set QUEUE_CONNECTION=database in your .env and run php artisan queue:table && php artisan migrate. Once your application grows, migrate to Redis for better throughput.
How do I test queued jobs in Laravel?
Use Queue::fake() at the start of your test to prevent jobs from actually being dispatched to a worker. Then trigger the action under test and use Queue::assertDispatched(MyJob::class) to verify the job was pushed. You can also pass a closure to assert on specific job properties. For jobs you want to actually execute during a test, set QUEUE_CONNECTION=sync in your test environment.
Technologies

Stay up-to-date
Be updated with all news, products and tips we share!
On this page
- 2026 UPDATE - LARAVEL 13
- Improved app performance with queues
- What are Laravel queues?
- Setting up a queue
- Running the queue worker
- Handling failed jobs
- Running queue workers in production with supervisor
- Queue drivers: Which one to use?
- Testing queued jobs
- Conclusion
- Upcoming Articles in the Series
- Bring Your Ideas to Life 🚀
- Frequently Asked Questions

