RabbitMQ PRECONDITION_FAILED - inequivalent arg: redeclaring a queue with different properties
Your application logs are filling with channel exceptions and consumers or publishers keep reconnecting:
PRECONDITION_FAILED - inequivalent arg 'x-queue-type' for queue 'email.queue'
in vhost 'Email': received none but current is the value 'quorum' of type 'longstr'
This is AMQP error 406, a channel-level exception. A client declared a queue (or exchange) that already exists, but one or more of the declared properties do not match how the entity was originally created. RabbitMQ refuses the declaration and closes the channel. The connection usually survives, but most client libraries treat a channel exception as fatal and tear down the whole connection, so a single mismatched declaration often looks like a reconnect storm.
The classic triggers: you migrated queues from classic to quorum and not every client sets x-queue-type, or someone changed a TTL, max-length, or durability setting in application code without deleting the old queue first. The queue’s original definition is stored in the broker’s metadata and it does not change when you edit the code.
What this means
AMQP 0-9-1 treats queue.declare as idempotent only when it is identical. When a client declares a queue that already exists, the broker compares the incoming declaration against the stored definition. The compared properties are durable, exclusive, auto-delete, and the full arguments map (x-message-ttl, x-max-length, x-queue-type, x-dead-letter-exchange, and so on). If anything differs, the broker raises 406 PRECONDITION_FAILED on that channel and closes it. The same equivalence check applies to exchanges, where the compared properties include the exchange type and durability.
The error message names the exact argument that mismatched, the value the client sent (“received”), and the value the broker has stored (“current”). Read it literally. received none but current is the value 'quorum' means the client did not send the argument at all and the stored queue has it set.
Two details matter for diagnosis:
- Only client-provided arguments are compared. If a policy set the argument on the existing queue, a client that omits that argument will not trigger the exception. This is why policy-managed arguments (TTL, max-length, DLX) are the recommended pattern: they decouple the queue’s runtime behavior from what each client declares.
- The channel is dead, the definition is not. Retrying the same declaration on a new channel fails the same way, forever. No amount of reconnecting fixes a property mismatch. Applications in this state end up in a tight declare-fail-reconnect loop, which shows up as elevated channel and connection churn on the broker.
flowchart TD
A[Client calls queue.declare] --> B{Queue exists?}
B -- no --> C[Queue created with declared properties]
B -- yes --> D{durable, exclusive, auto-delete and args match?}
D -- yes --> E[Declare succeeds, idempotent no-op]
D -- no --> F[406 PRECONDITION_FAILED, channel closed]
F --> G[Client library often closes connection and reconnects]
G --> ACommon causes
| Cause | What it looks like | First thing to check |
|---|---|---|
| Classic to quorum migration, incomplete client rollout | inequivalent arg 'x-queue-type', received none or classic, current quorum | Which clients still declare without x-queue-type |
| TTL or max-length changed in code, old queue still exists | inequivalent arg 'x-message-ttl' or 'x-max-length', values differ | The current queue arguments vs the new code |
| Durability flipped in code | inequivalent arg 'durable', received true but current is false (or reverse) | durable column in rabbitmqctl list_queues |
| Mixed client versions declaring the same queue | Error appears intermittently, correlates with which instance connected | Whether different service versions declare different arguments |
| Policy-applied argument vs hardcoded argument | One client declares an argument the queue got from an operator policy | rabbitmqctl list_policies vs the client declaration |
| Exchange redeclared with a different type | Same 406, but naming an exchange; inequivalent arg 'type' | rabbitmqctl list_exchanges type column |
| Quorum-incompatible features carried over | x-max-priority, x-queue-mode, or x-overflow: reject-publish-dlx mismatch during quorum migration | Whether the old queue used priority, lazy mode, or reject-publish-dlx overflow |
Quick checks
All read-only.
# Find the error and the exact mismatched argument in the broker log
grep -i "PRECONDITION_FAILED" /var/log/rabbitmq/rabbit@$(hostname).log | tail -20
# Inspect the stored definition of the queue named in the error
rabbitmqctl list_queues name durable auto_delete arguments | grep -F "email.queue"
# Check whether a policy is applying arguments to this queue
rabbitmqctl list_policies
# If the error names an exchange, check its stored type and durability
rabbitmqctl list_exchanges name type durable arguments
# See whether failing clients are causing channel churn on the broker
# (default guest/guest credentials only authenticate from localhost)
curl -s -u guest:guest http://localhost:15672/api/overview | jq '.churn_rates | {channel_created, channel_closed, connection_created}'
If arguments in the list_queues output is empty but the error says current is the value 'quorum', check you are looking at the right vhost: rabbitmqctl list_queues -p <vhost> ....
How to diagnose it
- Extract the three facts from the error message. The argument name, the received value, and the current value. In
received none but current is the value 'quorum', the fix direction is either “make the client sendx-queue-type: quorum” or “recreate the queue without it.” The message tells you which side disagrees. - Identify the declaring client. The channel exception is logged server-side. Cross-reference the timestamp with the connection that hit it using the management API (
GET /api/connections, look atuser,peer_host, andclient_properties). In most incidents it is one service, one version, or one freshly deployed instance. - Compare against the stored definition. Run the
list_queues ... argumentscheck above and diff it against the client’s declare call. Remember the policy exception: if a policy applies the argument, clients omitting it are fine, and the mismatch is coming from a client that sends a conflicting value. - Decide which definition is authoritative. Usually the running queue with live messages is authoritative and the code is wrong. Sometimes the queue is a leftover from an old configuration and the new code is intended. This decision determines the fix path below.
- Check the blast radius. Look at channel and connection churn rates. A single misdeclaring client in a retry loop can generate thousands of connection attempts per hour and show up as an unrelated-looking “connection storm.”
Metrics and signals to monitor
There is no broker metric for PRECONDITION_FAILED itself. Channel and connection protocol errors are visible only in the RabbitMQ log, so log-based detection is the primary signal. The secondary signals are the churn the failure causes.
| Signal | Why it matters | Warning sign |
|---|---|---|
Broker log: PRECONDITION_FAILED lines | The only direct record of the exception, including the argument and values | Any occurrence outside a planned migration |
Channel churn rate (churn_rates.channel_created / channel_closed) | Each failed declare kills a channel; retry loops inflate churn | Creation rate sustained well above the rolling baseline |
| Connection churn rate | Many client libraries drop the whole connection on a channel exception | Connection creation spikes correlated with deploys |
Queue churn (queue_declared) | Repeated failing declares may still count as declarations | Declared rate climbing with zero new queues created |
| Consumer count on the affected queue | A consumer stuck in declare-fail-reconnect is not consuming | Consumers below expected count while queue depth grows |
Fixes
Make the client match the existing queue
The zero-data-loss fix. Update the declaring code so every property matches the stored definition: same durability, same arguments. Redeploy. This is correct when the queue holds messages you need and its current definition is the intended one.
Tradeoff: during rolling deployments you will run mixed client versions. If only some instances are updated, the old ones still throw 406 until they are replaced. That is transient and harmless as long as updated instances carry the traffic.
Recreate the queue with the new properties
The only way to change a queue’s immutable properties is to delete it and let clients redeclare it. This is destructive: deleting a queue discards every message in it. Drain it first (let consumers catch up, or shovel messages elsewhere), stop publishers if possible, then:
# DESTRUCTIVE: deletes the queue and all messages in it
rabbitmqadmin delete queue name=email.queue --vhost=Email
Then redeploy the clients with the new declaration. During the gap between deletion and the first successful declare, publishers may get unroutable messages; if publishers use mandatory=true they will see returns instead of silent loss.
Move arguments into policies, out of client code
For arguments RabbitMQ supports via policy (TTL, max-length, dead-lettering, queue type), declare them operator-side and have clients declare the bare queue. Policies can be changed without touching the queue or the clients, and because policy-applied arguments are exempt from the equivalence check, clients that omit them never trigger 406. This is the durable fix for “every service declares the same queue slightly differently.”
Tradeoff: a policy change applies to all matching queues, and applying a policy to many queues at once causes a CPU burst as the broker reconfigures them. Apply during low traffic on large deployments.
Classic to quorum migration specifics
This is the most common modern source of the error. Useful escape hatches, all version-dependent:
quorum_queue.property_equivalence.relaxed_checks_on_redeclaration = trueinrabbitmq.conf(available since 3.11.16) makes the broker ignorex-queue-typeduring equivalence checks. This lets un-updated clients redeclare a queue that was converted to quorum without hitting 406. It is a migration bridge, not a permanent posture.default_queue_typeon the vhost (since 3.13.3) makes queues declared without an explicit type default to quorum, so old clients create new queues of the right type automatically.- Incompatible arguments must be cleaned up in code. Quorum queues do not support
x-max-priority, exclusive queues, orx-overflow: reject-publish-dlx, andx-queue-mode: lazyis meaningless for them. A redeclaration carrying these against a quorum queue fails. Remove them from client declarations before or during migration. x-consumer-timeoutis not part of the equivalence check, so changing it does not cause this error. Do not chase it.- One behavioral surprise after migration: redeclaring a quorum queue does not renew its
x-expireslease the way classic queues did. A quorum queue with queue TTL can be deleted even if clients periodically redeclare it.
Do not rapidly flip a queue’s type back and forth; there is a known race where rapid redeclaration with changing x-queue-type causes internal errors for live publishers. Migrate deliberately, once.
Prevention
- Declare queues identically everywhere, or nowhere. Either centralize queue definitions in one component (provisioning tooling, Terraform, a topology operator), or standardize the declare call in a shared library. The failure mode is always two sources of truth.
- Prefer policies over client arguments for anything you might want to change later. Policies are the only way to change TTL, max-length, or DLX behavior without recreating the queue.
- Gate migrations on the declare path. When switching queue types, inventory every client that declares the affected queues before converting anything. The 406 will find the ones you missed, in production, at deploy time.
- Alert on the log pattern.
PRECONDITION_FAILEDin the broker log should page during a migration window and ticket at any other time. It never self-resolves. - Watch channel churn after deploys. A post-deploy spike in channel churn with no obvious cause is often a declaration mismatch in the new build.
How Netdata helps
- Netdata collects RabbitMQ object and churn rates (connections, channels, queues created and declared) from the management API, so a declare-fail-reconnect loop shows up as a channel churn anomaly you can correlate with deploy timing.
- Per-queue metrics (messages ready, unacknowledged, consumers) show the downstream effect: a queue whose consumers are stuck in the 406 retry loop starts accumulating messages while its consumer count reads zero or flaps.
- Connection count and churn trends let you distinguish a single misdeclaring client (churn spike, stable total) from a genuine connection storm.
- Because Netdata keeps per-second history, you can line up the exact minute a new client version rolled out with the first churn spike and confirm the causal deploy before rolling back.
- The 406 line itself still requires log collection; pairing log alerts on
PRECONDITION_FAILEDwith Netdata’s churn and queue-depth charts gives you both the cause and the effect on one timeline.
Related guides
- RabbitMQ connection blocked / blocking: publishers frozen by a resource alarm
- RabbitMQ connection in flow state: credit-based backpressure explained
- RabbitMQ consumer timeout: delivery acknowledgement timed out and the channel is closed
- RabbitMQ consumer_utilisation low: consumers attached but not keeping up
- RabbitMQ consumers connected but not acknowledging: the consumer black hole
- RabbitMQ disk free limit alarm: free disk space insufficient and publishing halted
- RabbitMQ disk_free_limit at the default 50MB: the production footgun
- RabbitMQ flow control vs resource alarms: the two throttling mechanisms operators confuse
- RabbitMQ head message age: the queue latency that depth alone cannot show
- How RabbitMQ actually works in production: a mental model for operators
- RabbitMQ memory resource limit alarm: publishers blocked across the whole cluster
- RabbitMQ monitoring checklist: the signals every production broker needs






