# How to Run RabbitMQ in a Cloud Sandbox

> 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.

Source: https://sandboxer.com/guides/run-rabbitmq-in-a-sandbox. Updated 2026-10-27.

**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](https://pypi.org/project/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](https://hub.docker.com/_/rabbitmq) 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`:

```bash
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"
```

```text
4.4.0
```

The 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?](/guides/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 creates `app` instead of `guest`. RabbitMQ only lets `guest` connect from localhost anyway (see its [access control docs](https://www.rabbitmq.com/docs/access-control)). Over the hosted URL, `guest:guest` got `401 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:

```bash
sbx forward $id --remote 5672 --local 5673
```

```text
{"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](/guides/run-mysql-or-mariadb-in-a-sandbox) 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:

```python
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()
```

```bash
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 get
```

```text
connected 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: 999
```

Two 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=True` and nothing else. The broker closed the connection: `541 INTERNAL_ERROR - Feature transient_nonexcl_queues is deprecated`. Declare scratch queues `exclusive=True`, as the script now does, or make them durable. RabbitMQ's [deprecated features list](https://www.rabbitmq.com/docs/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](https://perftest.rabbitmq.com/), in the same sandbox. It ships as a Docker image. `--network host` lets it reach `127.0.0.1:5672`:

```bash
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:

```text
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 µs
```

Compare 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:

```bash
sbx host $id 15672 --localhost --title "RabbitMQ management"
# prints https://fresh-airy-kayak-15672.box.sandboxer.com?_token=<token>; keep it secret
```

The 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](https://www.rabbitmq.com/docs/http-api-reference), which can publish a message. That is handy for a quick test from a script that has no AMQP client:

```bash
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"}'
```

```text
{"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](/docs/hosting) 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:443` completed 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 `AMQPConnectorStackTimeout` after 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](https://www.rabbitmq.com/docs/web-stomp) 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:

```bash
sbx stop $id --wait     # 2.2 s
sbx resume $id          # 1.5 s, memoryRestored: true
python amqp_client.py 5673 get
```

```text
connected in 507 ms
jobs queue depth before: 1000
got: job 1
jobs queue depth after: 999
```

The 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:

```bash
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"
```

```text
rabbit Up 4 seconds
name	durable	messages
jobs	true	999
```

The 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](/docs/snapshots#resume) for the resume rules.

## 7. Clean up

Stop the forward with Ctrl-C, then delete the sandbox:

```bash
sbx delete $id --yes
```

After 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

- [Run Redpanda (Kafka API) in a sandbox](/guides/run-redpanda-kafka-in-a-sandbox)
- [Give a sandbox port an HTTPS URL](/guides/give-a-sandbox-port-an-https-url)
- [Run Docker Compose in a sandbox with a URL per service](/guides/run-docker-compose-in-a-sandbox-with-a-url-per-service)
- [SSH and file transfer](/docs/ssh)
