Install RabbitMQ on VPS - Message Broker Setup
RabbitMQ is the most popular open-source message broker — enabling async communication between services to make applications faster and more scalable than synchronous APIs. It supports multiple protocols (AMQP, MQTT, STOMP, HTTP) and includes a management UI for real-time monitoring of queues, connections, and channels. This guide covers installing RabbitMQ on VPS with Docker, creating virtual hosts and users per application, exposing the management console through Nginx, and setting up basic clustering for high availability.
RabbitMQ vs Alternatives: Why Use It
- AMQP protocol: Reliable message delivery with no data loss — supports persistent queues and acknowledgment mechanisms
- Flexible routing: Topic exchanges, fanout, direct routing — send messages to multiple consumers in parallel
- Queue persistence: Messages can be stored to disk — survives RabbitMQ restarts
- Backpressure handling: If a consumer is slow, RabbitMQ automatically stops accepting new messages from producers — prevents memory overflow
- Management Console: Web UI for monitoring queue depth, connections, and dead-letter queues without CLI
- Clustering & Mirroring: Setup clustering for failover and load balancing easily
Install RabbitMQ with Docker Compose
mkdir -p /opt/rabbitmq/{data,config} && cd /opt/rabbitmq
# Create RabbitMQ config file
cat > config/rabbitmq.conf << 'EOF'
# Default user and password (change immediately!)
default_user = guest
default_pass = guest
# Management plugin
management.load_definitions = /etc/rabbitmq/definitions.json
# Advanced settings
channel_max = 0 # unlimited channels
connection_max = 0 # unlimited connections
heartbeat = 60 # heartbeat timeout
frame_max = 131072 # 128 KB
# Disk and memory thresholds
vm_memory_high_watermark.relative = 0.6
disk_free_limit.absolute = 5GB
EOF
# Create definitions.json for auto-creating vhost, users, permissions (optional)
cat > config/definitions.json << 'EOF'
{
"vhosts": [
{"name": "myapp"}
],
"users": [
{"name": "myapp_user", "password": "CHANGE_ME_STRONG_PASSWORD", "tags": ""}
],
"permissions": [
{
"user": "myapp_user",
"vhost": "myapp",
"configure": ".*",
"write": ".*",
"read": ".*"
}
]
}
EOF
# Create docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: "3.9"
services:
rabbitmq:
image: rabbitmq:3.13-management-alpine
restart: unless-stopped
ports:
- "127.0.0.1:5672:5672" # AMQP port
- "127.0.0.1:15672:15672" # Management UI
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
volumes:
- ./config/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf
- ./config/definitions.json:/etc/rabbitmq/definitions.json
- ./data:/var/lib/rabbitmq
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
interval: 30s
timeout: 10s
retries: 5
volumes:
rabbitmq_data:
EOF
docker-compose up -d
Setup Virtual Host and User for Applications
# Create vhost "myapp" separate from "/" (default vhost)
docker-compose exec rabbitmq rabbitmqctl add_vhost myapp
# Create user for your application
docker-compose exec rabbitmq rabbitmqctl add_user myapp_user MySecurePassword123
# Grant permissions to configure, write, and read queues in the vhost
docker-compose exec rabbitmq rabbitmqctl set_permissions -p myapp myapp_user ".*" ".*" ".*"
# Verify vhost and user created successfully
docker-compose exec rabbitmq rabbitmqctl list_vhosts
docker-compose exec rabbitmq rabbitmqctl list_users
Setup Nginx Reverse Proxy for Management Console
nano /etc/nginx/sites-available/rabbitmq
server {
listen 80;
server_name rabbitmq.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name rabbitmq.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/rabbitmq.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/rabbitmq.yourdomain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:15672;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
ln -s /etc/nginx/sites-available/rabbitmq /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
certbot --nginx -d rabbitmq.yourdomain.com
Send Messages from Python Application
# Install pika library
pip install pika
# Example producer (send message)
cat > producer.py << 'EOF'
import pika
# Connect to RabbitMQ
credentials = pika.PlainCredentials('myapp_user', 'MySecurePassword123')
parameters = pika.ConnectionParameters(
host='127.0.0.1',
port=5672,
virtual_host='myapp',
credentials=credentials,
heartbeat=60
)
connection = pika.BlockingConnection(parameters)
channel = connection.channel()
# Create queue and exchange
channel.queue_declare(queue='emails', durable=True)
channel.exchange_declare(exchange='tasks', exchange_type='topic', durable=True)
channel.queue_bind(queue='emails', exchange='tasks', routing_key='email.*')
# Send message
message = '{"to": "[email protected]", "subject": "Hello"}'
channel.basic_publish(
exchange='tasks',
routing_key='email.send',
body=message,
properties=pika.BasicProperties(delivery_mode=2) # persistent
)
print("Message sent!")
connection.close()
EOF
python3 producer.py
# Example consumer (receive message)
cat > consumer.py << 'EOF'
import pika
import json
credentials = pika.PlainCredentials('myapp_user', 'MySecurePassword123')
parameters = pika.ConnectionParameters(
host='127.0.0.1',
port=5672,
virtual_host='myapp',
credentials=credentials
)
connection = pika.BlockingConnection(parameters)
channel = connection.channel()
channel.queue_declare(queue='emails', durable=True)
def callback(ch, method, properties, body):
try:
msg = json.loads(body)
print(f"Received: {msg}")
# Send email here
ch.basic_ack(delivery_tag=method.delivery_tag)
except Exception as e:
print(f"Error: {e}")
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='emails', on_message_callback=callback)
print("Waiting for messages...")
channel.start_consuming()
EOF
# Run in separate terminal or background
python3 consumer.py &
Setup Dead Letter Queue (DLQ) for Failed Messages
# Create exchange and queue for dead letters
docker-compose exec rabbitmq rabbitmqctl eval \
'amqp_client:start(),
Channel = amqp_connection:open_channel(Connection),
{ok, Connection} = amqp_connection:start(network, [
{username, <<"myapp_user">>},
{password, <<"MySecurePassword123">>},
{virtual_host, <<"myapp">>},
{host, "localhost"}
]),
amqp_channel:call(Channel, #amqp_queue.declare{queue = <<"emails_dlq">>, durable = true}),
amqp_channel:close(Channel),
amqp_connection:close(Connection).'
# Or use management API
curl -X PUT http://localhost:15672/api/queues/myapp/emails_dlq \
-u guest:guest \
-H "content-type:application/json" \
-d '{"durable":true,"arguments":{}}'
# Configure original queue to send failed messages to DLQ
curl -X PUT http://localhost:15672/api/queues/myapp/emails \
-u guest:guest \
-H "content-type:application/json" \
-d '{
"durable":true,
"arguments":{
"x-dead-letter-exchange":"",
"x-dead-letter-routing-key":"emails_dlq",
"x-message-ttl":86400000
}
}'
Setup Clustering for High Availability
# A cluster requires minimum 2 nodes for failover
# Add node 2 to docker-compose.yml
cat >> docker-compose.yml << 'EOF'
rabbitmq2:
image: rabbitmq:3.13-management-alpine
restart: unless-stopped
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
RABBITMQ_ERLANG_COOKIE: rabbitmq-cluster-cookie
ports:
- "127.0.0.1:5673:5672"
- "127.0.0.1:15673:15672"
volumes:
- ./config/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf
- ./rabbitmq2_data:/var/lib/rabbitmq
depends_on:
- rabbitmq
rabbitmq3:
image: rabbitmq:3.13-management-alpine
restart: unless-stopped
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
RABBITMQ_ERLANG_COOKIE: rabbitmq-cluster-cookie
ports:
- "127.0.0.1:5674:5672"
- "127.0.0.1:15674:15672"
volumes:
- ./config/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf
- ./rabbitmq3_data:/var/lib/rabbitmq
depends_on:
- rabbitmq
volumes:
rabbitmq2_data:
rabbitmq3_data:
EOF
docker-compose up -d
# Join node 2 and 3 to cluster
docker-compose exec rabbitmq2 rabbitmqctl stop_app
docker-compose exec rabbitmq2 rabbitmqctl reset
docker-compose exec rabbitmq2 rabbitmqctl join_cluster rabbit@rabbitmq
docker-compose exec rabbitmq2 rabbitmqctl start_app
docker-compose exec rabbitmq3 rabbitmqctl stop_app
docker-compose exec rabbitmq3 rabbitmqctl reset
docker-compose exec rabbitmq3 rabbitmqctl join_cluster rabbit@rabbitmq
docker-compose exec rabbitmq3 rabbitmqctl start_app
# Verify cluster status
docker-compose exec rabbitmq rabbitmqctl cluster_status
# Setup queue mirroring — replicate queue to other nodes
docker-compose exec rabbitmq rabbitmqctl set_policy ha-all \
"^emails$" '{"ha-mode":"all","ha-sync-batch-size":5}'
"memory alarm" state — increase vm_memory_high_watermark.relative in config or remove old messages with rabbitmqctl purge_queue QUEUE_NAMEVerify RabbitMQ is Working Correctly
# Check all containers
docker-compose ps
# Check RabbitMQ status
docker-compose exec rabbitmq rabbitmqctl status | head -30
# List all queues and message count
docker-compose exec rabbitmq rabbitmqctl list_queues name messages consumers
# List active connections
docker-compose exec rabbitmq rabbitmqctl list_connections name user vhost
# List active channels
docker-compose exec rabbitmq rabbitmqctl list_channels
# Test AMQP port connection
telnet localhost 5672
# Should display AMQP handshake string
# Test management console HTTP API
curl -u guest:guest http://localhost:15672/api/overview | python3 -m json.tool | head -20
consumers column in queue list. If it shows 0, no consumer is connected — verify consumer code and network connectivity.Troubleshooting Common Errors
Error: "403 ACCESS-REFUSED - operation not permitted on /"
User lacks permission to access vhost "/" (default vhost) — grant explicit permissions
# Grant user access to "myapp" vhost
docker-compose exec rabbitmq rabbitmqctl set_permissions -p myapp myapp_user ".*" ".*" ".*"
# If legacy code still points to "/", grant temporary permission (not for production)
docker-compose exec rabbitmq rabbitmqctl set_permissions -p "/" myapp_user "" "" "read"
Error: "PRECONDITION_FAILED - inequivalent arg 'x-expires' for queue"
Queue argument conflict — queue was created with TTL or DLX but code tries to recreate with different arguments
# Delete old queue
docker-compose exec rabbitmq rabbitmqctl delete_queue myqueue -p myapp
# Recreate or make code idempotent (passive declare only)
# In Python pika:
channel.queue_declare(queue='myqueue', durable=True, passive=False)
Consumer not receiving messages despite message sent
Queue binding mismatch with routing key — message sent but no queue subscribed
# Check binding between queue and exchange
docker-compose exec rabbitmq rabbitmqctl list_bindings exchange_name queue_name
# Create binding if missing
docker-compose exec rabbitmq rabbitmqctl bind_queue myqueue myexchange "myapp.*"
# Check dead letter queue for rejected messages
docker-compose exec rabbitmq rabbitmqctl list_queues name messages | grep dlq
RabbitMQ crashes/restarts repeatedly
Memory or Erlang cookie mismatch — if cluster node cookies don't match will cause errors
# Check logs
docker-compose logs rabbitmq --tail=50
# Verify Erlang cookie matches across all 3 nodes
docker-compose exec rabbitmq cat /var/lib/rabbitmq/.erlang.cookie
docker-compose exec rabbitmq2 cat /var/lib/rabbitmq/.erlang.cookie
# If mismatch, reset (WARNING: will lose data)
docker-compose down -v
docker-compose up -d
Frequently Asked Questions
What is the difference between RabbitMQ and Redis for messaging?
Redis stores messages in memory — fast but data is lost on restart. RabbitMQ stores to disk — slightly slower but persistent and reliable. For short-term caching use Redis, for mission-critical queues use RabbitMQ. Both can coexist in the same architecture (Redis for sessions/cache, RabbitMQ for async tasks).
How do I set Message TTL (Time To Live)?
Set x-message-ttl in queue arguments as milliseconds — RabbitMQ will expire messages after the timeout. Example: x-message-ttl: 3600000 = 1 hour. If a message is not consumed within that time it will be automatically deleted, preventing queue growth from old messages.
Can producers/consumers connect directly to RabbitMQ IP:port?
Yes, but not recommended for production — use HAProxy or a load balancer in front of the RabbitMQ cluster so clients don't need to know which node to connect to, and failover happens automatically. HAProxy backend for AMQP must use tcp mode, not http mode since AMQP uses a binary protocol.