The Best RabbitMQ Hosting Platforms in 2026
RabbitMQ is twenty years old, written in Erlang, and still the most sensible thing to reach for when you need work distributed to a pool of workers with an acknowledgement per item. It has also spent a decade being compared unfavourably to Kafka by people who wanted a replayable log and bought a work queue, which is a bit like reviewing a hammer badly because it turned out to be poor at sawing.
I'm Ajay, I build PandaStack. We do not host RabbitMQ brokers and have no plans to — we host the producers, the consumers and the Postgres they write into. That means I have nothing to sell you in the broker column, which is the useful position to write a buyer's guide from. I will also spend a chunk of this post arguing that a decent number of readers do not need a broker at all.
What RabbitMQ is actually good at
RabbitMQ is a broker that routes individual messages to consumers and tracks the state of each one. A message is delivered, held as unacknowledged, and then either acked (gone forever) or nacked/rejected (requeued or dead-lettered). The unit of work is the message. That single design decision is what everything else follows from.
Kafka's unit is the partition offset. Consumers read a durable, ordered log and track where they are; nothing is 'consumed' in the sense of disappearing, and you can rewind to yesterday and reprocess. That is superb for event streaming, analytics pipelines and anything where multiple independent consumers want the same events. It is awkward for a work queue, because your parallelism is capped by the partition count and a single slow message blocks its partition behind it.
So the honest split, with no tribalism attached:
- Use RabbitMQ when jobs are independent, individually acknowledged, retried per-item, and consumers scale horizontally without coordination — image processing, email sending, webhook delivery, PDF generation, anything with a per-item retry policy.
- Use Kafka (or Redpanda, or Kinesis) when you want a durable replayable log, multiple independent consumer groups reading the same stream, ordering guarantees per key, and retention measured in days rather than milliseconds.
- Use RabbitMQ when routing is genuinely complex — topic exchanges with wildcard bindings, headers exchanges, fanout to several queues with different retry policies. Kafka pushes that logic into consumers; RabbitMQ does it in the broker.
- Use Kafka when throughput is measured in hundreds of thousands of messages per second and you need the log on disk anyway. RabbitMQ can be pushed hard, but it stops being cheap to operate well before Kafka does.
One more distinction that trips people up: RabbitMQ's job is to be empty. In a healthy system, messages arrive and leave within milliseconds and queue depth hovers near zero. A queue that is never empty is not a queue, it is a backlog with a nice management UI. Kafka's job is the opposite — the log is supposed to be full, and depth is not an alarm.
Quorum queues, and why classic mirrored queues are gone
If your RabbitMQ knowledge dates from before roughly 2021, the most important thing to update is the replication story. Classic mirrored queues — the old high-availability policy where a queue's contents were mirrored to other nodes — are deprecated and have been removed in current major versions. They had genuinely nasty behaviour during network partitions and node restarts, including the ability to silently lose confirmed messages during a mirror promotion.
Quorum queues are the replacement. They are backed by a Raft consensus log, replicated to an odd number of nodes, and they behave predictably under partition: a majority is needed to make progress, and if you lose it, the queue becomes unavailable rather than diverging. That is the correct trade for anything you care about. They are also the only sane default for a clustered deployment now, and any managed provider you evaluate should be defaulting new clusters to them.
- Quorum queues are always durable and always replicated — there is no non-durable variant, which removes a whole class of configuration mistakes.
- They cost more memory and disk per message than classic queues did, because every message goes through a Raft log on every member. Size accordingly.
- They do not support some classic-queue features — notably per-message TTL semantics differ, and priority queues arrived later and behave differently. Read the current docs rather than assuming parity.
- Streams are a third queue type, added in RabbitMQ 3.9, that gives you an append-only replayable log inside RabbitMQ. If you only wanted Kafka for one modest use case, streams may save you a second cluster.
The memory alarm: the failure mode that bites everyone
Here is the RabbitMQ outage nobody warns you about, and I would guess it accounts for a majority of first serious incidents with the product.
RabbitMQ watches its own memory use against a high watermark (historically 40% of system RAM, configurable). When it crosses that line it raises a memory alarm and blocks publishing connections. Not rejects — blocks. The TCP connection stops reading, publishers hang mid-call, and from the application's point of view the broker has simply stopped responding. There is a matching disk free-space alarm that behaves the same way.
Consumers keep draining, so the theory is that the queue empties, memory falls, and the alarm clears. That works beautifully when the cause was a transient publish spike. It works terribly when the cause is that your consumers are dead, slow or wedged — then nothing drains, the alarm never clears, and your entire producing fleet hangs on what looks like a network problem. I have watched a team spend forty minutes on a suspected DNS issue during exactly this.
Three defences, in order of how much they help:
- Alert on queue depth and on unacknowledged message count, with a threshold that fires long before the memory watermark. Depth is the leading indicator; the memory alarm is the crash.
- Set a publisher timeout and treat a blocked connection as a failure. Most clients expose a connection.blocked notification — handle it, shed load, and fail fast rather than accumulating hung threads.
- Bound your queues. A max-length policy with an overflow behaviour of drop-head or reject-publish means the queue has a ceiling you chose, instead of one your host's RAM chose for you. Dead-letter the overflow if the messages matter.
Lazy queues, disk spillover, and where messages actually live
Classic queues had a 'lazy' mode that pushed messages to disk as early as possible, trading throughput for a much smaller memory footprint — the correct setting for any queue that is expected to get deep. In modern versions this is no longer a knob you set; classic queues moved to a version 2 storage implementation that behaves lazily by default, and quorum queues have always written to disk first and kept only a bounded window in memory.
The practical upshot is that a current RabbitMQ handles a deep queue far better than the 2018-era one people's instincts are calibrated against. It does not make a deep queue healthy. Disk spillover buys you time to fix the consumers; it does not process anything, and it converts a memory problem into a disk-alarm problem, which is the same outage wearing a different hat.
Sizing: message rate, message size, and queue depth
Broker sizing conversations usually start with 'how many messages per second' and stop there, which is the least informative of the three numbers. What you need:
- Sustained publish rate and peak publish rate. The gap between them is your burst headroom requirement.
- Average and p99 message size. A 200-byte job id and a 2 MB embedded PDF are the same 'one message per second' and wildly different brokers. Put payloads in object storage and publish a pointer — this is the single highest-leverage change most teams can make.
- Expected steady-state depth, which should be approximately zero, and tolerable transient depth, which is what actually sizes your RAM and disk.
- Consumer count and per-message processing time. Throughput is consumers divided by processing time; the broker is almost never the limit.
- Number of connections and channels. Each connection costs memory and an Erlang process. A serverless fleet that opens a connection per invocation will exhaust a broker long before message volume does.
Then size for the burst, not the average. A broker sized for steady state is a broker that raises a memory alarm the first time a consumer deploy goes badly.
TLS, vhosts and the permissions model
AMQP 0-9-1 is a binary protocol on a raw TCP socket — 5672 plaintext, 5671 with TLS. Use 5671. Every managed provider offers it; a surprising number of self-managed clusters do not, on the theory that the VPC is a security boundary, which it is right up until it isn't.
Virtual hosts are RabbitMQ's isolation primitive: a vhost is a namespace with its own exchanges, queues and bindings, and users are granted configure/write/read permissions per vhost via regex. It is a real boundary for naming and access, and it is not a resource boundary — all vhosts on a node share the same memory, the same disk and the same memory alarm. One tenant filling a queue in vhost A blocks publishers in vhost B. If you need per-tenant blast-radius isolation, you need separate clusters, not separate vhosts.
A short operational health check that belongs in your runbook:
# Is this node healthy enough to take traffic?
rabbitmq-diagnostics -q ping
rabbitmq-diagnostics -q check_running
rabbitmq-diagnostics -q check_local_alarms # memory / disk alarms on THIS node
# Cluster-wide alarms and quorum queue membership
rabbitmq-diagnostics -q alarms
rabbitmq-queues quorum_status my-work-queue
# The number that actually predicts your next incident:
# depth, and how much is stuck in "delivered but not acked".
rabbitmqctl list_queues -p /prod \
name type messages messages_ready messages_unacknowledged consumers
# Who is connected, and is anyone blocked by an alarm?
rabbitmqctl list_connections name state user channelsThe consumer is where correctness lives
Two settings account for most of the difference between a well-behaved consumer and one that causes incidents: prefetch (QoS) and manual acknowledgement. Auto-ack means the broker considers a message delivered the moment it hits the socket — a consumer crash silently loses every in-flight message. An unbounded prefetch means one consumer pulls thousands of messages into local memory, starving the other consumers and turning a graceful restart into mass redelivery.
import json
import pika
params = pika.URLParameters("amqps://user:pass@broker.example.com:5671/%2Fprod")
params.heartbeat = 30
params.blocked_connection_timeout = 60 # don't hang forever on a memory alarm
conn = pika.BlockingConnection(params)
ch = conn.channel()
# Quorum queue: durable by construction, replicated via Raft.
ch.queue_declare(
queue="jobs",
durable=True,
arguments={
"x-queue-type": "quorum",
"x-max-length": 100_000, # a ceiling you chose
"x-overflow": "reject-publish", # backpressure, not silent loss
"x-dead-letter-exchange": "jobs.dlx",
"x-delivery-limit": 5, # poison messages go to the DLX
},
)
# Take a handful of messages at a time, not the whole queue.
ch.basic_qos(prefetch_count=8)
def handle(chan, method, props, body):
try:
job = json.loads(body)
process(job)
except TransientError:
# back to the queue; x-delivery-limit stops the infinite loop
chan.basic_nack(method.delivery_tag, requeue=True)
except Exception:
# permanently broken input -> dead-letter it, don't spin
chan.basic_nack(method.delivery_tag, requeue=False)
else:
chan.basic_ack(method.delivery_tag)
ch.basic_consume(queue="jobs", on_message_callback=handle, auto_ack=False)
ch.start_consuming()The Node equivalent, because half of you are here for that one:
import amqplib from "amqplib";
const conn = await amqplib.connect(process.env.AMQP_URL!, { heartbeat: 30 });
const ch = await conn.createChannel();
await ch.assertQueue("jobs", {
durable: true,
arguments: {
"x-queue-type": "quorum",
"x-dead-letter-exchange": "jobs.dlx",
"x-delivery-limit": 5,
},
});
await ch.prefetch(8);
// A blocked connection means the broker raised an alarm. Shed load loudly.
conn.on("blocked", (reason) => console.error("broker blocked publishes:", reason));
conn.on("unblocked", () => console.warn("broker unblocked"));
await ch.consume(
"jobs",
async (msg) => {
if (!msg) return;
try {
await process(JSON.parse(msg.content.toString()));
ch.ack(msg);
} catch (err) {
// requeue: false -> dead-letter. Retry policy belongs in the DLX,
// not in an infinite nack loop against a message that will never work.
ch.nack(msg, false, false);
}
},
{ noAck: false },
);Note what is missing from both: any attempt to make the consumer clever about scaling. That is deliberate, and it is the subject of the last section.
The managed options
Qualitatively, and with the usual instruction to verify pricing and limits against their current docs — this market moves and I am not going to quote you a number that is wrong by the time you read it.
- CloudAMQP — Model: dedicated and shared managed RabbitMQ across AWS, GCP and Azure, from a free shared tier up to multi-node clusters, with the management UI, plugins and metrics integrations exposed. Ops burden: lowest of the group; they run the Erlang, the upgrades and the alarms, and their support actually knows RabbitMQ rather than knowing 'a queue product'. Best for: teams who want RabbitMQ specifically and want somebody else to own it, and anyone who values being able to open a support ticket about quorum queue behaviour and get a real answer.
- Amazon MQ for RabbitMQ — Model: AWS-managed RabbitMQ broker, single-instance or clustered deployment, inside your VPC with IAM, CloudWatch and private networking wired in. Ops burden: low for infrastructure, moderate for version and feature currency — AWS lags upstream releases and pins you to the versions they support. Best for: shops already fully inside AWS where VPC-native networking, IAM and CloudWatch integration matter more than being on the newest RabbitMQ.
- Aiven for RabbitMQ — Model: managed RabbitMQ as part of a broader managed data platform, deployable across multiple clouds and regions with the same control plane as their Kafka, Postgres and OpenSearch services. Ops burden: low, with the useful property that one vendor relationship covers the broker and the rest of your data infrastructure. Best for: teams already running other Aiven services, and multi-cloud or EU-data-residency requirements where a single portable control plane is worth real money.
- DigitalOcean and similar app-cloud add-ons — Model: a managed broker provisioned next to your droplets and databases on the same private network, one click, one predictable monthly line item. Ops burden: low, with correspondingly less depth — fewer knobs, fewer plugins, less visibility into the Erlang runtime. Best for: small to mid-size deployments already on that cloud where price and proximity beat configurability.
- Self-hosted via the RabbitMQ Cluster Operator (Kubernetes) — Model: the official operator defines a RabbitmqCluster CRD and reconciles a real cluster — StatefulSet, persistent volumes, plugins, TLS — with a companion Messaging Topology Operator for declaring vhosts, users, queues and policies as Kubernetes resources. Ops burden: highest, and honestly so: you own upgrades, disk sizing, quorum membership after node loss, and the 3am memory alarm. Best for: teams already fluent in Kubernetes operators with a genuine reason to keep the broker in-cluster — data residency, cost at scale, or per-tenant clusters.
- Self-hosted on plain VMs — Model: apt install, a config file, and an Erlang cookie shared between nodes. Ops burden: highest of all, because you also own the things the operator would have automated. Best for: a single-node broker for a workload that can tolerate a restart, and essentially nothing else in production.
You may not need a broker at all
This is the section every RabbitMQ hosting roundup skips, so here it is first. A dedicated broker is a stateful service with its own cluster, its own failure modes, its own upgrade path and its own bill. Adding it should clear a bar.
- Postgres with SELECT ... FOR UPDATE SKIP LOCKED — the correct default for most teams. Safe concurrent dequeue, jobs transactional with the data they mutate, queryable with SQL, inspectable in psql at 3am, and no new service. This comfortably handles thousands of jobs per second on decent hardware, which is more than most applications will ever produce. It is also the only option where enqueueing a job and committing the row that caused it are the same transaction — no dual-write problem, no orphaned jobs referencing rows that got rolled back.
- Redis streams or lists — genuinely fast, one dependency lighter than RabbitMQ if you already run Redis, and with consumer groups and pending-entry lists you get acks and claim semantics. The catch is that Redis' durability and eviction story is much weaker: an evicted queue key is lost work, not a cache miss. Use it for work you can regenerate.
- SQS (or GCP Pub/Sub, or Azure Service Bus) — no cluster, no capacity planning, no memory alarm, priced per request. You give up rich routing, the AMQP protocol, and per-message priority, and you accept at-least-once delivery with visibility timeouts. For a plain work queue in a cloud you already use, this is frequently the right answer and RabbitMQ is the over-engineered one.
- RabbitMQ — when you actually need the routing. Topic exchanges with wildcard bindings, fanout to multiple queues with different retry policies, per-message priority, dead-letter chains with delays, and the AMQP protocol because your existing clients speak it. That is a real set of capabilities and nothing on this list replicates it.
The broker is rarely the hard part. Almost every 'RabbitMQ problem' I have been asked to look at was a consumer problem the broker was politely reporting.
The consumers are the hard part — and that's where we fit
Say it plainly: PandaStack does not host RabbitMQ brokers. If you want a managed broker, pick one from the list above. What we host is the other half of the system — the producers, the consumers, and the Postgres they write into.
That half is where the cost and the operational pain actually live. Consumers are long-running processes that must stay connected to hold a channel, they must scale with queue depth rather than with HTTP traffic, and in most fleets they sit idle a large fraction of the day burning money on reserved capacity to be ready for a burst that comes twice a week. Serverless functions handle the burst well and the connection model badly — an AMQP consumer wants a persistent connection with prefetch, which is exactly what a short-lived function runtime is worst at.
PandaStack runs workers as Firecracker microVMs. Snapshot-restore create is p50 179ms and p99 around 203ms, which means spinning up a worker in response to depth is a sub-second operation rather than a capacity-planning exercise, and idle costs nothing because there is no warm pool to keep alive. The first cold boot of a new template is about 3 seconds; after that every create takes the snapshot path.
from pandastack import Sandbox
# One isolated microVM per burst of work. No warm pool, no idle bill.
sbx = Sandbox.create(template="base", ttl_seconds=600)
sbx.filesystem.write("/app/consumer.py", CONSUMER_SOURCE)
sbx.exec("pip install pika")
# Consume until the queue drains, then let the TTL reap the VM.
result = sbx.exec(
"AMQP_URL=$BROKER_URL python /app/consumer.py --drain --idle-exit 30"
)
print(result.exit_code, result.stdout)The isolation matters more than it looks. A worker pool that runs per-tenant logic, customer-supplied transforms, or anything touching untrusted input has the same isolation requirements as any other untrusted workload, and usually gets a shared container because it is 'just a background job'. A microVM is a hardware-virtualised boundary, one per worker, and each agent pre-allocates 16,384 /30 subnets so per-worker network namespaces are not a scarce resource.
And if your workers write to Postgres — which they nearly all do — a managed Postgres on the same platform takes 30 to 90 seconds to create and gives you a database you can branch for a load test rather than pointing a synthetic consumer at production.
How to choose in ten minutes
- Write down whether you need to replay messages after they were processed. If yes, you want a log — Kafka, Redpanda, or RabbitMQ streams — not a work queue.
- Write down whether your routing is more complex than 'one queue, many workers'. If it isn't, price SQS or a Postgres table before you price a broker.
- If you do need RabbitMQ: pick CloudAMQP for depth of RabbitMQ expertise, Amazon MQ for VPC-native AWS integration, Aiven for multi-cloud and a shared control plane with the rest of your data stack, a cloud add-on for small and simple, and the Cluster Operator only if you already run Kubernetes operators competently.
- Confirm the plan defaults to quorum queues, offers TLS on 5671, and exposes queue depth and unacknowledged counts to your monitoring. Verify all three against their current docs.
- Then spend the rest of your budget on the consumers, because that is where your incidents will come from.
The summary
RabbitMQ remains the best mainstream answer for per-message acknowledged work distribution with real routing. Use quorum queues, never classic mirrored ones. Bound your queues so the ceiling is one you chose. Alert on depth, not on the memory alarm, because by the time the alarm fires your publishers are already hanging. Put payloads in object storage and publish pointers. Set prefetch and ack manually, always. And before you buy a broker at all, check whether a Postgres table with SKIP LOCKED does the job — for a large fraction of applications it does, and it removes an entire stateful service from your on-call rotation.
Frequently asked questions
Should I use RabbitMQ or Kafka?
It depends on whether your unit of work is a message or a log offset. RabbitMQ delivers individual messages to consumers, tracks each one as acknowledged or not, and supports per-message retry, dead-lettering and rich routing through exchanges — ideal for work queues where jobs are independent and parallelism should scale with consumer count. Kafka gives you a durable, replayable, partitioned log where consumers track offsets and nothing disappears on read — ideal for event streaming, multiple independent consumer groups over the same data, and reprocessing history. If you need to replay yesterday's events, you want a log. If you need to retry one failed job without touching its neighbours, you want a queue. RabbitMQ streams cover a modest slice of the log use case if you would rather not run two clusters.
What are quorum queues and do I have to migrate to them?
Quorum queues are RabbitMQ's Raft-based replicated queue type, and they replaced the old classic mirrored queues, which are deprecated and removed in current major versions. Mirrored queues could lose confirmed messages during mirror promotion after a partition; quorum queues instead require a majority of replicas to make progress and become unavailable rather than diverging. Yes, you have to migrate — and it is not transparent, because you cannot change a queue's type in place. The standard path is to declare new quorum queues alongside the old ones, cut producers over, let consumers drain the classic queues, then delete them. Do it while queues are shallow, not during an incident.
Why do my RabbitMQ publishers hang instead of getting an error?
Almost certainly the memory or disk alarm. When RabbitMQ crosses its memory high watermark or its free disk limit, it blocks publishing connections rather than rejecting them: the broker stops reading from the socket, and your publisher hangs mid-call with no error. It looks exactly like a network problem, which is why teams lose so much time on it. The alarm clears when consumers drain the queue and memory falls — but if the consumers are the reason the queue grew, it never clears. Handle your client's connection.blocked notification, set a blocked-connection timeout so publishes fail fast, and alert on queue depth well before the watermark, since depth is the leading indicator and the alarm is the crash.
Does PandaStack host RabbitMQ?
No, and we do not plan to. PandaStack runs Firecracker microVMs for applications, workers and serverless functions, plus managed Postgres — it is not a message broker host. Where we fit in a RabbitMQ architecture is the other half: the producers and consumers. Consumers are long-running processes that need a persistent AMQP connection with prefetch, which serverless function runtimes handle badly, and they need to scale on queue depth rather than HTTP traffic. Snapshot-restore create is p50 179ms, so scaling workers in response to depth is a sub-second operation with no warm pool to pay for at idle. Use CloudAMQP, Amazon MQ, Aiven or a self-managed cluster for the broker itself.
Can I just use Postgres instead of RabbitMQ for a job queue?
For most applications, yes. SELECT ... FOR UPDATE SKIP LOCKED gives you safe concurrent dequeue across any number of workers, and it comfortably handles thousands of jobs per second on ordinary hardware — well beyond what most applications generate. The underrated advantage is transactionality: enqueueing a job and committing the row that triggered it happen in the same transaction, so you cannot end up with a job referencing a row that got rolled back, which is a real class of bug with an external broker. What Postgres does not give you is RabbitMQ's routing — topic exchanges, fanout with per-queue retry policies, dead-letter chains, per-message priority. If you need those, buy the broker. If your queue is one table and many workers, you already have the answer installed.
Keep reading
49ms p50 cold start. Fork, snapshot, and scale to zero.