# RabbitMQ virtual hosts for enterprise tenant isolation
RabbitMQ virtual hosts for enterprise tenant isolation give each tenant a logically separate namespace for exchanges, queues, bindings, users, and policies inside one broker. They are the right tool for organizational and access-control separation between tenants, but they share the same Erlang VM, memory, disk, and CPU, so they are not a hard security or resource boundary on their own.
What a vhost actually isolates (and what it does not)
A RabbitMQ virtual host, or vhost, is a namespace inside a single broker or cluster. Every exchange, queue, and binding lives inside exactly one vhost, and names only need to be unique within that vhost, not across the whole broker. Two tenants can each have a queue named orders without collision, because tenant-a/orders and tenant-b/orders are different objects entirely.
A vhost also scopes users and permissions, policies (TTL, max-length, dead-letter routing, quorum queue settings, replication behavior), runtime parameters, connection and queue limits, and federation and shovel links.
What a vhost does not isolate is everything below the AMQP object model. All vhosts on a node share the same Erlang VM and scheduler threads, the same physical memory pool governed by the node's single vm_memory_high_watermark, the same disk governed by disk_free_limit, the same OS-level file descriptor and socket limits, and the same CPU cores.
This is the part auditors and architects most often get wrong. If one tenant's application publishes faster than it consumes, queue depth grows, memory pressure rises, and RabbitMQ trips a memory alarm or disk alarm at the node level. Once that fires, the node blocks publishers across every vhost, not just the offending one. A vhost is an administrative and namespace boundary, not a resource sandbox: it prevents tenants from seeing or touching each other's queues and messages, but it does not prevent a noisy tenant from degrading service for everyone sharing the node.
For node-level behavior (memory watermarks, disk limits, clustering, quorum queues), see the production architecture guide. For how exchanges, queues, and bindings fit together, see the topology guide.
When to isolate with vhosts vs separate clusters
The decision is not about how many tenants you have. It is about blast radius, compliance boundaries, and operational divergence.
Use vhosts on a shared cluster when:
- Tenants have similar throughput and burst profiles, so no single tenant can starve the others
- Tenants are internal teams or trusted business units, not adversarial parties
- Compliance scope treats the whole cluster as one system anyway
- Tenant count is high (dozens to hundreds) and minimizing infrastructure cost matters
- Upgrade cadence and maintenance windows can be shared without conflict
Use separate clusters when:
- A compliance boundary requires infrastructure separation, not just namespace separation (HIPAA and PCI DSS tenants with different auditors, for example)
- SLAs differ meaningfully: one tenant needs 15-minute incident response, another accepts next-business-day
- One tenant's traffic is large enough to alarm the shared node
- A tenant's outage must never affect another tenant's availability, and vhosts' shared blast radius is unacceptable
- Upgrade cadence diverges: one tenant is pinned to an older RabbitMQ version for a vendor integration
The practical rule: vhosts solve organizational multi-tenancy cheaply, while separate clusters solve blast radius and compliance multi-tenancy. Most enterprises land on a hybrid: vhosts for low-risk, similarly-sized tenants, and dedicated clusters for the handful with regulatory, SLA, or scale requirements that don't fit the shared model.
Multi-tenant permission design
Every RabbitMQ user is assigned permissions per vhost using three regular expressions: configure, write, and read, which control what a user can declare or delete, publish to, and consume from or bind to, respectively.
Design permissions around service accounts, not people. A named human account tied to permissions creates an audit and offboarding problem the moment someone changes roles or leaves; create one account per application per vhost instead.
rabbitmqctl add_vhost tenant-acme
rabbitmqctl add_user svc-acme-orders StrongRandomPassword
rabbitmqctl set_permissions -p tenant-acme svc-acme-orders \
"^orders\." "^orders\." "^orders\."
This grants svc-acme-orders rights only to resources starting with orders. inside tenant-acme. It cannot touch anything outside that prefix, and it has no visibility into any other vhost.
Topic permissions add a second layer for topic exchanges, where the routing key itself carries meaning (orders.region.us.priority.high):
rabbitmqctl set_topic_permissions -p tenant-acme svc-acme-orders \
amq.topic "^orders\.us\." "^orders\.(us|eu)\."
Here the service can publish only US-prefixed routing keys but read both US and EU traffic, useful when a reporting service needs broader read access than the publishers it aggregates.
User tags control management and monitoring access, separate from vhost resource permissions: management allows UI login and visibility into vhosts the user has permissions on; monitoring gives read-only access to broker-wide metrics, including vhosts the user has no resource permissions on; policymaker allows setting policies and parameters on administered vhosts; administrator grants full control, including user and vhost management across the whole broker.
Give application service accounts no tag at all; they only need AMQP access, not UI login. Reserve administrator for platform operators, not tenant admins. If a tenant manages their own policies, grant policymaker scoped to their vhost, never a broker-wide tag. Audit permission strings periodically: a configure, write, or read string of .* is a red flag on any service account, not a convenience.
Resource limits per vhost
RabbitMQ 3.8 and later support two hard limits set directly on a vhost:
rabbitmqctl set_vhost_limits -p tenant-acme \
'{"max-connections": 200, "max-queues": 500}'
max-connections caps how many AMQP connections a tenant can open into that vhost, indirectly limiting channel and consumer sprawl. max-queues caps how many queues can exist in the vhost, protecting against runaway dynamic queue creation from a misbehaving client or a broken temporary-queue cleanup path.
Per-user limits add a second layer, independent of vhost:
rabbitmqctl set_user_limits svc-acme-orders \
'{"max-connections": 50, "max-channels": 200}'
Say this plainly, because it surprises people: memory and disk are not per-vhost resources. There is no max-memory or max-disk setting scoped to a vhost. The node's vm_memory_high_watermark and disk_free_limit apply globally, and one tenant with unbounded queue growth can trip either alarm and block publishers cluster-wide.
In practice, the real throttle for memory and disk consumption is policies, not connection limits. Apply per-vhost policies for message TTL, so unconsumed messages don't accumulate indefinitely; max-length or max-length-bytes, to cap queue size and force overflow behavior (drop head or reject publish) once a bound is hit; a dead-letter exchange (DLX), so messages that hit TTL or length limits go somewhere observable instead of vanishing; and quorum queue defaults, since quorum queues carry different memory and replication behavior than classic queues.
rabbitmqctl set_policy -p tenant-acme tenant-caps \
'^orders\.' \
'{"max-length": 100000, "message-ttl": 3600000, "dead-letter-exchange": "orders.dlx"}' \
--apply-to queues
When a tenant legitimately needs more than the shared caps allow, the answer is not to loosen the limit for everyone. Raise that tenant's vhost limits and policy caps individually, watch node-level memory and disk headroom as you do it, and if the increase pushes them into a materially different resource footprint than other tenants on the node, treat that as the signal to migrate them to a dedicated vhost or a separate cluster (see the migration section below).
Operational patterns
Naming convention. Pick one and enforce it everywhere: env.tenant.purpose (prod.acme.orders) or tenant.env (acme.prod) both work; what matters is that it is predictable enough to script against and grep in logs. Avoid free-text vhost names chosen ad hoc per onboarding request.
Provisioning. Manual rabbitmqctl commands do not scale past a handful of tenants. Two supported paths: definitions export/import, where rabbitmqctl export_definitions produces a JSON document covering vhosts, users, permissions, policies, and topology that you can template per tenant and load with rabbitmqctl import_definitions; and the HTTP API, where the management plugin exposes PUT /api/vhosts/{name}, /api/permissions/{vhost}/{user}, and /api/vhosts/{name}/limits, which is what most Terraform and Ansible modules call under the hood. Use the community Terraform provider for RabbitMQ, or Ansible's rabbitmq_vhost and rabbitmq_user modules, to keep provisioning version-controlled rather than run by hand against production.
Monitoring. The RabbitMQ Prometheus exporter, built in since 3.8 via rabbitmq_prometheus, labels metrics with vhost, so you can build per-tenant dashboards and alerts on queue depth, connection count, and message rate without separate exporters. Alert on per-vhost queue growth and connection counts approaching max-connections, not just node-wide memory and disk, since node-wide alarms fire too late to attribute blame to a specific tenant. See the Kubernetes monitoring guide for exporter setup in containerized deployments.
Federation and shovel. Both are configured per vhost, not globally. A federation upstream or a shovel link declared in tenant-acme only moves messages in or out of resources within that vhost, keeping cross-tenant data movement explicit and auditable rather than implicit.
Security and compliance angle
Vhosts support separation of duties cleanly: a tenant's policymaker can adjust their own queue policies without touching another tenant's vhost, while a platform-wide administrator account stays reserved for infrastructure operators, not tenant staff. Keep that separation in the permission model, not just in process documentation.
Audit logging of definition changes matters more than most teams budget for. Every set_permissions, set_policy, and add_vhost call changes the tenant isolation model, and those changes should be logged and reviewable, whether through the management plugin's event exchange, shipped RabbitMQ logs, or a change-management process wrapped around the provisioning pipeline (Terraform plan/apply history works well here).
TLS should terminate per connection with mutual authentication where required. Per-vhost certificate mapping via x.509 or OAuth2 token claims ties a client certificate or JWT scope directly to a vhost's permissions, removing username and password management from tenant onboarding. This pairs well with a FIPS-validated cryptographic module where regulatory scope requires it; see FIPSMQ.
When presenting vhost isolation to an auditor, be precise about the claim. A vhost is a control boundary: it enforces access control and namespace separation, auditable through permission exports. It is not hard isolation at the infrastructure level, since compute, memory, and disk are shared. Auditors used to VM- or container-level isolation sometimes assume a vhost provides the same guarantee; correcting that up front avoids a finding later. See RabbitMQ compliance and the regulated-industry messaging stack guide for the fuller compliance picture.
Migration: moving a tenant to its own vhost or cluster
Moving a tenant off a shared vhost, either to a dedicated vhost or out to a dedicated cluster, does not require downtime if you sequence it correctly.
Shared vhost to dedicated vhost (same cluster):
1. Create the new vhost and replicate the tenant's users, permissions, and policies into it.
2. Set up a shovel from the old vhost's tenant queues to the new vhost, so messages keep flowing during cutover.
3. Update producer configuration to publish to the new vhost, staged across instances if needed.
4. Once producers are confirmed writing to the new vhost, update consumer configuration to read from it.
5. Let the shovel drain remaining messages, then remove it and decommission the old resources.
The same shovel pattern works from a dedicated vhost out to a separate cluster, since shovels connect over standard AMQP and don't require cluster membership; point the shovel's destination at the new cluster's vhost. As an alternative, a dual-publish window has producers write to both locations for a fixed period, then flips consumers over once parity is confirmed and stops the old-side publish. This trades a short window of duplicate delivery, which requires idempotent consumers, for a simpler model than shovel draining.
Cutover checklist:
- Confirm consumer idempotency before enabling dual-publish or relying on shovel drain timing
- Validate that policies (TTL, max-length, DLX) are replicated exactly in the new location
- Confirm permissions and service account credentials are tested in the new location
- Monitor queue depth on both sides during the transition window
- Keep the old vhost or cluster available, read-only, for a rollback window
Anti-patterns
- Using the default
/vhost for every tenant, which leaves no namespace separation at all - One shared administrator account used by every team, making audit trails meaningless
- Per-person credentials on service accounts instead of per-application service accounts, which breaks the moment someone leaves
- Permission strings of
.*onconfigure,write, orread, granting unrestricted access within the vhost - No
max-connectionsormax-queueslimits set, leaving a buggy client free to exhaust broker resources - No per-vhost policies for TTL or max-length, so a stalled consumer can grow a queue until it trips the node-wide memory alarm
- Treating vhost isolation as equivalent to network or infrastructure isolation when talking to auditors or customers
- Provisioning vhosts by hand instead of through definitions export/import, the HTTP API, or infrastructure-as-code, which makes configuration drift undetectable
Running this reliably at scale, especially across regulated tenants with divergent SLAs, is where AceMQ comes in. AceMQ is Broadcom's exclusive strategic RabbitMQ MSP partner, with a direct line to the RabbitMQ core team, a 15-minute emergency response SLA, and support coverage from 3.8.x through 4.x, across 130+ enterprise clients in 26+ countries, including regulated deployments in finance, healthcare, and government. See RabbitMQ services or contact us for an architecture review.
Frequently Asked Questions
Is a RabbitMQ vhost a security boundary?
Partially. A vhost enforces access control and namespace isolation: users need explicit permissions to see or touch a vhost's exchanges and queues, and one tenant cannot browse another tenant's resources. But all vhosts on a node share the same Erlang VM, memory, disk, and CPU. A vhost does not protect against resource exhaustion or node-level denial of service caused by another tenant. Treat it as a control boundary for access, not a hard isolation boundary for compute or memory, especially when describing it to auditors.
How do I limit resources per tenant in RabbitMQ?
Set max-connections and max-queues directly on the vhost with rabbitmqctl set_vhost_limits, and optionally cap max-connections and max-channels per user account. For memory and disk, which are not vhost-scoped, apply per-vhost policies instead: message TTL, max-length or max-length-bytes on queues, and a dead-letter exchange so overflow messages route somewhere visible rather than silently dropping or growing unbounded, which is what actually protects the shared node.
Should each customer get its own RabbitMQ vhost or cluster?
Use a vhost on a shared cluster when tenants have similar traffic profiles and no strict requirement for physical infrastructure separation; it is cheaper to operate and scales to hundreds of tenants. Move a tenant to a dedicated cluster when a compliance boundary demands infrastructure-level separation, when SLAs diverge meaningfully between tenants, or when one tenant's throughput is large enough to risk alarming the shared node and affecting everyone else on it.
Can one noisy tenant crash a shared RabbitMQ vhost setup for everyone?
Yes, and this is the most common vhost misunderstanding. Memory and disk alarms are node-level, not vhost-level. If one tenant's queue grows unbounded because a consumer stalls or a producer bursts, the node can trip vm_memory_high_watermark or disk_free_limit, which blocks publishers across every vhost on that node, not just the offending tenant's. Per-vhost policies for TTL and max-length, plus proactive per-vhost monitoring, are the practical mitigation.
What RabbitMQ user tags should service accounts have?
Application service accounts publishing and consuming messages generally need no management tag at all, since AMQP operations don't require management UI access. Reserve management for humans who need UI login, monitoring for read-only broad visibility, policymaker for tenant administrators who manage their own vhost's policies, and administrator strictly for platform operators managing the whole broker. Tagging a service account as administrator for convenience is a common audit finding.
How do I set up least-privilege permissions for a multi-tenant RabbitMQ vhost?
Create one service account per application per vhost, not shared or per-person credentials. Use rabbitmqctl set_permissions with regex patterns scoped to the resource prefix that application actually needs, for example ^orders\. rather than .*. Add topic permissions separately for topic exchanges where routing keys carry access-relevant meaning. Audit permission strings periodically for any .* pattern, which indicates a grant broader than the application requires.