Skip to content

Consumer Acknowledgements in RabbitMQ — Complete Guide

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Consumer Acknowledgements in RabbitMQ. We cover key concepts, practical examples, and best practices to help you master this topic.

Consumer acknowledgements let RabbitMQ know a message was processed successfully, enabling at-least-once delivery and preventing message loss.

What You Learn

You will learn how manual and automatic acknowledgement modes work, how to handle failures with nacks and requeueing, and best practices for reliable consumption.

Why It Matters

When a consumer receives a message but crashes before processing it, the message is lost if not acknowledged. Acknowledgements give the broker the information it needs to redeliver messages on failure, ensuring no data is lost.

Real-World Use

Durga Antivirus Pro uses manual acknowledgements for file scanning workers. If a worker crashes mid-scan, the message is requeued and picked up by another worker. This guarantees every file is scanned even if workers fail.

How Consumer Acknowledgements Work

flowchart LR
    B[Broker] -->|deliver| C[Consumer]
    C -->|basic.ack| B
    B -->|remove from queue| Q[Queue]
    C -->|basic.nack| B
    B -->|requeue or discard| Q
    style B fill:#f90,color:#fff

The broker delivers a message to a consumer. The consumer processes it then sends a basic.ack. If the consumer disconnects without acking, the broker requeues the message.

Automatic vs Manual Acknowledgement

import pika

conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
ch = conn.channel()
ch.queue_declare(queue='ack_demo', durable=True)

# Auto-ack: broker removes message as soon as it is delivered
ch.basic_publish(exchange='', routing_key='ack_demo', body='auto_ack')
method_frame, _, body = ch.basic_get(queue='ack_demo', auto_ack=True)
print(f"Auto-ack received: {body.decode()}")
print("Message removed from queue on delivery")

conn.close()

Expected output:

Auto-ack received: auto_ack
Message removed from queue on delivery

Manual Acknowledgement

import pika
import time

conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
ch = conn.channel()
ch.queue_declare(queue='manual_ack', durable=True)

ch.basic_publish(exchange='', routing_key='manual_ack', body='process_me')

def callback(ch, method, properties, body):
    print(f"Processing: {body.decode()}")
    time.sleep(0.5)
    print("Processing complete")
    ch.basic_ack(delivery_tag=method.delivery_tag)
    print("Ack sent to broker")

ch.basic_consume(queue='manual_ack', on_message_callback=callback, auto_ack=False)
print("Waiting for messages...")
ch.start_consuming()

Expected output:

Processing: process_me
Processing complete
Ack sent to broker

Negative Acknowledgement (Nack)

Use basic.nack to reject a message that could not be processed:

import pika

conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
ch = conn.channel()
ch.queue_declare(queue='nack_demo', durable=True)

ch.basic_publish(exchange='', routing_key='nack_demo', body='bad_message')

def callback(ch, method, properties, body):
    print(f"Received: {body.decode()}")
    print("Processing failed, sending nack with requeue=false")
    ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)
    print("Message discarded")

ch.basic_consume(queue='nack_demo', on_message_callback=callback, auto_ack=False)
ch.start_consuming()

Expected output:

Received: bad_message
Processing failed, sending nack with requeue=false
Message discarded

Requeue vs Discard

When nacking, set requeue=True to return the message to the queue, or requeue=False to discard or send to a dead letter exchange:

# Requeue: message goes back to the queue
ch.basic_nack(delivery_tag=tag, requeue=True)

# Discard: message is removed (or dead-lettered if configured)
ch.basic_nack(delivery_tag=tag, requeue=False)

Requeueing without caution creates infinite loops. Use a redelivery counter or dead letter exchange.

Multiple Acknowledgements

Acknowledge multiple messages at once for batch processing:

# Ack a single message
ch.basic_ack(delivery_tag=tag)

# Ack this message and all earlier unacked messages
ch.basic_ack(delivery_tag=tag, multiple=True)

