What Common Mistakes Lead to RabbitMQ Message Loss and How Can You Prevent Them?
RabbitMQ does not lose messages on its own. Message loss almost always happens at the seams: between a publisher that thinks it sent a message and a broker that never received it, or between a broker that thinks it delivered a message and a consumer that never finished processing it. Defaults make it easy to think you have durability when you do not. Most production message loss is the result of three or four predictable mistakes.
This guide covers the common mistakes that lead to RabbitMQ message loss (missing publisher confirms, transient messages, non-durable queues, auto-ack consumers, poison message loops, and TTL without a dead-letter target), how to detect each, and how to prevent recurrence.
Quick Answer
The most common causes of RabbitMQ message loss are: non-durable queues, non-persistent messages, missing publisher confirms, missing or incorrect consumer acks, no dead-letter routing for poison messages, and message expiry with no target queue. Prevention requires durable queues, delivery_mode=2 for messages that must survive restart, publisher confirms on the producer, manual acks on the consumer with correct retry and DLX configuration, and quorum queues for replication. Persistence alone is not durability; the broker must also be configured to retain the messages.
Categories Covered
- Producer-side loss: Mistake 1
- Broker durability: Mistakes 2 and 3
- Consumer-side loss: Mistake 4
- Failure handling: Mistakes 5 and 6
- Availability: Mistake 7
How a Message Can Be Lost
A message passes through several handoffs. Loss can happen at any of them:
- Producer to broker network handoff.
- Broker storing the message (or not).
- Routing from exchange to queue (or no queue).
- Queue surviving a broker restart (or not).
- Broker delivering to consumer.
- Consumer processing and acknowledging.
- Failure handling for poison messages.
Each step has a specific failure mode and a specific protection. The mistakes below are organised by where in this chain they occur.
Producer-Side Loss
Mistake 1: Publishing Without Publisher Confirms
What is happening
The application calls basic.publish and assumes the broker received it. If the network drops the message in flight, or the broker rejects it because routing has no destination, the application never finds out.
What it looks like
- Producer logs show successful publishes but downstream consumers receive fewer messages than expected.
- No correlation between publish count and queue ingress count.
- The producer does not log any handler for
basic.returnor for confirm timeouts.
First checks
- Compare the publish rate against the queue ingress rate.
- Confirm the producer registers handlers for
basic.returnand confirm timeouts.
How to fix it
Durable fix: enable publisher confirms on the channel and treat publishing as an asynchronous operation that must be confirmed before the producer considers the message delivered to the broker:
- Enable confirms:
channel.confirmSelect()(Java),channel.confirm_delivery()(Python), or the equivalent in your client. - Register a listener for nacks and unroutable returns.
- Resend or escalate on nack/return; do not silently drop.
Publisher confirms cover the producer-to-broker hop. They do not cover what happens after the broker has the message; that needs persistence and replication.
Common mistake
Calling confirmSelect once and then publishing without ever waiting on the result. Confirms are asynchronous; if the producer process dies before confirms arrive, the in-flight messages are effectively unacknowledged. Track outstanding sequence numbers and only commit upstream state when the confirm arrives.
Broker Durability
Mistake 2: Using Transient Messages for Important Work
What is happening
The default delivery_mode for a message is 1 (transient). Transient messages live in memory and are lost when the broker restarts, even on a durable queue. Many teams discover this only after their first broker restart.
What it looks like
- Messages disappear after a planned or unplanned broker restart.
- Queue depth drops to zero on restart even though no consumer was running.
- Producer code does not set
delivery_modeor sets it to1.
First checks
- Check whether producer code sets delivery_mode, and to what value.
- Check
messages_persistenton the queue.
How to fix it
Durable fix: set delivery_mode=2 (persistent) on messages that must survive a broker restart. Persistent messages are written to disk before the broker confirms them, so they remain available after restart.
Python
# Python (pika) example
channel.basic_publish(
exchange='orders',
routing_key='new',
body=payload,
properties=pika.BasicProperties(delivery_mode=2),
)
Common mistake
Marking messages persistent but publishing them to a non-durable queue. The message is written to disk, but the queue itself is removed on restart, so the message is lost along with the queue. Persistence requires both: durable queue and persistent message.
Version note
Quorum queues in RabbitMQ 4.x persist all data by default. With quorum queues, you no longer have to think about per-message delivery_mode for the queue contents themselves, but other producers may still publish to classic queues where it matters.
Mistake 3: Non-Durable Queues
What is happening
A non-durable queue is removed when the broker restarts. Any messages it held, persistent or not, are lost along with the queue. Auto-delete queues are also removed when the last consumer disconnects.
What it looks like
- Queue disappears from the management UI after restart.
- Producer publishes succeed (or return a 312 NO_ROUTE error via
basic.returnif the message is unroutable andmandatory=trueis set). - Application code declares the queue with
durable=false.
First checks
- Check the queue’s
durableflag. - Check application code for
durable=falseorauto_delete=truein queue declarations.
How to fix it
Durable fix: declare queues with durable=true if they must survive restart, and avoid auto_delete=true for queues that hold persistent work. Make sure both producer and consumer declare the queue with the same arguments to avoid PRECONDITION_FAILED during declaration.
Python
channel.queue_declare(queue='orders.new', durable=True)
Common mistake
Allowing one process to declare a queue with one set of arguments and another to declare it differently. The first declaration wins; later declarations get PRECONDITION_FAILED. Standardise queue declarations in shared code or define them via policy.
Consumer-Side Loss
Mistake 4: Auto-Ack on the Consumer
What is happening
Auto-ack tells the broker to consider the message acknowledged as soon as it is delivered. If the consumer crashes mid-processing, the message is gone. This is often used “to speed things up” or “because manual ack seems complicated”.
What it looks like
- Consumer logs show exceptions but no redelivery from RabbitMQ.
messages_unacknowledgedstays at zero even under high load.- Application code uses
auto_ack=trueornoAck=true.
First checks
- Check whether
messages_unacknowledgedstays at zero under load. - Check application code for
auto_ack=trueornoAck=true.
How to fix it
Durable fix: use manual acks for any work that must complete reliably. Ack on success, nack on failure. Configure prefetch to limit how many messages are in flight per consumer.
Python
def callback(ch, method, properties, body):
try:
process(body)
ch.basic_ack(delivery_tag=method.delivery_tag)
except RecoverableError:
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True)
except UnrecoverableError:
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)
channel.basic_qos(prefetch_count=10)
channel.basic_consume(queue=’orders.new’, on_message_callback=callback)
Common mistake
Acking before the work is actually finished, for example calling basic_ack before writing to the database. If the database write fails, the message is gone and the side effect did not happen. Ack only after the work commits.
Failure Handling
Mistake 5: Requeueing Poison Messages Forever
What is happening
A consumer hits a message it cannot process. It nacks with requeue=true. The broker redelivers. The consumer nacks again. The loop continues, blocking the queue and consuming resources. Eventually the consumer or its dependencies fail and messages are lost when state is reset.
What it looks like
- A specific message keeps reappearing in consumer logs.
- Queue throughput drops sharply but queue depth does not change.
- Quorum queue
x-delivery-limitis exceeded and messages are silently dropped (drops to zero in 4.x without dead-lettering).
First checks
- Check consumer logs for the same message reappearing.
- Check
messages_dead_letteredand quorum queue redelivery counts.
How to fix it
Durable fix: configure a dead-letter exchange (DLX) for every working queue. Nack with requeue=false once you know the message cannot be processed. The broker routes it to the DLX, where it can be inspected, replayed, or archived.
Ini, TOML
# Policy declaring a DLX for all orders queues
rabbitmqctl set_policy DLX "^orders\." \
'{"dead-letter-exchange":"orders.dlx"}' \
--apply-to queues
Version note
RabbitMQ 4.0 sets a default x-delivery-limit of 20 on quorum queues. Without dead-letter routing, messages that exceed this limit are dropped silently. Always configure a dead-letter target for quorum queues that may encounter poison messages.
Common mistake
Treating dead-letter routing as optional. In quorum queues from 4.0 onward, the absence of a DLX combined with the default delivery limit is a silent message loss path.
Mistake 6: Message Expiry Without a Target
What is happening
Messages or queues are configured with TTL (time-to-live). When the TTL expires, the message is dropped. If there is no dead-letter routing, the message is gone, with no record that it was ever published.
What it looks like
- Producers publish but messages disappear after a fixed interval.
- Queue depth drops without consumer activity.
- Message TTL or queue TTL is set on the queue.
First checks
- Check whether message TTL or queue TTL is set on the queue.
- Check whether the queue has a dead-letter exchange configured.
How to fix it
Durable fix: combine TTL with dead-letter routing if expired messages must be retained. Use a long retention queue or stream as the DLX target so expired messages can be inspected later.
Bash
# Apply both TTL and DLX via policy
rabbitmqctl set_policy expire-then-dlx "^session\." \
'{"message-ttl":60000,"dead-letter-exchange":"session.dlx"}' \
--apply-to queues
Common mistake
Setting a short TTL on a queue and then debugging a phantom message loss problem. TTL is not the problem; the missing DLX is.
Availability
Mistake 7: Treating Classic Queues as Highly Available
What is happening
Classic queues are not replicated. A node failure means the queue is unavailable until the node comes back. Until RabbitMQ 4.0, classic mirrored queues offered replication; from 4.0 they are removed. Teams that rely on classic queue mirroring after upgrading to 4.x will discover their queues are no longer replicated.
What it looks like
- The cluster runs RabbitMQ 4.x and still has policies referencing
ha-mode,ha-params, orha-sync-mode. - Queues are declared as classic (no
x-queue-type=quorum) and the cluster expects high availability. - After a node failure, queues hosted on that node are unavailable.
First checks
- Check policies for
ha-mode,ha-params, orha-sync-mode keys. - Check whether queues that need HA declare x-queue-type=quorum.
How to fix it
Durable fix: for replicated, highly available queues in RabbitMQ 4.x and later, use quorum queues. Migrate any remaining classic mirrored queues using the blue-green migration path or in-place where the workload allows. Update applications that need HA to declare quorum queues explicitly.
Version note
Classic queue mirroring was deprecated in 2021 and removed in RabbitMQ 4.0. Classic queues without mirroring still exist and are still supported, but they are not replicated. After upgrading to 4.x, mirroring-related policy keys have no effect.
Common mistake
Assuming that ha-mode, ha-params, or ha-sync-mode policies still provide replication after upgrading to 4.x. These keys have no effect from RabbitMQ 4.0 onward, so queues relying on them are unreplicated classic queues; declare quorum queues explicitly for workloads that need HA.
Summary Table
| Mistake | Primary signal | Likely cause | First check | Durable fix | Version note |
| No publisher confirms | Publish count > queue ingress | Network drop or unroutable publish never reported to producer | Compare publish rate vs queue ingress rate | Enable confirms and handle nack/return | |
| Transient messages | Queue empty after restart | Default delivery_mode=1 keeps messages in memory | Check producer sets delivery_mode; check messages_persistent | delivery_mode=2 | Quorum queues persist all data by default |
| Non-durable queue | Queue missing after restart | Queue declared with durable=false | Check the queue durableflag | durable=true on queue declaration | |
| Auto-ack | Crashes with no redelivery | auto_ack=true treats delivery as acknowledgement | Check messages_unacknowledged stays at zero under load | Manual ack after work commits | |
| Endless requeue | Same message repeatedly | Nack with requeue=true and no DLX | Check consumer logs and messages_dead_lettered | DLX + nack with requeue=false | Default x-delivery-limit of 20 on quorum queues from 4.0 |
| TTL without DLX | Messages disappear on a timer | TTL set with no dead-letter target | Check message TTL or queue TTL on the queue | Combine TTL with DLX | |
| Classic queues for HA | Queue unavailable after node failure | Classic queues are not replicated | Check policies for ha-modekeys and x-queue-type | Quorum queues | Mirroring removed in 4.0 |
Metrics to Monitor
| Metric | What it tells you | Related mistake |
| Publish rate vs queue ingress rate | Producer-to-broker integrity | No confirms |
messages_persistent | Persistent message count | Transient messages |
Queue durable flag | Whether queue survives restart | Non-durable queue |
messages_unacknowledged | Work held by consumers | Auto-ack |
messages_dead_lettered | DLX activity | Poison loop, TTL |
| Quorum queue redelivery counts | Approaching delivery limit | Endless requeue |
FAQ
Are persistent messages enough to guarantee no message loss?
No. Persistent messages must also live on a durable queue. The message is written to disk by the broker, but if the queue is non-durable, the queue (and the message with it) is removed on restart. Durability requires both: durable queue and persistent message.
Do I need publisher confirms if I use persistent messages?
Yes. Persistence affects what happens after the broker has the message. Confirms ensure the broker actually received and accepted it. Without confirms, a network or routing failure can lose the message before persistence is even relevant.
Why are quorum queues better than classic mirrored queues for delivery guarantees?
Quorum queues use Raft consensus to replicate messages across multiple nodes before confirming them. Classic mirrored queues replicated asynchronously and had known failure modes around split-brain and synchronisation. Quorum queues offer stronger guarantees and higher throughput. Classic mirrored queues were removed in RabbitMQ 4.0.
What happens to a poison message in a quorum queue without a DLX?
In RabbitMQ 4.x, the default x-delivery-limit on quorum queues is 20. After 20 unsuccessful redeliveries, the message is dropped if no dead-letter exchange is configured. Always configure a DLX on any quorum queue that may see poison messages.
Is auto-ack ever appropriate?
For non-critical, idempotent work where occasional loss is acceptable, yes. Examples include metrics emission and best-effort notifications. For anything that must complete reliably, including financial work, order processing, or anything with an external side effect, use manual ack.
How can I tell if my consumer is acking correctly?
Watch messages_unacknowledged per queue. If it grows continuously while consumers are running, acks are not happening fast enough or are being missed in some code path. If it stays at zero even under load, the consumer is using auto-ack.
When to Get Expert Help
“If you have seen unexplained message loss, missing acks, poison message loops, or unexpected behaviour after upgrading to RabbitMQ 4.x, a delivery-guarantee review can identify exactly where messages are being lost in your pipeline and what to change to prevent it. Seventh State reviews producer, broker, and consumer configuration, queue and message durability, dead-letter routing, and the 4.x migration state of your queues, then provides a remediation plan with safe migration paths for any classic mirrored queues that remain.”
| Seventh State Team




