iamfarhad / laravel-rabbitmq
Native ext-amqp RabbitMQ queue driver for Laravel production workloads with connection pooling, publisher confirms, Horizon support, Octane support, quorum queues, and high-performance workers
Fund package maintenance!
Requires
- php: ^8.2
- ext-amqp: *
- illuminate/queue: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- laravel/pint: ^1.10
- mockery/mockery: ^1.5
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- phpstan/phpstan: ^2.1
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^9.0|^10.0|^11.0|^12.0|^13.0
Suggests
- ext-pcntl: Required only when running rabbitmq:consume with --num-processes greater than 1.
- laravel/horizon: Install to enable optional Horizon compatibility via RABBITMQ_WORKER=horizon.
- laravel/octane: Install to enable optional Octane pool reset compatibility.
This package is auto-updated.
Last update: 2026-08-18 13:08:29 UTC
README
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.
Why this package?
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:
- Laravel Queue API compatibility.
- Native
ext-amqpimplementation. - Connection and channel pooling with health checks and retry backoff.
- Multi-host production configuration.
- Configurable exchanges, exchange types, and routing keys.
- Lazy queues, priority queues, quorum queues, delayed messages, dead-letter routing, and failed-message rerouting.
- Publisher confirms, transactions, RPC helpers, exchange helpers, and queue management helpers.
- Optional Laravel Horizon integration.
- Optional Laravel Octane pool reset support.
- Optional high-performance
basic_consumeworker mode. - Artisan commands for declaring exchanges, declaring queues, purging queues, deleting queues, and viewing pool stats.
Documentation
- Why native ext-amqp?
- Installation guide
- Production deployment guide
- Recipes
- Benchmarks
- Support policy and compatibility matrix
- Security policy
- Contributing guide
- Upgrade guide
- Migration guide
- Comparison with
vladimir-yuldashev/laravel-queue-rabbitmq
Requirements
- PHP 8.2 or higher (Laravel 13 itself requires PHP 8.3 or higher).
- Laravel 12.x or 13.x, both built in CI. Laravel 10.x and 11.x are still allowed by the Composer constraints and supported on a best-effort basis — they are not CI-verified, because every released version of those two lines is currently flagged by Composer's security-advisory policy and cannot be installed without disabling that policy. If you are on Laravel 10 or 11, upgrade the framework.
- RabbitMQ 3.13 or 4.x for the primary supported/tested matrix; RabbitMQ 3.8-3.12 is best effort.
ext-amqpPHP extension.ext-pcntlonly when runningrabbitmq:consume --num-processeswith a value greater than1.
See SUPPORT.md for the full Laravel/PHP/RabbitMQ support matrix.
Installation
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
Quick start
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
Production baseline
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.
Configuration highlights
Multi-host configuration
'hosts' => [ [ 'host' => 'rabbitmq-1', 'port' => 5672, 'user' => 'laravel', 'password' => 'secret', 'vhost' => '/', ], [ 'host' => 'rabbitmq-2', 'port' => 5672, 'user' => 'laravel', 'password' => 'secret', 'vhost' => '/', ], ],
Pool configuration
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
Publishing topology
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
Delayed jobs
->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
Multiple connections
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.
Facade
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.
Worker modes
Poll mode
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 mode
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:
- Prefetch applies here only.
basic.qosgovernsbasic_consumedeliveries, soRABBITMQ_PREFETCH_COUNTdoes nothing in poll mode. It defaults to1, 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-emptyfalls back to poll mode.basic_consumeonly 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.
Common recipes
See recipes for copy-paste examples covering:
- Delayed jobs.
- Quorum queues.
- Priority queues.
- Publisher confirms.
- Dead-letter routing.
- Horizon.
- Octane.
- Multi-host failover.
- Hot queue workers.
Admin commands
# 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.
Testing and quality
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.
Releasing
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.
Troubleshooting
Class AMQPConnection not found
Install and enable ext-amqp:
pecl install amqp
php -m | grep amqp
For detailed installation options, see installation guide.
Parallel worker error about pcntl
Install ext-pcntl, or run a single process:
php artisan rabbitmq:consume --queue=default --num-processes=1
Horizon events do not appear
Confirm Horizon is installed and set:
RABBITMQ_WORKER=horizon
Then restart your workers.
Support the project
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.
Security
Please report vulnerabilities privately. See SECURITY.md.
Contributing
Contributions are welcome. See CONTRIBUTING.md before opening a pull request.
License
The MIT License (MIT). See LICENSE for more information.