Use multiple acks carefully. They acknowledge all preceding unacked messages, not just the current one.

Common Mistakes

1. Using Auto-Ack in Production

Auto-ack removes messages on delivery, not on processing success. If the consumer crashes mid-processing, the message is lost permanently.

2. Not Acknowledging at All

If you never ack or nack, unacked message count grows until the consumer runs out of memory or hits the channel limit. Messages are held in memory on the broker.

3. Infinite Requeue Loops

Nacking with requeue=true without tracking delivery count creates infinite loops. The message is delivered, rejected, requeued, and delivered again forever.

4. Acknowledging Before Processing

Acking before the work is done defeats the purpose. If the consumer crashes after the ack, the message is considered processed and is removed.

5. Forgetting the Delivery Tag

The delivery tag identifies which message to ack. Always use method.delivery_tag from the callback. Hardcoding tags causes incorrect acknowledgements.

Practice Questions

1. What is the difference between auto-ack and manual ack?

Auto-ack removes the message when delivered. Manual ack removes it only after the consumer explicitly acknowledges processing.

2. What happens if a consumer disconnects without acking?

The broker requeues the message for delivery to another consumer. This provides at-least-once delivery.

3. When should you use basic.nack?

When processing fails and you want to either requeue the message (requeue=true) or discard/dead-letter it (requeue=false).

4. What is the multiple flag in basic.ack?

It acknowledges the specified message and all earlier unacknowledged messages on the channel.

Challenge

Build a consumer that processes messages with retry logic: Process normally, nack with requeue on transient failures (max 3 redeliveries), then nack with requeue=false to dead letter after exhausting retries.

FAQ

Can a consumer ack a message it did not receive?

No. The broker only accepts acks for messages delivered to that consumer on that channel. Acks for unknown tags are ignored.

What is the default acknowledgement mode?

In Pika, auto_ack defaults to True. Always set auto_ack=False for production use.

Does basic.reject differ from basic.nack?

basic.reject rejects a single message. basic.nack supports multiple and requeue parameters. Both signal processing failure.

How many unacked messages can a consumer have?

Limited by the channel's QoS prefetch setting. Without QoS, the limit is the channel's max outstanding messages.

What happens to unacked messages when a queue is deleted?

All messages in the queue, including unacked ones, are discarded. Delete queues only after confirming no unacked messages exist.

Mini Project: Safe Consumer

import pika
import time
import json

class SafeConsumer:
    def __init__(self):
        self.conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
        self.ch = self.conn.channel()
        self.ch.queue_declare(queue='safe_queue', durable=True)
        self.ch.basic_qos(prefetch_count=1)
        self.redeliveries = {}

    def process(self, ch, method, properties, body):
        msg_id = properties.message_id or str(time.time())
        redelivery_count = self.redeliveries.get(msg_id, 0)

        try:
            print(f"Processing: {body.decode()}")
            if redelivery_count > 0:
                print(f"Redelivery attempt {redelivery_count}")
            time.sleep(0.1)
            ch.basic_ack(delivery_tag=method.delivery_tag)
            print("Acknowledged")
            if msg_id in self.redeliveries:
                del self.redeliveries[msg_id]
        except Exception as e:
            print(f"Error: {e}")
            if redelivery_count < 3:
                self.redeliveries[msg_id] = redelivery_count + 1
                ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True)
                print(f"Requeued (attempt {redelivery_count + 1})")
            else:
                ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)
                print("Dead-lettered after 3 attempts")

    def run(self):
        self.ch.basic_consume(queue='safe_queue', on_message_callback=self.process)
        print("Consumer ready")
        self.ch.start_consuming()

consumer = SafeConsumer()
consumer.run()

Expected output:

Consumer ready
Processing: some message
Acknowledged

What's Next

Now that you understand consumer acknowledgements, explore QoS prefetch for controlling message flow to consumers, then learn about RabbitMQ clustering for high availability.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro