Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for RabbitMQ By James Joyner IV · · 9 min read

RabbitMQ Error Guide: 'NOT_FOUND - no exchange' basic.publish Failures

Fix RabbitMQ publish failures: 404 NOT_FOUND on basic.publish, returned messages, channel closed on publish, and missing publisher confirms explained.

  • #rabbitmq
  • #troubleshooting
  • #errors
  • #publishing

Exact Error Message

Publishing to a nonexistent exchange closes the channel with a 404:

Channel error on connection <0.812.0> (10.0.5.31:51544 -> 10.0.4.21:5672, vhost: '/',
 user: 'app'), channel 1:
operation basic.publish caused a channel exception not_found:
no exchange 'orders.events' in vhost '/'

Client side (Java) this surfaces as:

com.rabbitmq.client.ShutdownSignalException: channel error; protocol method:
#method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange
'orders.events' in vhost '/', class-id=60, method-id=40)

A returned (unroutable, mandatory) message instead arrives via basic.return:

basic.return: reply-code=312 reply-text=NO_ROUTE exchange=orders.events
routing_key=order.created

What the Error Means

basic.publish can fail in several distinct ways, and they are easy to confuse:

  • 404 NOT_FOUND: you published to an exchange that does not exist. AMQP publishing is asynchronous, so the broker reports this by closing the channel with a 404 rather than returning an error to the publish call itself.
  • Returned message (312 NO_ROUTE): the exchange exists, but with the mandatory flag set and no queue bound for the routing key, the broker hands the message back via basic.return.
  • Channel closed mid-publish: an unrelated channel exception (e.g., a precondition failure on the same channel) closes the channel, and subsequent publishes on it silently fail.
  • Confirms never arriving: in publisher-confirm mode, basic.ack/basic.nack from the broker do not arrive because the channel closed or confirms were never enabled correctly.

The key insight: a successful basic.publish call returning locally does not mean the broker accepted the message. Without mandatory + basic.return handling or publisher confirms, lost messages are invisible.

Common Causes

  • Exchange never declared, or declared on a different vhost. The publisher assumes an exchange that a deploy never created, or connects to vhost / while the exchange lives on app.
  • Typo or environment drift in the exchange name. orders.event vs orders.events, or a name that differs between staging and prod.
  • mandatory flag with no matching binding. Correct exchange, but no queue is bound for the routing key, so the message is returned.
  • Publishing on a channel already closed by a prior exception. A failed queue.declare with mismatched arguments closes the channel; later publishes on the same channel go nowhere.
  • Auto-delete exchange removed after its last binding dropped. An auto-delete exchange vanished when its bindings were removed.
  • Confirms enabled per-channel but the channel was recreated. A reconnect created a fresh channel without re-calling confirm.select, so acks never come.

How to Reproduce the Error

Publish to an exchange that was never declared:

channel.basic_publish(exchange='orders.events', routing_key='order.created', body=...)
# broker closes channel 1 with:
#   404 NOT_FOUND - no exchange 'orders.events' in vhost '/'

For a returned message, declare an exchange but bind no queue, then publish with mandatory=true:

exchange.declare(name='orders.events', type='topic')
basic.publish(exchange='orders.events', routing_key='order.created',
              mandatory=true, body=...)
# -> basic.return reply-code=312 NO_ROUTE

Diagnostic Commands

# Does the exchange exist, and on which vhost?
rabbitmqctl list_exchanges name type durable --vhost / | grep -i orders

# List exchanges across all vhosts to catch vhost drift
rabbitmqctl list_exchanges --vhost app name type

# What is bound to the exchange? (empty = mandatory publishes will be returned)
rabbitmqctl list_bindings source_name routing_key destination_name | grep -i orders.events

# Channel error history and closed-channel counts
rabbitmqctl list_channels pid number state messages_unconfirmed | head -20

# Watch the broker log for basic.publish channel exceptions
journalctl -u rabbitmq-server --since "10 min ago" | grep -i 'basic.publish\|not_found\|no_route'

# Confirm the connecting user can access the target vhost
rabbitmqctl list_permissions --vhost /

messages_unconfirmed climbing without bound on a channel is the signature of confirms that are enabled but never acked. An empty binding list for the exchange is the signature of returned (NO_ROUTE) messages.

Step-by-Step Resolution

  1. Read the reply code. 404 NOT_FOUND means the exchange is missing; 312 NO_ROUTE means it exists but nothing is bound. They have different fixes.

  2. For 404, verify the exchange and vhost. Run rabbitmqctl list_exchanges --vhost <v>. Confirm the name matches exactly and that the publisher connects to the same vhost. Declare the exchange as part of deployment (idempotent exchange.declare) so it always exists before publishers start.

  3. For 312 NO_ROUTE, check bindings. Run rabbitmqctl list_bindings. Add the missing queue binding for the routing key, or remove the mandatory flag if returns are not desired. Always register a basic.return handler so returns are not silently dropped.

  4. For channel-closed-on-publish, isolate the original exception. A publish failure is often collateral damage from an earlier channel exception (e.g., 406 PRECONDITION_FAILED on a redeclare). Find the first channel exception in the log and fix that; recreate the channel afterward.

  5. For missing confirms, re-enable per channel. confirm.select is per-channel and must be called again after any reconnect. Verify messages_unconfirmed is being drained by acks.

  6. Add publisher confirms for durability. Enable confirms and treat unacked publishes as failures to retry. This is the only reliable way to know the broker accepted a message.

  7. Verify. Re-publish and confirm the channel stays open and basic.ack arrives (or basic.return fires for unroutable messages you expect).

Prevention and Best Practices

  • Declare exchanges and bindings idempotently at startup so topology never depends on manual setup.
  • Always enable publisher confirms for messages that matter; a returned publish call is not proof of delivery.
  • Set mandatory=true and handle basic.return so unroutable messages are logged or retried, not lost.
  • Pin exchange and vhost names in config and validate them across environments to catch drift.
  • Use a dedicated channel per concern so one channel exception does not silently break unrelated publishes.
  • Alert on rising messages_unconfirmed and on any basic.return rate above zero.
  • no route / unroutable message: the dedicated NO_ROUTE case when mandatory is set and nothing is bound.
  • publisher nack received: the broker accepted then rejected via basic.nack under confirms — a different failure than 404.
  • resource alarm: under an alarm, publishes block rather than fail with a channel error.
  • access_refused (403): the user lacks write permission on the exchange or vhost, a different reply code than 404.

Frequently Asked Questions

Why does my publish call succeed but the message disappears? AMQP publishing is fire-and-forget by default. Without publisher confirms and mandatory + basic.return, a 404 or NO_ROUTE only closes the channel or returns the message asynchronously — the publish call itself does not raise.

Does declaring the exchange in the publisher fix the 404? Yes, if done idempotently before publishing. Call exchange.declare with passive=false at startup so the exchange always exists.

What is the difference between a returned message and a nack? A return (basic.return, 312) means the broker could not route the message to any queue. A nack (basic.nack) means the broker accepted routing but could not persist or enqueue it.

Why do confirms stop arriving after a reconnect? confirm.select is per-channel. A reconnect creates a new channel that is not in confirm mode until you call confirm.select again.

Can a single bad publish break other publishes? Yes — if they share a channel. The 404 closes the entire channel, so every subsequent publish on it fails until the channel is recreated.

Free download · 368-page PDF

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.