Skip to Content

Production Guide: Deploy RabbitMQ with Docker Compose + Caddy on Ubuntu

Secure, persistent, and production-ready messaging with TLS, health checks, and operational validation.

Running event-driven workloads in production usually fails for one of two reasons: either the broker is deployed quickly but left without durable storage and guardrails, or it gets over-engineered so early that small teams cannot operate it confidently. This guide gives you a practical middle path for deploying RabbitMQ on Ubuntu with Docker Compose and Caddy, including TLS, predictable restarts, secrets handling, health checks, and a verification checklist you can hand to operations. The end result is a resilient messaging endpoint you can use for background jobs, async APIs, webhook fan-out, and service decoupling without turning your stack into a science project.

We will deploy RabbitMQ 3 management image behind Caddy as a secure reverse proxy. RabbitMQ will keep data on persistent volumes, run on an isolated Docker network, and expose only what is needed. Caddy handles HTTPS certificates automatically and keeps proxy configuration clean and repeatable. You also get command-level checks to validate queue durability, management access, publish/consume flow, and restart behavior.

Architecture and flow overview

Traffic path: Client apps β†’ HTTPS (Caddy) β†’ RabbitMQ management UI/API and AMQP service.

Data path: RabbitMQ writes node state and queue messages to persistent Docker volumes mounted under /var/lib/docker/volumes.

Security path: TLS termination at Caddy, strong RabbitMQ credentials in .env, admin UI access via HTTPS only, and optional firewall restrictions for AMQP clients.

Operational model: Compose defines the full stack declaratively; systemd manages boot-time recovery; health checks provide quick drift detection.

# Logical components
# 1) caddy      : reverse proxy + automatic TLS
# 2) rabbitmq   : broker + management plugin
# 3) volumes    : durable state (mnesia/log)
# 4) network    : isolated bridge between caddy and rabbitmq

Copy button not working? Select the command block and copy manually.

Prerequisites

  • Ubuntu 22.04/24.04 host with sudo access
  • A DNS record (for example mq.example.com) pointing to the server public IP
  • Docker Engine + Docker Compose plugin installed
  • Ports 80 and 443 open for HTTPS issuance and traffic
  • If external AMQP clients connect directly, port 5672 reachable from trusted networks only
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg ufw

# Verify Docker
docker --version
docker compose version

# Optional baseline firewall
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Allow AMQP only from private CIDR (example)
# sudo ufw allow from 10.0.0.0/8 to any port 5672 proto tcp
sudo ufw enable

Copy button not working? Select the command block and copy manually.

Step-by-step deployment

Step 1: Prepare directories and permissions

Keep stack files under a dedicated path so backups, reviews, and updates are straightforward.

sudo mkdir -p /opt/rabbitmq/{compose,caddy,backups}
sudo chown -R $USER:$USER /opt/rabbitmq
cd /opt/rabbitmq/compose

Copy button not working? Select the command block and copy manually.

Step 2: Create environment file for secrets and runtime values

Store credentials outside Compose YAML. Generate long random values and rotate periodically.

cat > /opt/rabbitmq/compose/.env <<'EOF'
RABBITMQ_DEFAULT_USER=rmqadmin
RABBITMQ_DEFAULT_PASS=replace-with-strong-password
RABBITMQ_ERLANG_COOKIE=replace-with-long-random-cookie
RABBITMQ_HOST=mq.example.com
EOF

chmod 600 /opt/rabbitmq/compose/.env

Copy button not working? Select the command block and copy manually.

Step 3: Write Caddyfile and Docker Compose stack

Caddy proxies management UI over HTTPS while RabbitMQ runs internally on the Compose network. AMQP can remain internal for app containers, or be bound to host if needed for external producers/consumers.

cat > /opt/rabbitmq/compose/Caddyfile <<'EOF'
{$RABBITMQ_HOST} {
    encode gzip zstd
    reverse_proxy rabbitmq:15672
    header {
      Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
      X-Content-Type-Options "nosniff"
      X-Frame-Options "SAMEORIGIN"
      Referrer-Policy "strict-origin-when-cross-origin"
    }
}
EOF

cat > /opt/rabbitmq/compose/docker-compose.yml <<'EOF'
services:
  rabbitmq:
    image: rabbitmq:3.13-management
    container_name: rabbitmq
    restart: unless-stopped
    env_file:
      - .env
    environment:
      RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
      RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
      RABBITMQ_ERLANG_COOKIE: ${RABBITMQ_ERLANG_COOKIE}
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq
    networks:
      - edge
    ports:
      - "5672:5672"
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
      interval: 30s
      timeout: 10s
      retries: 5

  caddy:
    image: caddy:2.8
    container_name: rabbitmq-caddy
    restart: unless-stopped
    env_file:
      - .env
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      rabbitmq:
        condition: service_healthy
    networks:
      - edge

volumes:
  rabbitmq_data:
  caddy_data:
  caddy_config:

networks:
  edge:
    driver: bridge
EOF

Copy button not working? Select the command block and copy manually.

Step 4: Launch and validate base health

Bring up the stack and confirm both containers are healthy before testing application traffic.

cd /opt/rabbitmq/compose
docker compose pull
docker compose up -d

docker compose ps
docker logs rabbitmq --tail=80
docker logs rabbitmq-caddy --tail=80

Copy button not working? Select the command block and copy manually.

