BlogEngineeringPublisher Confirms in RabbitMQ: The Complete Guide to Guaranteed Message Delivery

Publisher Confirms in RabbitMQ: The Complete Guide to Guaranteed Message Delivery

DanubeData Engineering
DanubeData Engineering
March 29, 2026
10 min read
1 views
#rabbitmq #publisher-confirms #guaranteed-delivery #amqp #message-queue #performance

When you call basic_publish in RabbitMQ, the message is written to a TCP buffer and sent to the broker. But "sent" does not mean "received." If the connection drops mid-transfer, the broker runs out of memory, or the queue does not exist, your message disappears silently.

Publisher confirms are the mechanism that turns "sent" into "guaranteed delivered." When enabled, the broker sends an acknowledgment back to the producer for every message (or batch of messages) it successfully persisted. Without confirms, you are running fire-and-forget — and hoping for the best.

How Publisher Confirms Work

The flow is simple:

  1. The producer enables confirm mode on the channel
  2. The producer publishes a message
  3. The broker writes the message to disk (for persistent messages) or memory
  4. The broker sends back a basic.ack to confirm receipt
  5. If the broker cannot accept the message, it sends a basic.nack

The key insight is that confirms are asynchronous — the broker does not confirm each message individually in real-time. Instead, it batches confirmations, which is why the --confirm parameter in benchmarks controls how many messages you wait to confirm at once.

The Performance vs. Safety Tradeoff

We benchmarked different confirm batch sizes on our managed RabbitMQ service using the official rabbitmq-perf-test tool. The results reveal a clear tradeoff:

Confirm Batch SizeThroughputConfirm LatencyThroughput vs. No Confirms
No confirms123,296 msg/s100%
10,000111,554 msg/s73 ms90%
5,000108,260 msg/s42 ms88%
3,00068,271 msg/s40 ms55%
1,00018,564 msg/s52 ms15%
2006,181 msg/s31 ms5%

The sweet spot is a batch size of 5,000: you retain 88% of fire-and-forget throughput while getting a broker acknowledgment for every message. Below 3,000, throughput drops sharply because the producer spends most of its time waiting for the confirm round-trip.

Why Small Batches Are Slow

Each waitForConfirms call incurs a network round-trip. If your broker is 15ms away, a batch size of 200 means you can only send about 200 / 0.015 = 13,333 confirms per second — and each confirm covers 200 messages, giving you a theoretical max of ~13K msg/s. Our measured 6,181 msg/s includes serialization overhead and TCP buffering delays.

At batch size 5,000, the same math gives 5000 / 0.015 = 333,333 theoretical max, well above what a single queue can handle. The confirm overhead becomes negligible.

Code Examples

Python (pika)

import pika

connection = pika.BlockingConnection(
    pika.ConnectionParameters(
        host='amqp-myqueue-tenant.queues.danubedata.ro',
        port=5672,
        credentials=pika.PlainCredentials('admin', 'YOUR_PASSWORD')
    )
)
channel = connection.channel()
channel.queue_declare(queue='orders', durable=True)

# Enable publisher confirms
channel.confirm_delivery()

# Every publish now blocks until the broker confirms
try:
    channel.basic_publish(
        exchange='',
        routing_key='orders',
        body=b'{"order_id": 42}',
        properties=pika.BasicProperties(delivery_mode=2),  # persistent
        mandatory=True,
    )
    print("Message confirmed by broker")
except pika.exceptions.UnroutableError:
    print("Message could not be routed to a queue")
except pika.exceptions.NackError:
    print("Broker rejected the message")

Node.js (amqplib) — Batched Confirms

import amqplib from 'amqplib';

const conn = await amqplib.connect(
    'amqp://admin:YOUR_PASSWORD@amqp-myqueue-tenant.queues.danubedata.ro'
);
const ch = await conn.createConfirmChannel();
await ch.assertQueue('orders', { durable: true });

const BATCH_SIZE = 5000;
let pending = 0;

for (const order of orders) {
    const ok = ch.publish('', 'orders',
        Buffer.from(JSON.stringify(order)),
        { persistent: true }
    );

    if (!ok) {
        // Channel buffer full — wait for drain
        await new Promise(resolve => ch.once('drain', resolve));
    }

    pending++;

    // Flush confirms every BATCH_SIZE messages
    if (pending >= BATCH_SIZE) {
        await ch.waitForConfirms(); // throws if any were nacked
        pending = 0;
    }
}

// Flush remaining
if (pending > 0) {
    await ch.waitForConfirms();
}

await conn.close();

PHP (php-amqplib)

<?php
use PhpAmqpLibConnectionAMQPStreamConnection;
use PhpAmqpLibMessageAMQPMessage;

$connection = new AMQPStreamConnection(
    'amqp-myqueue-tenant.queues.danubedata.ro',
    5672, 'admin', 'YOUR_PASSWORD'
);
$channel = $connection->channel();
$channel->queue_declare('orders', false, true, false, false);

// Enable publisher confirms
$channel->confirm_select();

// Publish with confirmation
$msg = new AMQPMessage(
    json_encode(['order_id' => 42]),
    ['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]
);
$channel->basic_publish($msg, '', 'orders');

// Wait for broker confirmation
$channel->wait_for_pending_acks_returns(5.0); // 5 second timeout

$channel->close();
$connection->close();

When to Use Publisher Confirms

Always Use Confirms For:

  • Payment processing: A lost payment event means a customer gets charged without receiving their product
  • Order pipelines: A dropped order message means a sale that never gets fulfilled
  • Transactional email: Password resets, invoices, and account verifications cannot be silently lost
  • Audit logs: Compliance requires proof that every event was recorded
  • Data synchronization: Missing sync events cause data drift between systems

Confirms Are Optional For:

  • Metrics and telemetry: A few dropped data points do not affect dashboards
  • Real-time notifications: Missing one push notification is recoverable
  • Cache invalidation: A missed invalidation is self-correcting on the next TTL expiry

Common Mistakes

1. Confirming Every Single Message

The worst pattern is calling waitForConfirms after every publish. This turns your producer into a synchronous, one-at-a-time pipeline bottlenecked by network latency. Use batched confirms with a batch size of 3,000-5,000 for the best throughput.

2. Ignoring Nacks

A basic.nack means the broker explicitly rejected your message — usually because the queue does not exist, a policy limit was exceeded, or internal storage failed. Always handle nacks: log them, retry with backoff, or route to a dead-letter queue.

3. Confusing Confirms with Consumer Acks

Publisher confirms guarantee the broker received the message. Consumer acknowledgments guarantee the consumer processed the message. You need both for end-to-end delivery guarantees.

4. Using Transactions Instead

AMQP transactions (tx.select, tx.commit) provide similar guarantees but are 10-100x slower than publisher confirms. They were the original mechanism before confirms were introduced. Always prefer confirms.

Conclusion

Publisher confirms cost you only 12% throughput (108K vs 123K msg/s at batch size 5,000) while guaranteeing every message reaches the broker. For any workload where losing a message has business impact, that is the best trade you will ever make.

All benchmarks were run on DanubeData's managed RabbitMQ using the official rabbitmq-perf-test tool. Deploy your own instance and verify these numbers yourself.

Share this article

Ready to Get Started?

Deploy your infrastructure in minutes with DanubeData's managed services.