RabbitMQ Error Guide: 'PRECONDITION_FAILED - unknown delivery tag' Ack Failure
Fix RabbitMQ 'unknown delivery tag' errors: acking on the wrong channel, double-ack, acking after auto-ack, and stale delivery tags after reconnect.
- #rabbitmq
- #troubleshooting
- #errors
- #channels
Exact Error Message
A PRECONDITION_FAILED - unknown delivery tag error fires when a consumer acknowledges (or rejects/nacks) a delivery using a tag the broker does not recognize on that channel. It is a channel-level (406) exception that closes the channel:
Channel error on connection <0.4120.0> (10.0.6.55:51890 -> 10.0.4.21:5672, vhost: '/', user: 'consumer'):
operation basic.ack caused a channel exception precondition_failed:
unknown delivery tag 27
In client libraries this often surfaces as the channel closing right after a successful message handler returns:
ChannelClosedException: channel error; protocol method:
#method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - unknown delivery tag 27, class-id=60, method-id=80)
What the Error Means
When the broker delivers a message to a consumer, it assigns a delivery tag: a monotonically increasing 64-bit integer that is unique per channel. The consumer later confirms the message with basic.ack (or rejects it with basic.nack/basic.reject) referencing that tag.
unknown delivery tag means the broker received an ack/nack for a tag that, on this channel, was never delivered or has already been settled. The tag is meaningless in the channel’s current context, so the broker refuses it and closes the channel. The message itself is unaffected — the bug is in how the application is acknowledging.
Common Causes
1. Acking on the wrong channel
Delivery tags are scoped to the channel that delivered the message. If a consumer receives a message on channel A but calls basic.ack on channel B (common when channels are pooled or shared incorrectly), the tag is unknown on B.
2. Double-acking the same delivery
Acking a message, then acking it again — or acking inside both a per-message handler and a batch cleanup — settles the tag the first time and makes the second ack reference an unknown tag.
3. Acking when the consumer is in auto-ack (no-ack) mode
If basic.consume was started with auto_ack=true, the broker considers every delivery acknowledged on send. A manual basic.ack afterwards references a tag the broker already discarded.
4. Acking a stale tag after a reconnect or channel reopen
Delivery tags reset when a channel closes. After automatic recovery reopens the channel, any tags captured before the reconnect are invalid; acking them yields unknown delivery tag.
5. Off-by-one or multiple-flag misuse
Using basic.ack with the multiple=true flag incorrectly, or incrementing/storing the tag wrong, can reference a tag that was already covered by a prior multiple-ack.
How to Reproduce the Error
Consume a message in auto-ack mode, then try to ack it manually:
# Publish one message
rabbitmqadmin publish routing_key=udt-demo payload="hello"
# Get it with auto-ack (ackmode=ack_requeue_false acknowledges on delivery)
rabbitmqadmin get queue=udt-demo ackmode=ack_requeue_false
+-------------+----------+---------------+---------+
| routing_key | exchange | message_count | payload |
+-------------+----------+---------------+---------+
| udt-demo | | 0 | hello |
+-------------+----------+---------------+---------+
The message is already settled. In a real consumer, a subsequent manual basic.ack on that delivery raises PRECONDITION_FAILED - unknown delivery tag.
Diagnostic Commands
# Per-channel unacked counts and consumer settings - spot channels acking wrongly
rabbitmqctl list_channels pid connection number consumer_count messages_unacknowledged prefetch_count
# Consumers and whether they run in auto-ack mode (ack_required=false means no manual ack)
rabbitmqctl list_consumers queue_name channel_pid consumer_tag ack_required prefetch_count
# Map channels to clients
rabbitmqctl list_connections name peer_host user channels
# Find the error and the offending peer in the broker log
journalctl -u rabbitmq-server --since "15 min ago" | grep -i "unknown delivery tag"
# Confirm queue state and unacked totals
rabbitmqctl list_queues name messages messages_unacknowledged consumers
ack_required=false in list_consumers is the smoking gun for an auto-ack consumer that should not be acking manually.
Step-by-Step Resolution
Step 1: Determine the consumer’s ack mode
rabbitmqctl list_consumers queue_name consumer_tag ack_required | grep <QUEUE>
If ack_required is false, the consumer is in auto-ack mode. Remove all manual basic.ack calls, or switch the consumer to manual ack (auto_ack=false) so your acks are valid.
Step 2: Ensure you ack on the delivering channel
Confirm the channel object you call ack on is the exact one that delivered the message. With a channel pool, carry the channel reference alongside each delivery and never ack across channels.
Step 3: Eliminate double-acks
Audit the handler so each delivery is acknowledged exactly once. Remove any redundant batch-ack or cleanup path that re-acks already-settled deliveries. If you use multiple=true, ack only the highest tag in the batch, once.
Step 4: Discard tags across reconnects
On channel recovery, drop any in-flight delivery tags captured before the reconnect — they are invalid. Re-consume; the broker redelivers unacked messages with fresh tags.
Step 5: Keep prefetch and processing aligned
Set a sensible prefetch_count so deliveries are not piling up and being acked out of order. Process and ack in the order delivered, or track tags individually.
Step 6: Verify the channel stays open
rabbitmqctl list_channels number messages_unacknowledged
A steady, declining unacked count with no channel churn confirms acks are now valid.
Prevention and Best Practices
- Pick one ack model per consumer: either auto-ack (and never call ack) or manual ack (and ack every delivery once).
- Always acknowledge on the same channel that delivered the message; never share delivery tags across channels.
- After a reconnect, treat all prior delivery tags as stale and rely on broker redelivery.
- Use manual acks with a bounded
prefetch_countfor at-least-once processing of important work. - Ack exactly once per delivery; guard handlers against double-ack on retry paths.
- Prefer
multiple=falseacks unless you deliberately batch, and then ack only the top tag.
Related Errors
PRECONDITION_FAILED - unknown delivery tagonbasic.nack/basic.reject— same root cause as ack, applied to rejection.CHANNEL_ERROR/ChannelClosedException— the channel closed by this error; reusing it then raises a channel error.RESOURCE_LOCKED— exclusive-queue contention, often during the same reconnect that invalidates delivery tags.PRECONDITION_FAILED - inequivalent arg— a different precondition failure on declare, unrelated to acks.- Redelivery storms — unacked messages requeued after a channel close from this error, causing duplicate processing.
Frequently Asked Questions
Are delivery tags global or per-channel? Per-channel. The same integer can refer to different messages on different channels, which is why acking on the wrong channel fails.
What happens to the message after this error? Nothing is lost. The channel closes, and any unacknowledged messages on it are requeued for redelivery to another consumer.
Can I ack messages from a different thread? Only if that thread uses the same channel that delivered them, and you serialize access to the channel. Cross-channel acks always fail.
Why does it only happen after a reconnect? Channel close resets the delivery-tag counter. Tags captured before the reconnect no longer map to anything on the new channel.
Is auto-ack safe? Auto-ack maximizes throughput but offers no delivery guarantee — a crash mid-processing loses the message. Use manual ack for important work and never ack manually in auto-ack mode. More fixes in the RabbitMQ guides.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.