How to Run RabbitMQ in a Cloud Sandbox
Updated October 27, 2026 .md
Short answer: Run the official rabbitmq:4-management Docker image in a Sandboxer microVM with both ports bound to 127.0.0.1. Reach AMQP on port 5672 from your laptop with sbx forward, and the management UI on a private HTTPS URL with sbx host --localhost. AMQP itself doesn't pass through hosted URLs. Persistent messages survived stop, resume and a broker restart.
What we ran#
Everything below ran on production on October 27, 2026, in one small sandbox (2 vCPU, 4 GB): RabbitMQ 4.4.0 on Erlang 29.1.2 from the official rabbitmq:4-management image. The laptop client was Python with pika. Timings are single runs, or 20 runs where we give a median. Yours will differ.
| Step | Result |
|---|---|
sbx new --type small until ready |
3.2 s |
| Pull the image (283 MB), start it, wait for the AMQP port | 14 s |
| PerfTest in the sandbox, 1 KB persistent messages, 15 s | 12,768 msg/s average, 5.8 ms median confirm latency |
AMQP from a laptop through sbx forward |
connect 0.5 s, publish + get 250 ms median round trip |
| 1,000 persistent publishes from the laptop, one confirm each | 126.4 s |
| Management UI and HTTP API on a private hosted URL | 401 without the token, 200 with token and RabbitMQ login |
| AMQP client against a hosted URL | no AMQP reply, client timed out after 15 s |
sbx stop --wait, then sbx resume |
2.2 s and 1.5 s, memoryRestored: true, queue intact, forward kept working |
| Docker daemon restart | broker back in 5 s, 999 persistent messages still queued |
RabbitMQ has two front doors, and they need different routes. AMQP on port 5672 is a binary TCP protocol. The management UI and HTTP API on port 15672 are plain HTTP. Sandboxer's hosted URLs are HTTPS, so only the second one fits there.
| What you reach | Port | Route from outside | Tested |
|---|---|---|---|
| AMQP clients (pika, amqplib, Spring AMQP…) | 5672 | sbx forward --remote 5672, or ssh -L |
Yes, sbx forward |
| Management UI in a browser | 15672 | sbx host 15672 --localhost, private |
Yes |
| Management HTTP API (including publish) | 15672 | Same hosted URL, with RabbitMQ basic auth | Yes |
| AMQP over the hosted HTTPS URL | 5672 | Doesn't work | Yes, failed |
| Producers and consumers that run in the sandbox | 5672 | 127.0.0.1:5672, no route needed |
Yes, PerfTest |
1. Start RabbitMQ#
Docker is preinstalled in every sandbox. The Docker Hub page documents the image, its -management tag and the RABBITMQ_DEFAULT_USER/RABBITMQ_DEFAULT_PASS variables. Make a password on your laptop, then start the container. The image pull can take longer than the default 30-second sbx exec timeout, so pass --timeout:
sbx new --name rabbit --type small --no-env
id=sb_58f03i38vj # the id that sbx new printed
RMQ_PASS=$(openssl rand -hex 12)
sbx exec $id --timeout 600 "docker run -d --name rabbit --hostname rabbit --restart unless-stopped \
-e RABBITMQ_DEFAULT_USER=app -e RABBITMQ_DEFAULT_PASS=$RMQ_PASS \
-p 127.0.0.1:5672:5672 -p 127.0.0.1:15672:15672 \
-v rabbitdata:/var/lib/rabbitmq rabbitmq:4-management >/dev/null \
&& until docker exec rabbit rabbitmq-diagnostics -q check_port_connectivity >/dev/null 2>&1; do sleep 1; done; \
docker exec rabbit rabbitmq-diagnostics -q server_version"4.4.0The password travels inside the command, so it's stored in the sandbox's command history (sbx history) and visible to docker inspect. That's fine for a throwaway broker in a sandbox you'll delete. For a password you care about, follow How do I pass secrets to a sandbox safely? instead.
Each flag has a job:
-p 127.0.0.1:…. Both ports listen inside the VM only. You decide below what reaches them, and how.--hostname rabbit. RabbitMQ names its node and data directory after the hostname. A fixed name keeps the same data directory if the container is recreated.-v rabbitdata:/var/lib/rabbitmq. Queues and persistent messages live in a named volume on the sandbox disk.RABBITMQ_DEFAULT_USER. The image then createsappinstead ofguest. RabbitMQ only letsguestconnect from localhost anyway (see its access control docs). Over the hosted URL,guest:guestgot401 not_authorized.--restart unless-stopped. If a resume boots the sandbox from disk, Docker starts the broker again (section 6).
2. Connect an AMQP client from your laptop#
Forward a local port to the sandbox's port 5672. sbx forward runs over the same authenticated tunnel as sbx ssh and keeps running in the foreground, so give it its own terminal. We used local port 5673 to stay clear of any RabbitMQ on the laptop:
sbx forward $id --remote 5672 --local 5673{"event":"forwarding","id":"sb_58f03i38vj","reverse":false,"local":5673,"remote":5672,"bind":"127.0.0.1"}Any AMQP client on the laptop can now use amqp://app:<password>@127.0.0.1:5673/. Nothing is public. The MySQL recipe shows the plain ssh -L route, which works the same way for port 5672.
Save this as amqp_client.py. It declares a durable jobs queue and turns on publisher confirms, then measures a round trip or publishes a batch:
import os, statistics, sys, time
import pika
port = int(sys.argv[1]); mode = sys.argv[2]
params = pika.ConnectionParameters(
"127.0.0.1", port, "/", pika.PlainCredentials("app", os.environ["RMQ_PASS"]))
t0 = time.perf_counter()
conn = pika.BlockingConnection(params)
print(f"connected in {(time.perf_counter()-t0)*1000:.0f} ms")
ch = conn.channel()
ch.confirm_delivery()
q = ch.queue_declare("jobs", durable=True)
print("jobs queue depth before:", q.method.message_count)
persistent = pika.BasicProperties(delivery_mode=pika.DeliveryMode.Persistent)
if mode == "rtt":
ch.queue_declare("rtt", exclusive=True)
times = []
for i in range(21):
t = time.perf_counter()
ch.basic_publish("", "rtt", f"ping {i}".encode())
method, _, body = ch.basic_get("rtt", auto_ack=True)
times.append((time.perf_counter() - t) * 1000)
times = times[1:]
print(f"publish+confirm+get round trip: median {statistics.median(times):.0f} ms, min {min(times):.0f} ms over {len(times)}")
elif mode == "publish":
n = int(sys.argv[3])
t = time.perf_counter()
for i in range(n):
ch.basic_publish("", "jobs", f"job {i}".encode(), persistent)
print(f"published {n} persistent messages with confirms in {time.perf_counter()-t:.1f} s")
elif mode == "get":
method, props, body = ch.basic_get("jobs", auto_ack=True)
print("got:", body.decode() if body else None)
q = ch.queue_declare("jobs", durable=True, passive=True)
print("jobs queue depth after:", q.method.message_count)
conn.close()export RMQ_PASS # the password from step 1
python amqp_client.py 5673 publish 1000
python amqp_client.py 5673 rtt
python amqp_client.py 5673 getconnected in 507 ms
jobs queue depth before: 0
published 1000 persistent messages with confirms in 126.4 s
jobs queue depth after: 1000
connected in 503 ms
jobs queue depth before: 1000
publish+confirm+get round trip: median 250 ms, min 250 ms over 20
jobs queue depth after: 1000
connected in 502 ms
jobs queue depth before: 1000
got: job 0
jobs queue depth after: 999Two lessons from this run:
- Waiting for each confirm over a long link is slow. The blocking client waits for every confirm before it sends the next message. That is about 126 ms per message on our link, so 1,000 messages took two minutes. Most of that time is the network between our laptop and the sandbox, which depends on where you are. For bulk work, confirm in batches or asynchronously, or run the producer next to the broker (section 3).
- RabbitMQ 4 refuses transient, non-exclusive queues by default. Our first try declared the scratch queue with
auto_delete=Trueand nothing else. The broker closed the connection:541 INTERNAL_ERROR - Feature transient_nonexcl_queues is deprecated. Declare scratch queuesexclusive=True, as the script now does, or make them durable. RabbitMQ's deprecated features list covers it.
3. Measure throughput inside the sandbox#
To see what the broker does without the network in the way, run RabbitMQ's own load tool, PerfTest, in the same sandbox. It ships as a Docker image. --network host lets it reach 127.0.0.1:5672:
sbx exec $id --timeout 400 "docker run --rm --network host pivotalrabbitmq/perf-test:latest \
--uri amqp://app:$RMQ_PASS@127.0.0.1:5672 -x 1 -y 1 -s 1000 -z 15 -f persistent -c 100 -q 1000 2>&1 | tail -6"That is one producer and one consumer, 1,000-byte persistent messages, up to 100 unconfirmed messages in flight, a prefetch of 1,000 and a 15-second run. PerfTest 2.25.0 printed:
test stopped (Reached time limit)
id: test-101634-126, sending rate avg: 12768 msg/s
id: test-101634-126, receiving rate avg: 12768 msg/s
id: test-101634-126, consumer latency min/median/75th/95th/99th/max 344/4091/6068/9962/29296/156461 µs
id: test-101634-126, confirm latency min/median/75th/95th/99th/max 1367/5824/7463/11148/15558/22092 µsCompare that with the laptop run: 12,768 confirmed messages a second in the sandbox, versus about 8 a second with one-at-a-time confirms over the tunnel. If agents or workers produce and consume the messages, run them in the sandbox and use the tunnel for inspection and light traffic.
4. Open the management UI on a private URL#
The management plugin is HTTP, so it can have a hosted URL. It listens on 127.0.0.1 here, so pass --localhost and the sandbox relays to it:
sbx host $id 15672 --localhost --title "RabbitMQ management"
# prints https://fresh-airy-kayak-15672.box.sandboxer.com?_token=<token>; keep it secretThe URL is private by default. Behind it are two separate locks: Sandboxer's token, then RabbitMQ's own login. We checked each one with curl:
| Request to the hosted URL | Status | Who answered |
|---|---|---|
GET /api/overview, no token |
401 "Access token required" page |
Sandboxer proxy, RabbitMQ never saw it |
GET /?_token=<token> |
302 to /, sets the __sbx_auth cookie |
Sandboxer proxy |
| Cookie, no RabbitMQ login | 401, www-authenticate: Basic realm="RabbitMQ Management" |
RabbitMQ |
Cookie plus -u guest:guest |
401 not_authorized |
RabbitMQ |
Cookie plus -u app:<password> |
200, rabbitmq_version 4.4.0 |
RabbitMQ |
Cookie, GET / |
200, <title>RabbitMQ Management |
RabbitMQ |
In a browser, open the printed link once. The proxy swaps the token for a cookie, and RabbitMQ shows its login page. Sign in as app.
The same URL serves the HTTP API, which can publish a message. That is handy for a quick test from a script that has no AMQP client:
curl -s -b jar -u app:$RMQ_PASS -H 'content-type: application/json' \
-X POST "https://fresh-airy-kayak-15672.box.sandboxer.com/api/exchanges/%2F/amq.default/publish" \
-d '{"properties":{"delivery_mode":2},"routing_key":"jobs","payload":"sent over https","payload_encoding":"string"}'{"routed":true}jar is the cookie file from the ?_token= request (curl -c jar). A few seconds later, GET /api/queues/%2F/jobs showed "messages":1000,"messages_persistent":1000: the 999 left from step 2, plus this one. RabbitMQ refreshes queue stats on a timer, so a read right after the publish still showed 999.
5. Why AMQP doesn't work on a hosted URL#
The hosting docs put services on https://<subdomain>-<port>.box.sandboxer.com. That URL is an HTTPS endpoint, not a raw TCP port. To check, we hosted port 5672 too, first private and then --public, and pointed AMQP at it:
- A raw TLS connection to
fresh-airy-kayak-5672.box.sandboxer.com:443completed the handshake. When we sent the AMQP protocol header, nothing came back before our 10-second read timeout. - pika with TLS to the same host and port 443 failed with
AMQPConnectorStackTimeoutafter 15 seconds.
Then we took that URL down with sbx host hide $id 5672. Use sbx forward or SSH for AMQP. If browsers need to talk to RabbitMQ, its Web STOMP and Web MQTT plugins run over WebSockets, which hosted URLs carry. We didn't test those plugins here.
6. Stop, resume and broker restarts#
We left sbx forward running and stopped the sandbox:
sbx stop $id --wait # 2.2 s
sbx resume $id # 1.5 s, memoryRestored: true
python amqp_client.py 5673 getconnected in 507 ms
jobs queue depth before: 1000
got: job 1
jobs queue depth after: 999The same forward process carried the new connection. The management URL answered 200 with the cookie from before the stop. Memory was restored, so the broker never restarted: docker ps showed rabbit Up 4 minutes.
A resume boots from disk instead when no machine with the same CPU type is free. Then files stay and processes start fresh. We didn't force that case, but restarting the Docker daemon shows the same thing: the container stops, the restart policy brings it back, and RabbitMQ reloads its queues from the volume:
sbx exec $id --timeout 120 "sudo systemctl restart docker
until docker exec rabbit rabbitmq-diagnostics -q check_port_connectivity >/dev/null 2>&1; do sleep 1; done
docker ps --format '{{.Names}} {{.Status}}'; docker exec rabbit rabbitmqctl -q list_queues name durable messages"rabbit Up 4 seconds
name durable messages
jobs true 999The broker was back in 5 seconds with all 999 messages. They survived because the queue is durable and the messages were published persistent (delivery_mode=2). Transient messages don't survive a broker restart. See snapshots for the resume rules.
7. Clean up#
Stop the forward with Ctrl-C, then delete the sandbox:
sbx delete $id --yesAfter the delete, the API returned 404 for the sandbox, and the management URL returned 404. A small sandbox bills at half the default rate of $0.036/hour while it runs, and nothing while it's stopped.
FAQ#
Can I connect to RabbitMQ in a sandbox without SSH?#
Not over AMQP. Hosted URLs are HTTPS, and our AMQP client timed out against one. Use sbx forward, which needs only outbound HTTPS and the CLI, or ssh -L. For HTTP-only access, the management API can publish and read queues over the hosted URL.
Why does RabbitMQ close my connection with "transient_nonexcl_queues is deprecated"?#
RabbitMQ 4 refuses queues that are neither durable nor exclusive by default. Declare scratch queues with exclusive=True or make them durable. The error came from the broker, not from the sandbox.
Why is publishing from my laptop so slow?#
Each blocking publish waited for its confirm, so every message paid a full round trip through the tunnel, about 126 ms for us. In the sandbox, PerfTest reached 12,768 msg/s with up to 100 confirms in flight. Batch your confirms, or run producers in the sandbox.
Is a small sandbox enough?#
For a dev or test broker, yes. 2 vCPU and 4 GB handled 12,768 persistent 1 KB messages a second in our run. A deep backlog uses memory and disk, so watch the volume, and pick default for heavier loads.
Do I need to open a firewall port?#
No. sbx forward reaches 127.0.0.1:5672 inside the VM through the CLI's tunnel. sbx host sets up the route for the management port. RabbitMQ listened only on 127.0.0.1 the whole time.
Related#
More guides
For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.
For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.
The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.
If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.