← All articles

Laravel Queue Not Working: Supervisor Troubleshooting Guide

Diagnose a Laravel queue that is not processing jobs by checking its connection, worker, Supervisor process, permissions, timeouts, logs, and deployment flow.

Bakry Abdalsalam builds websites, applications, integrations, and WordPress products. Bakry Dev Hub documents the technical decisions behind this work.

THE SHORT VERSION

Trace one job end to end: confirm it is dispatched to the expected connection and queue, run a worker manually as the production user, then make Supervisor reproduce that known-good command.

When a Laravel queue “does not work,” the job may never be dispatched, may be stored on a different connection, may wait on a queue no worker consumes, or may fail after a worker reserves it. Supervisor only keeps a process alive; it cannot correct an application or queue configuration error.

Use a single test job and follow it from dispatch to completion.

Confirm the queue is actually asynchronous

Check the effective production environment:

QUEUE_CONNECTION=redis

If QUEUE_CONNECTION=sync, the job runs inside the web request and never appears in a worker. If the application uses cached configuration, changing .env alone is not enough:

php artisan config:show queue
php artisan config:clear
php artisan config:cache

Run these as part of a controlled deployment. Confirm that web processes and CLI workers use the same release directory and environment.

Next, verify that the job implements ShouldQueue and that the code path dispatches it. If the job is dispatched inside a database transaction, consider dispatching after commit so a fast worker does not load records that are still uncommitted.

Match the connection and queue name

A connection identifies the backend; a queue identifies a lane within that backend. A job sent to redis on the emails queue will wait forever if the worker only consumes default.

Example dispatch:

SendWelcomeEmail::dispatch($user)
    ->onConnection('redis')
    ->onQueue('emails');

The worker must match:

php artisan queue:work redis --queue=emails,default -vvv

Run one worker in the foreground from the current release directory. This exposes exceptions directly and separates Laravel from Supervisor:

cd /var/www/example/current
sudo -u www-data php artisan queue:work redis \
  --queue=emails,default \
  --sleep=3 \
  --tries=3 \
  --timeout=90 \
  --once -vvv

If this fails, fix the application, credentials, backend connection, or permissions first. If it succeeds, reproduce the command in Supervisor.

Inspect pending and failed work

Use the tools appropriate to the backend: inspect the database jobs table, Redis queue length, SQS metrics, or Laravel Horizon. For failed jobs:

php artisan queue:failed
php artisan queue:retry all

Do not retry everything until the underlying exception is fixed; retries can duplicate external side effects or overload a dependency. Read storage/logs/laravel.log and the Supervisor worker log with the exact failure time.

Also check whether jobs are delayed, rate-limited, released back to the queue, or blocked by uniqueness or overlap middleware. An empty queue can mean the worker is consuming and repeatedly failing very quickly.

Configure Supervisor from a known-good command

A practical Ubuntu configuration might be:

[program:example-worker]
process_name=%(program_name)s_%(process_num)02d
directory=/var/www/example/current
command=/usr/bin/php artisan queue:work redis --queue=emails,default --sleep=3 --tries=3 --timeout=90 --max-time=3600
user=www-data
numprocs=2
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
stopwaitsecs=120
redirect_stderr=true
stdout_logfile=/var/log/supervisor/example-worker.log

Use absolute paths and the same user that succeeded in the foreground test. stopwaitsecs should exceed the longest legitimate job duration so Supervisor does not kill work during a graceful stop.

Load the configuration:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status "example-worker:*"

If the process enters BACKOFF or FATAL, read the log and run the configured command exactly as the configured user. Typical causes are a wrong PHP path, wrong working directory, missing .env, unreadable release files, or an unwritable storage directory.

Align timeout and retry settings

Laravel’s worker --timeout should be several seconds shorter than the queue connection’s retry_after. Otherwise, the backend can make a job available again while the first worker still runs it.

For example:

// config/queue.php
'retry_after' => 120,
command=/usr/bin/php artisan queue:work redis --timeout=90 --tries=3

External calls also need their own connection and response timeouts. A worker timeout is a final guard, not a substitute for bounded HTTP or database operations.

Design jobs to tolerate retries. Payment, email, and webhook side effects need stable idempotency keys or unique constraints; the Laravel webhook idempotency guide shows a durable pattern.

Restart workers during every deployment

Queue workers are long-lived and do not automatically load new application code. After a successful release and migration:

php artisan queue:restart

Laravel stores the restart signal in cache, so all workers must use the expected cache backend. Supervisor should start replacement processes after the old workers exit gracefully.

When using a current symlink, set Supervisor’s directory carefully and confirm new workers start in the new release. A reliable zero-downtime Laravel deployment treats worker restart as part of the release, not a manual afterthought.

Verify and monitor the result

Dispatch a diagnostic job with a unique ID and log these fields: job class, job ID, queue, attempt, start time, end time, and outcome. Never log serialized secrets or complete customer payloads.

Verify:

  1. The job appears on the intended backend and queue.
  2. A production-user worker reserves it.
  3. The job’s intended side effect occurs once.
  4. supervisorctl status remains RUNNING.
  5. A deployment causes a graceful worker restart.
  6. Failed-job count and queue latency are monitored.

Common mistakes

The most common queue failures are a production sync connection, mismatched queue names, config cache containing an old .env, Supervisor running as the wrong user, workers pointing at an old release, and --timeout exceeding retry_after.

Avoid fixing the symptom by adding more workers. First prove one worker processes one job correctly; then choose concurrency from job duration, backend capacity, downstream rate limits, and available memory.

Frequently asked questions

Why are jobs waiting while Supervisor says RUNNING?

RUNNING only confirms that a process exists. The worker may consume a different connection or queue, use cached configuration, or repeatedly fail jobs. Compare the Supervisor command with the job’s actual destination.

Should production use queue:listen or queue:work?

queue:work is the normal efficient production worker and should be supervised and restarted during deployment. queue:listen reloads the application for every job and has different performance characteristics.

Official references

Have a question about this guide or an idea for a technical collaboration? Contact Bakry through the Dev Hub.

End of field note.