RabbitMQ Publisher Confirms: Knowing When the Broker Actually Has Your Message
basicPublish returning doesn't mean RabbitMQ stored your message. Publisher confirms give you a per-message ACK after disk write — here's how to enable, track, and verify them without killing throughput.
30 Mar 2026, 12:36 UTC

The problem: "sent" does not mean "stored"
When your application calls basicPublish, the client writes bytes to a TCP socket and returns. If the broker crashes a millisecond later — before persisting the message — that message is gone, and your code has no idea. Transactions (txSelect) fix this but serialize every publish and can cut throughput by an order of magnitude. Publisher confirms are the supported middle ground: the broker asynchronously acknowledges each message after it has been handled (for durable queues, after it is written to disk), so your client learns definitively whether the publish succeeded.
The useful takeaway: enable confirms per channel with confirmSelect(), track outstanding messages yourself, and treat a NACK or a confirm timeout as a signal to republish. Confirms are cheap compared to transactions — typically a few percent overhead rather than a 10x slowdown — but they only work if you actually wait for them before declaring victory.
How the mechanism works
Confirms are a channel-level extension to the AMQP protocol. Once you call confirmSelect() on a channel, the broker assigns a monotonically increasing delivery tag to every message published on that channel. For each message it later sends an basic.ack (message accepted and, for persistent messages on durable queues, written to the log) or a basic.nack (the broker could not take responsibility — for example, an internal error during persistence). Acks can arrive batched with the multiple flag set, meaning "everything up to this tag is confirmed."
Two things follow from this design. First, confirms are asynchronous: the ack arrives on the connection's network thread some milliseconds after publish, so you need a data structure mapping delivery tags to the messages you sent. Second, confirms live and die with the channel. If the channel or connection breaks before an ack arrives, you must assume those messages may not have been stored and republish them — which is why consumers should be idempotent (a republished message can produce a duplicate).
A worked example in Java
The official Java client supports confirms natively. This example publishes a batch and waits for all confirms, with a timeout. Run it in any application that already connects to RabbitMQ; no special broker configuration is needed, and the broker does not need to be restarted. Version assumption: Java client 5.x, RabbitMQ 3.8+.
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("rabbit.internal");
try (Connection conn = factory.newConnection();
Channel channel = conn.createChannel()) {
channel.confirmSelect(); // MUST be called before publishing
int outstanding = 1000;
ConcurrentNavigableMap<Long, String> pending = new ConcurrentSkipListMap<>();
channel.addConfirmListener((deliveryTag, multiple) -> {
if (multiple) {
pending.headMap(deliveryTag, true).clear();
} else {
pending.remove(deliveryTag);
}
}, (deliveryTag, multiple) -> {
// NACK: broker refused responsibility — log and republish
System.err.println("NACKed tag " + deliveryTag);
});
for (int i = 0; i < outstanding; i++) {
long tag = channel.getNextPublishSeqNo();
String body = "order-" + i;
pending.put(tag, body);
channel.basicPublish("orders", "order.created",
MessageProperties.PERSISTENT_TEXT_PLAIN,
body.getBytes(StandardCharsets.UTF_8));
}
if (!channel.waitForConfirmsOrDie(5_000)) {
throw new IllegalStateException("Unconfirmed messages remain");
}
}
Key details:
getNextPublishSeqNo()returns the tag the broker will assign to the next publish on this channel, letting you register the message inpendingbefore sending.- The ack listener must handle the
multipleflag, or you will leak entries in your pending map. waitForConfirmsOrDiethrows on the first NACK; in production you usually want the listener-based approach so you can republish individual failed messages instead of failing the whole batch.- Channels are not thread-safe. If multiple threads publish, give each its own channel or serialize access — otherwise delivery tags and your pending map will disagree.
What a confirm actually guarantees — and what it does not
A confirm means the broker has accepted responsibility for the message: for a persistent message routed to a durable queue, that means it has been written to the queue's log on disk. It does not mean a consumer has processed it, and it does not mean the message was routed anywhere at all. If you publish to an exchange with no matching binding, the message is dropped and you still get an ack — the broker successfully "handled" it by discarding it. To catch unroutable messages, combine confirms with the mandatory flag and a return listener, or configure an alternate exchange.
On mirrored or quorum queues, the ack semantics differ slightly: quorum queues confirm once a majority of replicas have written the entry, which is the behavior you want for real durability. Classic mirrored queues are deprecated in recent RabbitMQ versions; prefer quorum queues for anything where the confirm is your durability evidence.
Common mistakes
- Forgetting
confirmSelect(). Nothing fails loudly; you simply never receive acks, andwaitForConfirmsblocks forever or times out. If you use a connection/channel pool, make sure the confirm mode is enabled when the channel is created, not assumed. - Blocking per message. Calling
waitForConfirmsOrDieafter every single publish reintroduces transaction-like latency. Publish a batch, then wait once, or go fully asynchronous with listeners. - Ignoring the confirm listener thread. Confirm callbacks run on the connection's I/O thread pool. Doing heavy work (like a synchronous database write for the republish) inside the listener can stall all channels on the connection. Hand NACKs off to a separate executor.
- Assuming confirms survive reconnection. They do not. On connection recovery, anything in your pending map is unconfirmed; republish it and rely on consumer idempotency (e.g., a deduplication key in the message header) to absorb duplicates.
Verifying it works
Three practical checks:
- Log every NACK and every confirm timeout in your listener. In a healthy system both should be zero or near-zero; any sustained NACK rate is a broker-side problem worth investigating.
- In the management UI (Management plugin, port 15672), open the channel and check the "confirm" publish rate — it should track your publish rate. A gap between "publish" and "publisher confirm" rates means acks are lagging.
- Deliberately kill the broker mid-publish in a staging environment. Your pending map should retain the unconfirmed messages, and your republish path should fire. If messages silently vanish, your tracking or timeout logic has a hole.
Publisher confirms are the default answer for "did the broker get it?" in RabbitMQ: far cheaper than transactions, explicit about failure, and the only option that lets you build a reliable republish loop. The cost is the bookkeeping — a pending map, a timeout, and idempotent consumers — which is unavoidable in any at-least-once design.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.