Skip to main content

Manual backup and restore for RabbitMQ

Platform backup was removed from RabbitMQ on 15 September 2026, so protecting a cluster's configuration is now something you run yourself. This page exports the cluster's definitions through the Management HTTP API, checks the file is complete, and imports it into a new cluster.

warning

This backs up definitions only — vhosts, users, permissions, exchanges, queues, bindings, and policies. Messages sitting in queues are not included and cannot be recovered this way.

Applies to RabbitMQ 3.13.x on Erlang 26.x.

What definitions contain​

ComponentWhat it covers
VhostsThe virtual hosts
Users and permissionsAccounts and their access rights
ExchangesDirect, topic, fanout, and headers exchanges
QueuesQueue declarations and their arguments
BindingsThe links between exchanges and queues
PoliciesTTL, HA, max-length, dead-letter, and the rest

How it works​

Run everything from a backup server — a VM you control that can reach the RabbitMQ cluster on port 15672. It calls the Management HTTP API directly, so nothing is installed on the cluster itself and no node is ever stopped.

PhaseWhat happens
BackupThe backup server calls GET /api/definitions on the source cluster and saves the JSON
RestoreThe backup server posts a saved file to POST /api/definitions on the target cluster

During a restore the source cluster is not contacted at all.

Prerequisites​

ItemRequirement
NetworkThe backup server reaches the cluster on port 15672
AccountA user with the administrator tag
Target versionOn restore, the target cluster's RabbitMQ version must be at or above the source
Erlang versionShould match the source cluster

The commands below use these placeholders: <HOST> is the source cluster address and <TARGET_HOST> the destination one, <USERNAME> and <PASSWORD> are the administrator credentials, <VHOST_NAME> is a vhost name, <TIMESTAMP> is the stamp in a saved filename, and <RABBITMQ_HOST> is the address the scheduled script reads.

Back up the definitions​

Export the whole cluster​

curl -s \
-u '<USERNAME>:<PASSWORD>' \
http://<HOST>:15672/api/definitions \
-o /backup/rabbitmq/rabbitmq_definitions_$(date +%Y%m%d_%H%M%S).json

Example:

curl -s \
-u 'admin:your_password' \
http://10.0.1.10:15672/api/definitions \
-o /backup/rabbitmq/rabbitmq_definitions_$(date +%Y%m%d_%H%M%S).json

Export a single vhost​

curl -s \
-u '<USERNAME>:<PASSWORD>' \
'http://<HOST>:15672/api/definitions/<VHOST_NAME>' \
-o /backup/rabbitmq/rabbitmq_vhost_<VHOST_NAME>_$(date +%Y%m%d_%H%M%S).json

Example — backing up the vhost app_production:

curl -s \
-u 'admin:your_password' \
'http://10.0.1.10:15672/api/definitions/app_production' \
-o /backup/rabbitmq/rabbitmq_vhost_production_$(date +%Y%m%d_%H%M%S).json
warning

A per-vhost export leaves out users and global parameters. Use the whole-cluster export when the file is meant for a full migration.

Check the file before you trust it​

An export that returned an error page is still a file on disk, so count what it holds:

python3 << 'EOF'
import json

with open("/backup/rabbitmq/rabbitmq_definitions_<TIMESTAMP>.json") as f:
d = json.load(f)

print(f"Vhosts: {len(d.get('vhosts', []))}")
print(f"Users: {len(d.get('users', []))}")
print(f"Exchanges: {len(d.get('exchanges', []))}")
print(f"Queues: {len(d.get('queues', []))}")
print(f"Bindings: {len(d.get('bindings', []))}")
print(f"Policies: {len(d.get('policies', []))}")
EOF

The counts should match the source cluster:

Vhosts:      4
Users: 5
Exchanges: 5
Queues: 8
Bindings: 8
Policies: 3

Restore into a new cluster​

Import the file​

curl -s -X POST \
-u '<USERNAME>:<PASSWORD>' \
-H "Content-Type: application/json" \
-d @/backup/rabbitmq/rabbitmq_definitions_<TIMESTAMP>.json \
http://<TARGET_HOST>:15672/api/definitions

Example:

