Laravel Queues in Production: Jobs, Failures and the Things That Bite
Moving work to a queue is easy. Keeping that queue healthy at 3am when a third-party API is down is the part nobody writes about.
Every slow request is a queue job waiting to happen. Sending email, generating a PDF, calling a payment provider, resizing an upload — none of that belongs in the request cycle. Moving it out is the easy part.
Make Every Job Idempotent
A job will run twice. Not might — will. A worker gets killed mid-execution, the job returns to the queue, and it runs again. If your job charges a card, that is a real problem.
public function handle(): void
{
if ($this->order->fresh()->is_paid) {
return; // already handled by an earlier attempt
}
$this->chargeCard();
}
Set Retries and Backoff Deliberately
The defaults are rarely what you want. A job hitting a rate-limited API should back off exponentially, not hammer it three times in a second.
public $tries = 5;
public $backoff = [10, 30, 120, 600];
public $timeout = 60;
Watch the failed_jobs Table
Laravel writes every permanently failed job to failed_jobs. In most projects I audit, nobody has ever looked at it. Run php artisan queue:failed and you often find a year of silently dropped emails.
Send yourself an alert on failure:
public function failed(Throwable $e): void
{
Log::error('Payment job failed', ['order' => $this->order->id, 'error' => $e->getMessage()]);
}
Supervisor, Not nohup
A worker process will die. Supervisor restarts it. Without a process manager, your queue stops silently and nothing tells you.
[program:laravel-worker]
command=php /var/www/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
numprocs=2
Always Restart Workers on Deploy
Workers hold your code in memory. Deploying new code without php artisan queue:restart means jobs keep running the old version — a genuinely confusing bug to chase.
What to Monitor
- Queue depth — a number that only goes up means workers are dead or too slow.
- Failed job count in the last hour.
- Oldest pending job age — better signal than depth alone.