A running worker does not necessarily process the queue your job was sent to. Follow a reproducible example to check the connection and queue name, then confirm that your worker is consuming the right jobs.
On this page
A Laravel job can remain pending even while a queue worker is running. One easy-to-miss cause is a mismatch between the queue selected when dispatching the job and the queue the worker watches.
Start by comparing these two lines:
QueueProbe::dispatch()->onConnection('database')->onQueue('diagnostics');php artisan queue:work database --queue=defaultThe dispatch targets diagnostics; the worker targets default. They use the same connection, but they are not using the same queue. For this example, the worker needs --queue=diagnostics.
What we verified
We reproduced this mismatch with Laravel 11.44.0, PHP 8.3.25, and an isolated in-memory SQLite database. The test dispatched one diagnostic job and ran a worker once against each queue. The job wrote a marker to a test table when it executed.
| Check | Pending jobs | Execution markers |
|---|---|---|
| After dispatch | 1 | 0 |
| Worker checked default | 1 | 0 |
| Worker checked diagnostics | 0 | 1 |
The wrong queue did not produce a failed job: the worker simply had no matching job to process. That distinction matters when you are looking for an exception that never occurred.
The walkthrough below uses a log message instead of the test table so you can observe the result in an existing application's configured logs. This logger-based walkthrough has not been independently run on every Laravel version; the dispatch and worker behavior above was tested on the stated versions.
1. Create a small diagnostic job
Use a local or staging application with the database queue configured and its jobs table already migrated. Do not run an experiment on a queue containing customer work.
Generate the job:
php artisan make:job QueueProbeIn app/Jobs/QueueProbe.php, use this complete class:
<?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;
use Illuminate\Support\Facades\Log;
class QueueProbe implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function handle(): void
{
Log::info('Queue probe processed', ['queue' => $this->queue]);
}
}There is no email, payment, or external API call in this job. Its purpose is to make worker execution visible.
2. Dispatch to an explicit destination
Open Tinker in the same application:
php artisan tinkerThen enter:
App\Jobs\QueueProbe::dispatch()
->onConnection('database')
->onQueue('diagnostics');Wait for the next Tinker prompt before inspecting the database. For this example, the expected new row is in the database queue's jobs table with queue set to diagnostics.
If the row never appears, first check the exception output and the configured database. Do not assume a worker problem before confirming that dispatch succeeded.
3. Run the matching worker
In another terminal, from the same project directory, run:
php artisan queue:work database --queue=diagnostics --once --tries=1 -vThe connection argument is database; the named queue is diagnostics. After a successful run, the pending job should disappear and the configured logging destination should contain Queue probe processed. The log is not necessarily storage/logs/laravel.log: that depends on your logging configuration.
--once makes this a short diagnostic run, not a permanent worker service. If another worker already consumed the probe, you may find the log message before running this command.
4. Compare the real job with the real worker
If the probe works but your application job does not, write down the destination on both sides:
| Setting | Dispatch side | Worker side |
|---|---|---|
| Connection | onConnection(...) or the configured default | Argument after queue:work |
| Queue | onQueue(...) or the connection's default queue | --queue=... or the connection's default queue |
| Application | Project and environment dispatching the job | Project and environment running the worker |
For an application that uses both diagnostics and default, a worker can listen to both:
php artisan queue:work database --queue=diagnostics,defaultChoose queue ordering deliberately; earlier queues receive priority. In production, put the intended command in your worker process manager rather than relying on an open terminal.
5. If the destination matches, inspect the next failure
- Job fails after starting: inspect the exception and
php artisan queue:failedwhen failed-job storage is enabled. Fix the cause before retrying. - Worker uses old code: long-running workers need to restart after deployment. Laravel's
queue:restartsignals a graceful exit; a process manager must start replacement workers, and the restart signal depends on shared persistent cache configuration. - Configuration appears unchanged: cached configuration can hide
.envedits. Inspectconfig('queue.default')in Tinker and rebuild configuration through your deployment process. - Job runs immediately: inspect whether the connection is
sync; synchronous execution does not leave work waiting for a background worker.
Avoid clearing the entire queue to diagnose this problem. Removing pending jobs does not correct a mismatched destination.
Next step
Once the probe succeeds, compare your application's job with this minimal example. Change one variable at a time: destination first, then the job's own logic. That makes it easier to distinguish a worker configuration issue from an exception inside the job.
For a practical notification example, see Implementing Queues in Laravel for Notifications.
References: Laravel queue documentation, configuration caching, and Laravel Forge queue workers. Use the documentation for your installed Laravel version when applying deployment settings.