Step 5: Enable host reboot recovery with systemd

Compose restart policies are useful, but a dedicated systemd unit gives cleaner host lifecycle control and visibility in journalctl.

sudo tee /etc/systemd/system/rabbitmq-compose.service > /dev/null <<'EOF'
[Unit]
Description=RabbitMQ Compose Stack
Requires=docker.service
After=docker.service network-online.target

[Service]
Type=oneshot
WorkingDirectory=/opt/rabbitmq/compose
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
RemainAfterExit=yes
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable rabbitmq-compose.service
sudo systemctl start rabbitmq-compose.service
sudo systemctl status rabbitmq-compose.service --no-pager

Copy button not working? Select the command block and copy manually.

Configuration and secrets handling best practices

For production systems, treat broker credentials like application secrets, not configuration text. Use a secret manager if available; if not, harden file permissions and rotate on schedule.

  • Use a unique admin account, then create app-specific RabbitMQ users with least-privilege vhost permissions.
  • Avoid embedding credentials in CI logs or shell history; use non-interactive environment injection where possible.
  • Back up rabbitmq_data and test restore at least monthly.
  • Document queue TTL, dead-letter exchanges, and retention expectations per workload.
  • Enable monitoring from day one (queue depth, publish rate, consumer lag, disk alarms, memory watermark).
# Create an app user and least-privilege permissions

docker exec rabbitmq rabbitmqctl add_vhost app-vhost
docker exec rabbitmq rabbitmqctl add_user appuser 'replace-with-app-password'
docker exec rabbitmq rabbitmqctl set_permissions -p app-vhost appuser ".*" ".*" ".*"

# Rotate default admin password after first login

docker exec rabbitmq rabbitmqctl change_password rmqadmin 'new-strong-password'

Copy button not working? Select the command block and copy manually.

Verification checklist

Run these checks before declaring the deployment ready:

  1. TLS and UI: Open https://mq.example.com and log into RabbitMQ management successfully.
  2. Broker health: Cluster status returns without alarms.
  3. Publish/consume test: Create a test queue and verify end-to-end message flow.
  4. Restart test: Restart containers and confirm queue/data persistence.
  5. Boot test: Reboot host and ensure systemd brings stack back automatically.
# Broker/node checks
docker exec rabbitmq rabbitmq-diagnostics -q ping
docker exec rabbitmq rabbitmqctl status

# Optional quick queue declaration/publish test (inside container)
docker exec rabbitmq rabbitmqadmin declare queue name=hello durable=true
docker exec rabbitmq rabbitmqadmin publish routing_key=hello payload='it works'

docker compose restart rabbitmq
docker exec rabbitmq rabbitmqctl list_queues name messages durable

Copy button not working? Select the command block and copy manually.

Common issues and fixes

1) HTTPS certificate not issuing

Symptoms: Caddy logs ACME challenge failures.
Fix: Confirm DNS A/AAAA records, port 80 reachability, and no competing reverse proxy on the same host ports.

2) RabbitMQ container healthy=false

Symptoms: Compose shows unhealthy service.
Fix: Check docker logs rabbitmq for invalid Erlang cookie, malformed env values, or volume permission errors.

3) Messages disappear unexpectedly

Symptoms: Queue drains after restart.
Fix: Ensure queue is declared durable and publishers mark messages persistent when required by your durability policy.

4) Memory or disk alarms under load

Symptoms: Throughput drops and publishers are blocked.
Fix: Scale consumers, set queue TTL/dead-lettering, right-size host resources, and avoid unbounded backlog growth.

5) Clients cannot connect on 5672

Symptoms: Timeout/refused connections.
Fix: Validate firewall rules, confirm host port binding, and verify app credentials/vhost permissions.

FAQ

Should I expose RabbitMQ management directly to the internet?

You can, but only behind HTTPS and preferably with additional controls (IP allowlist, VPN, or SSO gateway). For stricter environments, keep management private and access through bastion/VPN.

Is Docker Compose enough for production RabbitMQ?

For single-node or moderate workloads, yesβ€”if you include persistent volumes, backups, monitoring, and restart automation. Multi-node high-availability clusters may justify Kubernetes or dedicated orchestration later.

How often should I rotate RabbitMQ credentials?

A practical baseline is every 60 to 90 days, plus immediate rotation after team/offboarding changes or suspected exposure. Rotate app users in phases to avoid downtime.

Do I need quorum queues from day one?

Not always. Start with workload-driven choices. Quorum queues improve resilience but have operational tradeoffs. Test with your traffic profile before broad rollout.

How do I back up RabbitMQ safely?

Back up persistent volume data and critical definitions (users, vhosts, policies). Validate restore procedures on a separate environment so backups are proven, not assumed.

What metrics matter most in early production?

Queue depth, publish/ack rates, consumer utilization, disk free alarms, memory watermark alarms, and connection/channel counts. Alert on sustained queue growth and blocked publishers.

Can I keep AMQP private while exposing only the web UI?

Yes. Keep port 5672 restricted to private networks or internal containers only, while Caddy exposes HTTPS management separately with strict access controls.

Related guides

Talk to us

If you want support designing or hardening your messaging platform, we can help with architecture, migration planning, and production readiness.

Contact Us

How to Deploy Grafana in Production with Docker Compose + systemd
A practical production guide with operational validation and recovery patterns.