RabbitMQ monitoring maturity model: from survival to expert
Most RabbitMQ monitoring setups fail in one of two ways. Either they watch almost nothing and discover outages from user reports, or they chart everything and page on noise. The difference is rarely tooling. It is knowing which signals actually predict the failure modes RabbitMQ is prone to: the memory wall, the disk halt, consumer black holes, split-brain, and silent message loss.
This guide organizes RabbitMQ signals into four maturity levels. Level 1 tells you the broker is on fire. Level 2 tells you where the fire is. Level 3 gives you leading indicators so you see the fire before it starts. Level 4 explains the internals well enough to debug the incidents that reach your third or fourth postmortem.
The model assumes RabbitMQ 3.13 or newer with quorum queues as the default queue type. Classic mirrored queues were removed in 4.0 and are not covered. Where behavior differs between 3.x and 4.x, it is called out inline.
How to use this model
Do not treat the levels as a checklist to complete in one sprint. Treat them as a diagnostic: find the highest level where every signal is collected, alerted on with sane thresholds, and understood by the on-call team. A team with Level 3 dashboards but no runbook for messages_unacknowledged growth is operationally at Level 1.
Two principles apply across all levels:
- Ratio signals beat absolute values. Queue depth, connection count, and memory in bytes are workload-dependent.
mem_used / mem_limit,fd_used / fd_total, and publish-versus-deliver imbalance travel across environments. - RabbitMQ failure modes are cliff-edge, not gradual. The memory and disk alarms do not degrade throughput; they stop all publishers cluster-wide the instant a threshold is crossed. Your monitoring must catch the approach, because there is no graceful degradation to observe at the threshold itself.
flowchart TD L1["Level 1: Survival
node alive, alarms, total depth, connections"] L2["Level 2: Operational
per-queue depth, consumers, rates, ratios, partitions"] L3["Level 3: Mature
memory breakdown, consumer utilisation, head age, churn, quorum leaders"] L4["Level 4: Expert
Erlang GC, Mnesia, dist-port queue, binary heap, redelivery"] L1 --> L2 --> L3 --> L4
Level 1: survival
The minimum to know whether RabbitMQ is alive and not on fire. Every signal here is binary or nearly binary, and every one maps to a page-level incident.
| Signal | Source | What it catches |
|---|---|---|
| Node availability | rabbitmq-diagnostics -q ping or GET /api/nodes field running | Erlang VM crash, hung node |
| Memory alarm | GET /api/nodes field mem_alarm | Cluster-wide publisher block from memory pressure |
| Disk free alarm | GET /api/nodes field disk_free_alarm | Cluster-wide publisher block from low disk |
| Total queue depth (ready + unacknowledged) | GET /api/overview field queue_totals | Unbounded message accumulation |
| Connection count | GET /api/overview field object_totals.connections | Clients unable to connect, mass disconnect |
# Level 1 spot check
rabbitmq-diagnostics -q ping
curl -s -u guest:guest http://localhost:15672/api/nodes | jq '.[] | {name, running, mem_alarm, disk_free_alarm}'
curl -s -u guest:guest http://localhost:15672/api/overview | jq '{queue_totals, connections: .object_totals.connections}'
Alerting notes that matter even at this level:
- Require sustained failure. A single failed probe can be a heavy GC pause. Page only after the condition survives more than two collection intervals, and suppress alerts during warm-up (uptime under 5 minutes) because memory spikes while queues repopulate after a restart.
- The alarms are cluster-wide. One node’s
mem_alarmblocks publishers on every node. Alert text should say “cluster publishing halted,” not “node X memory high.” - Do not use
GET /api/health/checks/is-in-serviceas a liveness check. It intentionally returns 503 on nodes paused or drained for maintenance. It is a readiness and drain signal, not a dead-node signal. - Check the default
disk_free_limit. The default is 50MB, which is dangerously small on any modern server. Routine log rotation can trigger a cluster-wide halt. Setdisk_free_limit.relativeto 1.0-2.0 (1-2x RAM) or an absolute value of 2GB or more.
Level 2: operational
What a competent team running RabbitMQ in production monitors. This level moves from “is it up” to “is message flow healthy,” and it is where per-queue visibility starts.
Everything in Level 1, plus:
| Signal | Source | What it catches |
|---|---|---|
| Message rates: publish, deliver_get, ack | GET /api/overview field message_stats | Publisher failure, consumer failure, flow imbalance |
| Per-queue ready and unacknowledged | GET /api/queues | Slow consumers, stuck consumers, poison messages |
| Per-queue consumer count | GET /api/queues field consumers | Zero consumers on a queue with a backlog |
| Memory usage ratio | mem_used / mem_limit on GET /api/nodes | Approach to the memory alarm |
| File descriptor ratio | fd_used / fd_total | Connection storms, FD exhaustion |
| Socket ratio | sockets_used / sockets_total | New-connection rejection before FD limit |
| Connection flow and blocked states | GET /api/connections field state | Write-path bottleneck vs. active resource alarm |
| Network partition status | GET /api/nodes field partitions | Split-brain |
| Vhost status | GET /api/vhosts field cluster_state | Partially available vhosts |
| Queue state | GET /api/queues field state | Crashed, down, stopped, or minority queues |
Three distinctions separate Level 2 teams from Level 1 teams:
- Unacknowledged messages are tracked separately from ready messages. Unacked messages are pinned in memory and cannot be paged to disk. A consumer holding thousands of unacked messages with a zero ack rate is the single most common precursor to a memory alarm.
deliver_get, notdeliver, is compared againstpublish. Pull-mode consumers usingbasic.getappear only ingetanddeliver_get. Comparing publish todeliveralone manufactures phantom imbalances.- Flow and blocked are not the same alert.
state: flowis per-connection, credit-based backpressure: surgical, transient, and normal in bursts. Alert only when more than 10% of connections sit inflowfor over 5 minutes.state: blockedorblockingmeans a resource alarm is active cluster-wide and is already covered by the Level 1 alarm signals.
Threshold guidance: alert on the memory ratio above 0.7 sustained, the FD and socket ratios above 0.8, and publish-versus-deliver_get imbalance exceeding 2x for 10 or more minutes relative to a rolling baseline.
Level 3: mature
Full operational coverage: internals, leading indicators, and the composite patterns that cause real incidents. This is the level where monitoring starts predicting instead of reacting.
Everything in Level 2, plus:
| Signal | Source | What it catches |
|---|---|---|
| Memory breakdown by category | rabbitmq-diagnostics memory_breakdown | Whether growth is message bodies, queue heaps, connections, or Mnesia |
| Consumer utilisation | GET /api/queues field consumer_utilisation | Consumers attached but ineffective on a backlogged queue |
| Head message age | head_message_timestamp on GET /api/queues | Latency degradation that depth alone hides |
| Connection, channel, and queue churn | GET /api/overview field churn_rates | Reconnect loops, channel-per-message anti-patterns, queue leaks |
| Quorum queue leader distribution | rabbitmqctl list_queues name type leader | Post-restart leader imbalance concentrating load on one node |
| Erlang run queue | run_queue on GET /api/nodes | Scheduler saturation before it causes heartbeat timeouts |
| Per-queue paged_out and persistent counts | GET /api/queues | Memory pressure paging, a leading indicator of the alarm |
| Redeliver rates | message_stats.redeliver | Consumer failure loops and poison messages |
| Return unroutable rate | message_stats.return_unroutable | Routing misconfiguration (only when publishers set mandatory=true) |
| Node uptime | GET /api/nodes field uptime | Unexpected restarts; also gates other alerts during warm-up |
| Cluster link traffic | cluster_links on GET /api/nodes | Inter-node connectivity, corroborated with partition status |
The patterns this level exists to catch:
- Consumer black hole: consumers connected, deliver rate near zero or positive, ack rate zero, ready depth rising. Connection health is not consumer health.
- Poison message loop: stable depth, consumers present, elevated redeliver rate, head message age climbing. Fix with a delivery limit (
x-delivery-limiton quorum queues) so the message dead-letters instead of looping. - Silent routing loss: publish rate healthy, deliver_get zero, depth flat. If publishers do not set
mandatory=true, unroutable messages are discarded silently andreturn_unroutablenever moves. The depth-and-deliver composite is the only broker-side signal. - False partitions: GC pauses exceeding
net_ticktime(default 60s) can produce transient partition detection. This is why partition alerts require a sustained condition, and why run queue and partitions belong on the same dashboard.
Head age caveats: it is computed from the publisher-set timestamp message property, so it is absent or zero when publishers do not set timestamps. The Prometheus per-object endpoint exposes rabbitmq_detailed_queue_head_message_timestamp; age itself is a computed value (now minus timestamp), not a direct metric. Treat age as workload-relative: a queue with 1,000 one-second-old messages is healthy, and 100 two-hour-old messages is an incident.
Level 4: expert
The deep, frequently missed signals that experienced operators add after their third or fourth major incident. Most require going beyond the Management API: Erlang eval, the Prometheus endpoint, or OS tooling.
Everything in Level 3, plus:
- Erlang process ratio (
proc_used / proc_total). Each connection, channel, and queue is an Erlang process. Exhaustion crashes the node. Alert above 0.8. - Erlang GC rate and bytes reclaimed (
rabbitmqctl eval 'erlang:statistics(garbage_collection).'). Sustained GC pressure is a latency source and keeps reported memory elevated. Userabbitmq-diagnostics force_gcdiagnostically: if memory drops afterward, the growth was lazy GC, not a leak. Warning:force_gctriggers garbage collection across processes and can cause a noticeable CPU spike; do not run it on a production node during peak traffic. - Binary heap memory (
rabbitmqctl eval 'erlang:memory(binary).'). Message bodies live in the reference-counted binary heap, which can stay bloated after queues drain because the last referencing process has not GC’d yet. This explains “memory stable, queues empty” confusion. In Prometheus per-object metrics, related process-level values appear aserlang_vm_dist_proc_heap_size_wordsanderlang_vm_dist_proc_min_bin_vheap_size_words. - Mnesia table sizes and transaction pressure (
rabbitmqctl eval '[{T, mnesia:table_info(T, size)} || T <- mnesia:system_info(tables)].'). Mnesia holds cluster metadata, not messages. Past roughly 100,000 queues, declaration operations and node rejoin sync become slow. Khepri is the successor metadata store and Mnesia support is planned for removal in a future major version; on clusters that have migrated to Khepri, watch Raft metrics (rabbitmq_raft_*) instead of or alongside Mnesia. - Distribution port send queue (
ss -tnpon port 25672). All inter-node traffic (metadata replication, Raft, internal RPC) shares one TCP connection per node pair. A growing send queue is the earliest sign of cluster-link saturation, which otherwise surfaces as confusing metadata timeouts and false partitions. The Prometheus plugin exposes this aserlang_vm_dist_port_queue_size_bytesanderlang_vm_dist_node_queue_size_bytes. - Per-channel and global redelivery counters (
rabbitmq_global_messages_redelivered_total). Prefer the global counters over per-connection or per-channel aggregates; older dashboards that sum per-connection counters produce nonsensical rate spikes after connection churn. - Raft health for quorum queues (
rabbitmq_raft_*metrics: commit latency, commit index, WAL files). - Publisher confirm latency. Not available broker-side; it must be measured from the publishing client. It is a 5-10 minute early warning before flow control or alarms bite.
Collection notes for this level:
- The Prometheus plugin on port 15692 is the right transport for most Level 3 and 4 metrics. Use the aggregated
/metricsendpoint by default;/metrics/per-objectis computationally expensive and should be reserved for the queues you actually need per-entity data on. - Management API polling more frequently than every 5-10 seconds can itself load the broker. Keep per-object scrapes at 30-second intervals in production.
- The management plugin’s internal stats collection can be disabled (
management_agent.disable_metrics_collector = true) without affecting the Prometheus plugin, which is useful on very large deployments.
How Netdata helps
The signals in this model are most useful when correlated, and correlation is where manual curl polling falls apart during an incident.
- Netdata’s RabbitMQ collector pulls the Management API signals behind Levels 1 through 3 (node alarms, memory and FD ratios, queue totals, message rates, churn, per-queue depth and consumers) at per-second resolution, so cliff-edge transitions are visible instead of averaged away.
- Queue depth, unacknowledged count, and ack rate on one dashboard makes the consumer black hole pattern (consumers present, ack rate zero, depth rising) readable at a glance.
- Memory ratio alongside
paged_outcounts separates genuine memory growth from paging activity, which is the leading indicator beforemem_alarmfires. - Churn counters next to connection and FD usage expose the “stable count, massive churn” leak that a point-in-time connection count hides.
- Uptime tracking lets you gate memory and rate alerts during post-restart warm-up instead of tuning them down globally.
- Anomaly detection on publish and deliver rates catches the publish-versus-deliver imbalance and silent-routing-loss composites without hand-tuned per-queue thresholds.
Related guides
- RabbitMQ monitoring checklist: the signals every production broker needs
- How RabbitMQ actually works in production: a mental model for operators
- RabbitMQ memory resource limit alarm: publishers blocked across the whole cluster
- RabbitMQ disk free limit alarm: free disk space insufficient and publishing halted
- RabbitMQ vm_memory_high_watermark: setting the memory alarm threshold correctly
- RabbitMQ disk_free_limit at the default 50MB: the production footgun
- RabbitMQ paging messages to disk: the paging ratio as an early memory warning
- RabbitMQ connection blocked / blocking: publishers frozen by a resource alarm
- RabbitMQ connection in flow state: credit-based backpressure explained
- RabbitMQ flow control vs resource alarms: the two throttling mechanisms operators confuse
- RabbitMQ queue backlog growing: messages_ready climbing and how to drain it
- RabbitMQ unacknowledged messages growing: the pinned-memory leak behind most alarms






