Skip to content
Muhammet Şafak
tr
Framework & Library 4 min read

Async Job Processing with Laravel Queue and Supervisor

How to set up asynchronous job queues in Laravel using the database driver, and keep workers running reliably in production with Supervisor.


A web request has one job: respond to the user fast. But some operations are inherently slow — sending an email, processing an image, waiting on a third-party API. If you handle those inside the request-response cycle, the user sits there waiting for your slow work to finish. Laravel Queue exists exactly for this: push the work onto a queue now, return the response immediately, and do the actual work in the background.

In this post I walk through how I set up a job queue in Laravel and how I use Supervisor to keep it running in production.

Choosing a queue driver

Laravel Queue supports multiple drivers — database, Redis, Amazon SQS, and more. The driver determines where queued jobs are stored. The QUEUE_CONNECTION key in your .env file controls this, and its default is sync — meaning jobs are never queued at all; they run inline immediately. That’s convenient during development, but in production you’ll see zero benefit from Queue until you switch to a real driver.

In this post I’m using the database driver. It’s sufficient for small to medium workloads and requires no additional infrastructure. Jobs are stored in a jobs table; to create it:

php artisan queue:table
php artisan migrate

Defining a job

Every piece of work pushed onto the queue is a class. As an example, I’ll create a SendMailJob that sends an email in the background:

php artisan make:job SendMailJob

I fill out the generated class, clarifying what parameters it accepts and what it does:

<?php
namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class SendMailJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(
        private string $name,
        private string $mail,
    ) {}

    public function handle(): void
    {
        $subject = 'Merhaba ' . $this->name;
        $msg = 'Sitemize üye olduğunuz için teşekkür ederiz!';
        if (!mail($this->mail, $subject, $msg)) {
            throw new \Exception('Mail gönderilemedi.');
        }
    }
}

Two points matter here. First: the data I receive in __construct is stored as class properties — Laravel serializes this data and holds it in the queue, then restores it when the job runs. For that reason, it’s good practice to pass simple values (IDs, short strings) to the constructor rather than large objects. Second: inside handle, I throw an exception when the job fails. This is not optional — the queue’s retry mechanism only kicks in when it sees an exception. A job that silently returns on failure is treated as successful.

Dispatching a job is a single line:

\App\Jobs\SendMailJob::dispatch('Muhammet', '[email protected]');

Processing jobs

Jobs pushed onto the queue don’t run by themselves; you need a worker to process them:

php artisan queue:work

This command continuously listens to the queue and processes jobs as they arrive. If you want to separate jobs by priority, you can use named queues with onQueue() and point the worker at those queues:

\App\Jobs\SendMailJob::dispatch($name, $mail)->onQueue('medium');
php artisan queue:work --queue=high,medium,default

The order here determines priority: the worker won’t move on to medium until high is empty.

Supervisor: keeping workers alive

queue:work is a long-running process — and long-running processes die. An exception, a memory limit, a deployment… the worker stops, and if nobody notices, the queue silently backs up. In production, that’s unacceptable.

Supervisor is a tool on Unix systems that monitors background processes and restarts them when they stop. That’s exactly what I want for Laravel workers.

sudo apt-get update
sudo apt-get install supervisor -y

I create a configuration file under /etc/supervisor/conf.d/:

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /proje/yolu/artisan queue:work --queue=default --tries=3
autostart=true
autorestart=true
user=deploy
numprocs=8
redirect_stderr=true
stdout_logfile=/proje/yolu/storage/logs/worker.log
stopwaitsecs=3600

A few lines are critical. autorestart=true brings the worker back when it dies. numprocs=8 runs eight parallel workers — tune that number to your load. stopwaitsecs=3600 gives a running job up to an hour to finish during a deployment, so it isn’t cut off mid-execution. To register and start the configuration:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*

One final note

When you push a code change and deploy, the running workers continue holding the old code in memory. Add php artisan queue:restart to your deployment steps after every deploy — otherwise you’ll spend hours wondering why “I updated the code but it’s still behaving the old way.” I learned that one the hard way, more than once.

Laravel Queue and Supervisor together provide a simple but solid async processing layer: Laravel defines and dispatches jobs, and Supervisor ensures the processes that handle those jobs never stop running. Each is incomplete without the other; the value is in how they work together.

Tags: #PHP#Laravel
Share:

Comments

Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.

Related Posts

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind