vladimir-yuldashev/laravel-queue-rabbitmq
RabbitMQ queue driver for Laravel. Adds a full-featured RabbitMQ backend to Laravel's queue system, supporting delayed and priority queues, exchange routing, and reliable message publishing and consumption.
1. Install and confirm your floors. v14.4.0 is a patch on the v14 line: PHP 8.1+ (PHP 8.4 is now in CI, so 8.4 is a supported target rather than a "works on my machine" claim) and Laravel 10/11/12. If you are on Laravel 12.34+ you specifically want ^14.4 — #638 fixes a compatibility break with that framework version. Nothing in this release changes the public API, so upgrading from 14.3.x is a composer update with no code changes.
composer require vyuldashev/laravel-queue-rabbitmq:^14.4
php artisan vendor:publish --tag=config # writes config/rabbitmq.php
2. Wire up the connection. The driver is not a replacement for queue.php, it adds another connection to it. Two files are involved: config/rabbitmq.php (broker credentials, hosts, SSL, heartbeat) and config/queue.php (the queue/exchange this connection defaults to).
// config/queue.php
'connections' => [
'rabbitmq' => [
'driver' => 'rabbitmq',
'queue' => env('RABBITMQ_QUEUE', 'default'),
'connection' => \PhpAmqpLib\Connection\AMQPLazyConnection::class,
'hosts' => [[
'host' => env('RABBITMQ_HOST', '127.0.0.1'),
'port' => env('RABBITMQ_PORT', 5672),
'user' => env('RABBITMQ_USER', 'guest'),
'password' => env('RABBITMQ_PASSWORD', 'guest'),
'vhost' => env('RABBITMQ_VHOST', '/'),
]],
'options' => [
'queue' => [
'job' => \VladimirYuldashev\LaravelQueueRabbitMQ\Queue\Jobs\RabbitMQJob::class,
'exchange' => 'app',
'exchange_type' => 'direct',
'exchange_routing_key' => 'app.default',
],
'heartbeat' => 0,
],
],
],
QUEUE_CONNECTION=rabbitmq
RABBITMQ_HOST=rabbitmq
RABBITMQ_VHOST=/
3. Where to look first. The published config/rabbitmq.php is small — hosts, SSL options, heartbeat. Almost everything you will actually tune day-to-day lives in the options array on the queue connection (exchange, routing key, declare flags, queue arguments). Bookmark the driver's QueueConfig/RabbitMQQueue classes in vendor/vyuldashev/laravel-queue-rabbitmq/src/Queue/ when you need the exact key names for your installed version.
4. First use case: prove the broker and credentials before touching application code. The package ships artisan commands for topology, and they fail loudly on a bad vhost, ACL, or port — which is far easier to debug than a worker that silently consumes nothing.
php artisan rabbitmq:queue:create default
php artisan rabbitmq:queue:bind default app --routing-key=app.default
php artisan rabbitmq:queue:purge default # confirm connectivity
php artisan rabbitmq:queue:delete default # clean up
Then flip one low-risk job (a notification, not an order) to $connection = 'rabbitmq', dispatch it, and run a verbose worker:
php artisan queue:work rabbitmq --queue=default -v
If the job never appears, the problem is topology or routing, not your job class — jump to the Gotchas section.
One connection, many queues (the default pattern). Rather than defining a connection per queue, keep a single rabbitmq connection and vary the queue per job. onQueue() sets the queue name, and the driver resolves exchange/routing key from the options.queue block:
class SendInvoice implements ShouldQueue
{
public $connection = 'rabbitmq';
public $queue = 'invoices';
public $tries = 5;
public $backoff = [10, 60, 300];
public $timeout = 120;
}
Multiple connections are worth it only when the broker, vhost, or credentials differ (e.g. a rabbitmq-priority connection pointing at a different exchange).
Topology as code. Declare exchanges and bindings from your deploy script or a one-off command instead of clicking in the management UI, so staging and production match:
Queue::connection('rabbitmq')->declareExchange(new Exchange('app', 'topic'));
Queue::connection('rabbitmq')->bindQueue('invoices', 'app', 'invoice.*');
Once infra owns topology (Terraform, a broker operator, a platform team), set 'declare' => false in the queue config so the app never attempts AMQP configure operations it lacks permission for.
Exchange types by intent. direct for point-to-point work queues (the common case). topic when routing keys encode domains (order.created, order.*) and consumers subscribe by pattern. fanout when the same event must reach several independent services — each gets its own queue bound to the exchange, so one consumer's backlog doesn't starve the others.
Delayed jobs and retries. delay(), later(), and $backoff all funnel into a delayed publish, which on RabbitMQ requires the rabbitmq_delayed_message_exchange plugin. Without it, delay does not behave as you expect — this is the single most common "our retries don't work" report. Configure the exchange accordingly:
'options' => ['queue' => [
'exchange' => 'delayed',
'exchange_type' => 'x-delayed-message',
'exchange_arguments' => ['x-delayed-type' => 'direct'],
]],
Failure handling — pick one path, not both. Laravel's failed-job provider (the failed_jobs table, queue:failed, queue:retry, queue:forget) is the operator-friendly default and is what your on-call runbook probably assumes. The alternative is broker-side dead-lettering: 'reroute_failed' => true with failed_exchange/failed_routing_key, or x-dead-letter-exchange in the queue arguments. Enabling both means every failure is recorded twice and someone eventually replays a message twice.
Priority and durability via queue arguments. Priority queues (x-max-priority) and quorum queues are configured by passing queue arguments in the connection config — worth knowing before you promise an SLA on a queue type the broker wasn't told to create.
Long-running consumers. Under supervisor you generally run core Laravel workers (queue:work rabbitmq) because they honour $tries, $timeout, --max-jobs, and queue:restart. Reach for the package's rabbitmq:consume when you need consumer-side control the core worker doesn't expose (explicit exchange/routing-key binding, its own memory/sleep/tries flags). Whichever you choose, use queue:work, not queue:listen — listen boots a fresh process per job and destroys throughput.
Deploy loop. php artisan queue:restart after every deploy (supervisor restarts pick up new code); scale by adding workers, not by raising prefetch to hide a slow job — prefetch just moves the backlog from the broker into one worker's memory.
Testing. Queue::fake() covers dispatch assertions in unit tests, but it asserts nothing about AMQP routing. Keep a small integration suite that runs against a real rabbitmq:3-management service container in CI, publishes, consumes, and asserts the job ran — that's the only layer that catches a wrong routing key or an undeclared exchange.
exchange set, messages go to the AMQP default exchange with the queue name as the routing key. It looks like it works in dev, then breaks the moment someone binds a consumer to a named exchange. Always set exchange and routing_key explicitly in anything beyond a spike./. 'vhost' => 'staging' fails; '/staging' works. '/' is the default vhost and is easy to omit by accident.$backoff/delay() seem ignored, check rabbitmq-plugins list on the broker before you touch application code.queue:flush and queue:clear are DB-driver features. They do nothing useful here; use php artisan rabbitmq:queue:purge <queue>.ShouldBeUnique-style checks) rather than assuming exactly-once. This matters more with quorum queues and aggressive --timeout values.--timeout below whatever the broker or your job's own HTTP clients expect, and don't let a job outlive its heartbeat. A worker that hangs blocks the queue for everyone behind it.AMQPLazyConnection. It defers connecting until the first publish/consume, which keeps web requests that never touch the queue from opening (and leaking) AMQP sockets.read, declaring needs configure/write. In production, prefer 'declare' => false and provision topology out-of-band.ready count rising means no consumer is bound or the consumer is subscribed to a different queue name, (4) unacked climbing means jobs are hanging — look at --timeout and prefetch, not at the broker.--queue is a comma-separated list of queue names, resolved by the driver per name. Queues that don't exist (and aren't declared, because declare is off) simply never yield messages — a silent, confusing "worker runs but does nothing".options.queue.job, the connection class via the connection key, and Laravel's Queue::before/after/failing events plus JobProcessing/JobProcessed give you metrics and tracing without editing the package. Override the job class only when you genuinely need custom AMQP property handling.14.3.* and had issues booting under Laravel 12.34, the fix is in #638, and PHP 8.4 users get a tested target for the first time. Pin ^14.4 and re-run your integration queue test once after the bump.How can I help you explore Laravel packages today?