RabbitMQ Publisher Confirms: Stop Assuming Messages Arrived
Fire-and-forget publishing hides broker rejections. Publisher confirms let a producer wait for basic.ack or basic.nack per message, reducing silent loss at the cost of tracking outstanding publishes.
06 Sept 2026, 13:32 UTC

Fire-and-forget publishing feels fast until a network blip or a typo in an exchange name silently drops messages. Publisher confirms provide a broker-side acknowledgment that a message was accepted and routed to at least one queue, removing the guesswork from the publishing stage of your pipeline.
What a confirm actually guarantees
Publisher confirms operate on a per-channel basis. Once a producer sends a confirm.select command, the broker begins responding with basic.ack (acknowledgment) for messages it has successfully persisted and routed, or basic.nack (negative acknowledgment) for messages it rejected.
It is critical to understand that a confirm is not an end-to-end delivery guarantee. A confirm only means the broker has taken responsibility for the message. It does not mean a consumer has processed it. If the broker crashes before the message is mirrored or written to disk (depending on your durability settings), or if the consumer fails during processing, the confirm does not protect against that loss.
Tracking outstanding publishes
Because RabbitMQ handles confirms asynchronously, the client must track which messages are still "in flight." The broker uses a delivery tag—a monotonically increasing integer assigned by the channel—to identify which publish is being acknowledged.
Producers typically maintain a local map of these delivery tags. When a basic.ack arrives, the corresponding tag is removed from the map. If a basic.nack arrives or a timeout occurs, the producer can trigger a retry logic or log a critical failure. This bookkeeping introduces a memory overhead; if a producer sends thousands of messages per second without pruning the outstanding map, client-side memory consumption will climb.
Implementation pattern in Python
The following example demonstrates the logic for enabling confirms and handling the broker's response using the pika library. Run this on a producer host with write permissions to the target exchange.
import pika
# Establish connection and channel
connection = pika.BlockingConnection(pika.ConnectionParameters(host='rabbitmq-host'))
channel = connection.channel()
# Enable publisher confirms on this channel
channel.confirm_delivery()
# Local state to track messages awaiting acknowledgment
outstanding_confirms = {}
def on_ack(delivery_tag, multiple):
# Remove the confirmed tag from our local tracking map
if delivery_tag in outstanding_confirms:
del outstanding_confirms[delivery_tag]
# If 'multiple' is True, all tags up to this one are acknowledged
def on_nack(delivery_tag, multiple):
# Log the failure or trigger a resend of the specific message
print(f"Message {delivery_tag} was nacked by broker")
# In a real async client, you would attach these callbacks to the channel
# For BlockingConnection, you often check for success immediately after publish
try:
# delivery_mode=2 makes the message persistent
channel.basic_publish(
exchange='orders.exchange',
routing_key='orders.create',
body=b'Order Data',
properties=pika.BasicProperties(delivery_mode=2)
)
# Expected check: The method returns True if the broker sent a basic.ack
print("Message confirmed by broker")
except pika.exceptions.UnroutableError:
# Risk: If mandatory=True and the message can't be routed, it triggers an error
print("Message could not be routed to any queue")
To verify this behavior without production traffic: enable confirms, then attempt to publish a message to an exchange that does not exist. You should observe a basic.nack or a channel exception rather than the code proceeding as if the message were delivered.
Trade-offs and limitations
The primary trade-off is performance. Publisher confirms introduce additional network round-trips and require client-side memory for tracking tags. For high-throughput telemetry where losing 0.1% of data is acceptable, fire-and-forget is more efficient. For financial transactions or order processing, the latency hit is a necessary cost.
Another limitation is the lack of ordering guarantees across multiple channels. If you use multiple channels to publish in parallel, the order in which you receive basic.ack responses may not match the order in which you sent the messages.
Actionable closing
To implement this reliably, enable confirms on your most critical publishing channels first. Implement a capped buffer for outstanding messages to prevent memory leaks and set up alerting for basic.nack rates. This ensures your application knows exactly when the broker has accepted responsibility for your data.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.