curl -s -X POST \
-u 'admin:your_password' \
-H "Content-Type: application/json" \
-d @/backup/rabbitmq/rabbitmq_definitions_20260519_020001.json \
http://10.0.2.10:15672/api/definitions

Read the response code​

A successful import returns nothing, so ask for the status code explicitly:

curl -s -o /dev/null -w "%{http_code}\n" -X POST \
-u 'admin:your_password' \
-H "Content-Type: application/json" \
-d @/backup/rabbitmq/rabbitmq_definitions_20260519_020001.json \
http://10.0.2.10:15672/api/definitions
CodeMeaning
200Import succeeded
400The JSON file is not valid
401Wrong username or password
403The user lacks the administrator tag

Verify the two clusters match​

Compare the counts on both sides rather than assuming the import was complete:

python3 << 'EOF'
import json, urllib.request, base64

def get_definitions(host, user, password):
url = f"http://{host}:15672/api/definitions"
req = urllib.request.Request(url)
token = base64.b64encode(f"{user}:{password}".encode()).decode()
req.add_header("Authorization", f"Basic {token}")
with urllib.request.urlopen(req) as r:
return json.load(r)

SOURCE = get_definitions("10.0.1.10", "admin", "your_password")
DEST = get_definitions("10.0.2.10", "admin", "your_password")

keys = ["vhosts", "users", "exchanges", "queues", "bindings", "policies"]

print(f"{'Thành phần':<15} {'Nguồn':>8} {'Đích':>8} {'Khớp':>8}")
print("-" * 45)
for k in keys:
src = len(SOURCE.get(k, []))
dst = len(DEST.get(k, []))
match = "OK" if src == dst else "LỆCH"
print(f"{k:<15} {src:>8} {dst:>8} {match:>8}")
EOF

Expected result:

Thành phần      Nguồn     Đích     Khớp
---------------------------------------------
vhosts 4 4 OK
users 5 5 OK
exchanges 5 5 OK
queues 8 8 OK
bindings 8 8 OK
policies 3 3 OK

What to expect from an import​

Passwords survive. The definitions file stores password hashes rather than plaintext, and those hashes import as they are. Users keep their existing passwords on the target cluster.

HA policies need the nodes to back them. A ha-mode: all policy imports onto any cluster, but it only takes effect where there are at least three nodes. Give the target cluster a topology comparable to the source.

Importing twice is safe. The operation is idempotent: resources that already exist are updated or left alone, never deleted. Re-running a failed import does no harm.

The cluster name is not overwritten. cluster_name in the file is informational. The target cluster keeps its own name.

Automate it​

The backup script​

Create /opt/backup/rabbitmq_backup.sh:

#!/bin/bash

# ---- Configuration ----
HOST="<RABBITMQ_HOST>"
USER="admin"
PASS='your_password'
BACKUP_DIR="/backup/rabbitmq"
RETENTION_DAYS=30
# ------------------

mkdir -p "$BACKUP_DIR"

FILENAME="rabbitmq_definitions_$(date +%Y%m%d_%H%M%S).json"
FILEPATH="${BACKUP_DIR}/${FILENAME}"

curl -s \
-u "${USER}:${PASS}" \
"http://${HOST}:15672/api/definitions" \
-o "$FILEPATH"

if [ $? -eq 0 ] && [ -s "$FILEPATH" ]; then
echo "[OK] Backup thành công: ${FILENAME}"
find "$BACKUP_DIR" -name "rabbitmq_definitions_*.json" \
-mtime +${RETENTION_DAYS} -delete
else
echo "[FAIL] Backup thất bại!"
rm -f "$FILEPATH"
exit 1
fi

Make it executable:

chmod +x /opt/backup/rabbitmq_backup.sh
tip

Quote PASS with single quotes. A password containing ! triggers bash history expansion inside double quotes and the script will send the wrong credentials.

Schedule and watch it​

crontab -e

Add a daily run at 02:00:

0 2 * * * /opt/backup/rabbitmq_backup.sh >> /var/log/rabbitmq_backup.log 2>&1

Then check the log after the first scheduled run:

tail -f /var/log/rabbitmq_backup.log

Output of each successful run:

[OK] Backup thành công: rabbitmq_definitions_20260519_020001.json

Next steps​