iamfarhad/laravel-rabbitmq
Production-ready RabbitMQ queue driver for Laravel with native Queue integration. Built on ext-amqp with connection/channel pooling, configurable topology, Horizon hooks, Octane-safe resets, and optional high-performance basic_consume workers plus admin Artisan commands.
Native ext-amqp RabbitMQ queue driver for Laravel production workloads.
Built for teams that control their infrastructure and want native RabbitMQ performance for long-running Laravel workers, connection/channel pooling, publisher confirms, quorum queues, Horizon support, Octane support, and RabbitMQ 3.13 / 4.x readiness.
Most Laravel RabbitMQ packages optimize for Composer-only installation. This package intentionally optimizes for production systems where the PHP runtime can include native ext-amqp.
Use it when you want:
ext-amqp implementation.basic_consume worker mode.vladimir-yuldashev/laravel-queue-rabbitmqext-amqp PHP extension.ext-pcntl only when running rabbitmq:consume --num-processes with a value greater than 1.See SUPPORT.md for the full Laravel/PHP/RabbitMQ support matrix.
composer require iamfarhad/laravel-rabbitmq
Install the AMQP extension when it is not already available:
pecl install amqp
For Debian/Ubuntu images, install the native dependency first:
sudo apt-get update
sudo apt-get install -y librabbitmq-dev libssh-dev
sudo pecl install amqp
For Docker, Alpine, Laravel Sail, and GitHub Actions examples, see the installation guide.
Publish the config:
php artisan vendor:publish \
--provider="iamfarhad\\LaravelRabbitMQ\\LaravelRabbitQueueServiceProvider" \
--tag="config"
Set Laravel to use RabbitMQ:
QUEUE_CONNECTION=rabbitmq
Start RabbitMQ locally:
docker run -d --name rabbitmq \
-p 5672:5672 \
-p 15672:15672 \
rabbitmq:3.13-management
Configure your application:
QUEUE_CONNECTION=rabbitmq
RABBITMQ_HOST=127.0.0.1
RABBITMQ_PORT=5672
RABBITMQ_USER=guest
RABBITMQ_PASSWORD=guest
RABBITMQ_VHOST=/
RABBITMQ_QUEUE=default
Dispatch Laravel jobs normally:
dispatch(new App\Jobs\ProcessPodcast($podcast));
dispatch(new App\Jobs\ProcessPodcast($podcast))->onQueue('podcasts');
dispatch(new App\Jobs\ProcessPodcast($podcast))->delay(now()->addMinutes(10));
Run a worker:
php artisan rabbitmq:consume --queue=default --num-processes=1
Laravel's default worker also works:
php artisan queue:work rabbitmq --queue=default
For production, start with explicit heartbeat, timeout, retry, and health-check settings:
QUEUE_CONNECTION=rabbitmq
RABBITMQ_CONSUME_MODE=poll
RABBITMQ_HEARTBEAT_CONNECTION=60
RABBITMQ_CONNECT_TIMEOUT=10
RABBITMQ_READ_TIMEOUT=120
RABBITMQ_WRITE_TIMEOUT=30
RABBITMQ_MAX_RETRIES=3
RABBITMQ_RETRY_DELAY=1000
RABBITMQ_HEALTH_CHECK_ENABLED=true
RABBITMQ_HEALTH_CHECK_INTERVAL=30
Set RABBITMQ_READ_TIMEOUT to at least twice the heartbeat, so a half-open TCP
connection cannot hang a worker indefinitely while still leaving room for
heartbeat frames.
With RABBITMQ_CONSUME_MODE=consume, leave RABBITMQ_READ_TIMEOUT=0 instead: a
non-zero read timeout aborts a blocking basic_consume whenever the queue sits
idle for that long.
See production deployment for Supervisor, systemd, Docker Compose, Kubernetes, prefetch, publisher confirms, quorum queues, and dead-letter routing examples.
'hosts' => [
[
'host' => 'rabbitmq-1',
'port' => 5672,
'user' => 'laravel',
'password' => 'secret',
'vhost' => '/',
],
[
'host' => 'rabbitmq-2',
'port' => 5672,
'user' => 'laravel',
'password' => 'secret',
'vhost' => '/',
],
],
RABBITMQ_MAX_CONNECTIONS=10
RABBITMQ_MIN_CONNECTIONS=2
RABBITMQ_MAX_CHANNELS_PER_CONNECTION=100
RABBITMQ_MAX_RETRIES=3
RABBITMQ_RETRY_DELAY=1000
RABBITMQ_HEALTH_CHECK_ENABLED=true
RABBITMQ_HEALTH_CHECK_INTERVAL=30
By default RABBITMQ_EXCHANGE is empty, so jobs are published through the
default exchange, which routes on the literal queue name. Nothing else is
needed, and RABBITMQ_EXCHANGE_ROUTING_KEY is ignored in this mode — the
default exchange has no other way to route.
To publish through your own exchange:
RABBITMQ_EXCHANGE=jobs
RABBITMQ_EXCHANGE_TYPE=topic
RABBITMQ_EXCHANGE_ROUTING_KEY=jobs.%s
%s is replaced with the Laravel queue name. For example, queue emails publishes with routing key jobs.emails.
With a non-empty exchange the driver declares the exchange, declares the queue, and binds the queue to it with that routing key. Without the binding the broker silently discards every message, and publisher confirms acknowledge an unroutable message, so the loss is invisible.
Upgrading with an exchange already configured: if you created the binding by hand, check that it matches what the driver will create — same exchange, same routing key. A second binding on a different routing key delivers every message twice.
Additional bindings can be declared per queue:
'queues' => [
'orders' => [
'bindings' => [
['exchange' => 'events', 'exchange_type' => 'topic', 'routing_key' => 'order.*'],
],
],
],
To make an unroutable publish fail loudly instead of vanishing, enable publisher confirms with the mandatory flag:
RABBITMQ_PUBLISHER_CONFIRMS_ENABLED=true
RABBITMQ_PUBLISHER_CONFIRMS_MANDATORY=true
->delay() works without any broker plugin: the driver routes the job through a
per-TTL delay queue that dead-letters back to the target queue.
Because each distinct TTL needs its own queue, delays are rounded up into buckets so jittered backoff cannot create an unbounded number of them. Rounding up never fires a job early.
# Bucket size in milliseconds. Set to 1 for exact TTLs.
RABBITMQ_DELAY_QUEUE_GRANULARITY=1000
If you need many distinct or sub-second delays, install the
rabbitmq_delayed_message_exchange plugin and use a single exchange instead:
RABBITMQ_DELAYED_PLUGIN_ENABLED=true
Every setting resolves per connection, so a second RabbitMQ connection gets its own topology rather than inheriting the first one's:
'connections' => [
'rabbitmq' => [
'driver' => 'rabbitmq',
'queue' => 'default',
],
'rabbitmq_analytics' => [
'driver' => 'rabbitmq',
'queue' => 'analytics',
'exchange' => 'analytics-events',
'quorum' => true,
],
],
dispatch(new App\Jobs\RecordEvent($event))->onConnection('rabbitmq_analytics');
Anything a connection omits falls back to the rabbitmq connection and then to
the package defaults. Name each connection for the broker's management UI with
RABBITMQ_CONNECTION_NAME, which is what lets you tell one application's
connections from another's.
use iamfarhad\LaravelRabbitMQ\Facades\RabbitMQ;
RabbitMQ::size('orders');
RabbitMQ::declareQueue('orders');
RabbitMQ::publishToExchange('events', $payload, 'order.created');
The facade resolves your default queue connection when that is a RabbitMQ
connection, otherwise the connection named rabbitmq.
poll is the default and uses basic_get. It is the safest mode and matches Laravel's worker lifecycle expectations.
php artisan rabbitmq:consume --queue=default --consume-mode=poll
consume uses RabbitMQ's basic_consume push-style delivery. It avoids polling overhead and is better for hot queues.
php artisan rabbitmq:consume --queue=default --consume-mode=consume
For consume mode, prefer one queue per worker process. Scale with Supervisor numprocs, containers, or Kubernetes replicas.
Two things specific to this mode:
basic.qos governs basic_consume deliveries,
so RABBITMQ_PREFETCH_COUNT does nothing in poll mode. It defaults to 1,
which is right for a single-threaded worker: anything prefetched beyond the job
in flight sits unacked behind it, where a timeout or crash turns it into a
redelivery rather than throughput. Raise it only for short, I/O-bound jobs.--stop-when-empty falls back to poll mode. basic_consume only evaluates
stop conditions when a delivery arrives, so an empty queue would never trigger
them and the worker would block forever. That combination switches to poll mode
for the run and logs why.See recipes for copy-paste examples covering:
# Pool stats
php artisan rabbitmq:pool-stats
php artisan rabbitmq:pool-stats --json
php artisan rabbitmq:pool-stats --watch --interval=5
php artisan rabbitmq:pool-stats rabbitmq_analytics
# Exchanges
php artisan rabbitmq:exchange-declare jobs --type=topic
# Queues
php artisan rabbitmq:queue-declare orders --durable=1
php artisan rabbitmq:queue-declare bulk --lazy=1
php artisan rabbitmq:queue-declare quorum-orders --quorum=1
php artisan rabbitmq:queue-declare critical --priority=10
php artisan rabbitmq:queue-purge orders --force
php artisan rabbitmq:queue-delete orders --force
# Any of them can target another RabbitMQ connection
php artisan rabbitmq:queue-declare orders --connection=rabbitmq_analytics
Pools are per-process, so rabbitmq:pool-stats reports the pool of the artisan
process running it — not the pools inside your worker processes.
composer format-test
composer analyse
composer test
The test suite talks to a real broker and requires ext-amqp; no test is
skipped, so a missing extension or broker shows up as failures rather than
silence. Point it at a broker with the usual environment variables:
docker run -d --name rabbitmq-test -p 5673:5672 \
-e RABBITMQ_DEFAULT_USER=laravel \
-e RABBITMQ_DEFAULT_PASS=secret \
-e RABBITMQ_DEFAULT_VHOST=b2b-field \
rabbitmq:4
composer test
phpunit.xml defaults to port 5673 so a test broker does not collide with a
local one on 5672.
Releases are published by the Release workflow
(Actions → Release → Run workflow), which validates before it tags: the version
must have exactly one dated ## [x.y.z] - YYYY-MM-DD section in CHANGELOG.md
with content, and the tag must not already exist. It then creates the tag and the
GitHub release from that section.
Publishing a release through the GitHub UI instead bypasses that check.
Class AMQPConnection not foundInstall and enable ext-amqp:
pecl install amqp
php -m | grep amqp
For detailed installation options, see installation guide.
pcntlInstall ext-pcntl, or run a single process:
php artisan rabbitmq:consume --queue=default --num-processes=1
Confirm Horizon is installed and set:
RABBITMQ_WORKER=horizon
Then restart your workers.
If this package helps you run RabbitMQ in production with Laravel, please consider giving it a star. It helps other production teams discover the project.
Please report vulnerabilities privately. See SECURITY.md.
Contributions are welcome. See CONTRIBUTING.md before opening a pull request.
The MIT License (MIT). See LICENSE for more information.
How can I help you explore Laravel packages today?