Skip to content

TLS Configuration

Secure RabbitMQ connections with TLS encryption and client certificates.

CertificatePurpose
CA CertificateRoot of trust
Server CertificateRabbitMQ server identity
Client CertificateClient authentication — required: the shipped broker config sets ssl_options.fail_if_no_peer_cert = true

One rule, stated in three places and the same in all of them: the broker demands a client certificate. The Ansible role renders it (rabbitmq.conf.j2), RabbitMQ Setup says what that means for Mandible and Thorax, and Manual Installation builds the broker by hand with the same setting.

Terminal window
# Create CA directory
mkdir -p /etc/rabbitmq/certs
cd /etc/rabbitmq/certs
# Generate CA key
openssl genrsa -out ca-key.pem 4096
# Generate CA certificate
openssl req -x509 -new -nodes \
-key ca-key.pem \
-sha256 -days 3650 \
-out ca-cert.pem \
-subj "/CN=Mantis RabbitMQ CA/O=Mantis"
Terminal window
# Generate server key
openssl genrsa -out server-key.pem 2048
# Create certificate signing request
openssl req -new \
-key server-key.pem \
-out server-cert.csr \
-subj "/CN=rabbitmq.example.com/O=Mantis"
# Create SAN extension file
cat > server_san.ext << EOF
subjectAltName = @alt_names
[alt_names]
DNS.1 = rabbitmq.example.com
DNS.2 = localhost
IP.1 = 127.0.0.1
EOF
# Sign with CA
openssl x509 -req \
-in server-cert.csr \
-CA ca-cert.pem \
-CAkey ca-key.pem \
-CAcreateserial \
-out server-cert.pem \
-days 365 \
-sha256 \
-extfile server_san.ext
Terminal window
# Generate client key
openssl genrsa -out rabbitmq-client-key.pem 2048
# Create CSR
openssl req -new \
-key rabbitmq-client-key.pem \
-out rabbitmq-client.csr \
-subj "/CN=mantis-client/O=Mantis"
# Sign with CA
openssl x509 -req \
-in rabbitmq-client.csr \
-CA ca-cert.pem \
-CAkey ca-key.pem \
-CAcreateserial \
-out rabbitmq-client-cert.pem \
-days 365 \
-sha256
Terminal window
# Secure the keys
chmod 600 /etc/rabbitmq/certs/*-key.pem
chmod 644 /etc/rabbitmq/certs/*-cert.pem
chown -R rabbitmq:rabbitmq /etc/rabbitmq/certs
/etc/rabbitmq/rabbitmq.conf
# Plain TCP is disabled -- Mantis requires TLS and has no option to use a
# plain listener, and the shipped broker config hard-disables it.
listeners.tcp = none
# Enable TLS listener
listeners.ssl.default = 5671
# Certificate configuration
ssl_options.cacertfile = /etc/rabbitmq/certs/ca-cert.pem
ssl_options.certfile = /etc/rabbitmq/certs/server-cert.pem
ssl_options.keyfile = /etc/rabbitmq/certs/server-key.pem
# TLS version (require TLS 1.2+)
ssl_options.versions.1 = tlsv1.2
ssl_options.versions.2 = tlsv1.3
# Cipher suites
ssl_options.ciphers.1 = TLS_AES_256_GCM_SHA384
ssl_options.ciphers.2 = TLS_AES_128_GCM_SHA256
ssl_options.ciphers.3 = ECDHE-RSA-AES256-GCM-SHA384
ssl_options.ciphers.4 = ECDHE-RSA-AES128-GCM-SHA256
# Honor server cipher preference
ssl_options.honor_cipher_order = true
ssl_options.honor_ecc_order = true
/etc/rabbitmq/rabbitmq.conf
# Require client certificates
ssl_options.verify = verify_peer
ssl_options.fail_if_no_peer_cert = true
# Depth of certificate chain verification
ssl_options.depth = 2

Mantis cannot use a plain-TCP listener: the queue client hard-requires TLS (instinct/src/queue/config.rs, “TLS is mandatory… no option to disable TLS”), and the shipped broker config disables the TCP listener. Do not enable a plain listener for Mantis traffic.

/etc/rabbitmq/rabbitmq.conf
# HTTPS only. The plain listener is simply not configured -- there is no
# "disable" value for management.tcp.port, and 0 would request an ephemeral
# port, i.e. an unpredictable open HTTP management port rather than none.
# Enable HTTPS
management.ssl.port = 15671
management.ssl.cacertfile = /etc/rabbitmq/certs/ca-cert.pem
management.ssl.certfile = /etc/rabbitmq/certs/server-cert.pem
management.ssl.keyfile = /etc/rabbitmq/certs/server-key.pem

client_cert_path and client_key_path are not optional against the broker this repo ships: it sets ssl_options.fail_if_no_peer_cert = true, so a connection presenting no client certificate is rejected at the AMQPS handshake.

thorax.toml
[queue]
enabled = true
host = "rabbitmq.example.com"
port = 5671
vhost = "/mantis"
username = "mantis"
password = "<the mantis broker user's password>"
ca_cert_path = "/etc/mantis/certs/ca-cert.pem"
client_cert_path = "/etc/mantis/certs/rabbitmq-client-cert.pem"
client_key_path = "/etc/mantis/certs/rabbitmq-client-key.pem"
prefetch_count = 10
Terminal window
THORAX__QUEUE__HOST="rabbitmq.example.com"
THORAX__QUEUE__PORT=5671
THORAX__QUEUE__USERNAME="mantis"
THORAX__QUEUE__CA_CERT_PATH="/etc/mantis/certs/ca-cert.pem"

Mandible publishes commands to RabbitMQ and uses the same [queue] settings block with the MANDIBLE__ env var prefix.

The same rule applies here — both client-certificate paths must be set.

mandible.toml
[queue]
enabled = true
host = "rabbitmq.example.com"
port = 5671
vhost = "/mantis"
username = "mantis"
password = "<the mantis broker user's password>"
ca_cert_path = "/etc/mantis/certs/ca-cert.pem"
client_cert_path = "/etc/mantis/certs/rabbitmq-client-cert.pem"
client_key_path = "/etc/mantis/certs/rabbitmq-client-key.pem"
Terminal window
MANDIBLE__QUEUE__HOST="rabbitmq.example.com"
MANDIBLE__QUEUE__PORT=5671
MANDIBLE__QUEUE__USERNAME="mantis"
MANDIBLE__QUEUE__CA_CERT_PATH="/etc/mantis/certs/ca-cert.pem"
services:
rabbitmq:
image: rabbitmq:4.3.6-management
hostname: rabbitmq
volumes:
- ./certs/ca-cert.pem:/etc/rabbitmq/certs/ca-cert.pem:ro
- ./certs/server-cert.pem:/etc/rabbitmq/certs/server-cert.pem:ro
- ./certs/server-key.pem:/etc/rabbitmq/certs/server-key.pem:ro
- ./rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro
# Its own uid, so the entrypoint does not start as root and drop privileges --
# that drop discards group_add and leaves the group-readable key unreadable.
user: '999:999'
group_add:
- '${MANTIS_CERT_GID:-1000}'
ports:
- '5671:5671' # AMQPS
- '15671:15671' # Management HTTPS
apiVersion: v1
kind: Secret
metadata:
name: rabbitmq-tls
type: Opaque
data:
ca-cert.pem: <base64-encoded-ca>
server-cert.pem: <base64-encoded-cert>
server-key.pem: <base64-encoded-key>
Terminal window
# Using OpenSSL. The broker sets fail_if_no_peer_cert, so a client certificate
# is required for the handshake to complete.
openssl s_client -connect rabbitmq.example.com:5671 \
-CAfile /etc/mantis/certs/ca-cert.pem \
-cert /etc/mantis/certs/rabbitmq-client-cert.pem \
-key /etc/mantis/certs/rabbitmq-client-key.pem \
-showcerts
# Check certificate
openssl s_client -connect rabbitmq.example.com:5671 \
-CAfile /etc/mantis/certs/ca-cert.pem \
-cert /etc/mantis/certs/rabbitmq-client-cert.pem \
-key /etc/mantis/certs/rabbitmq-client-key.pem \
</dev/null 2>/dev/null | openssl x509 -text -noout
Terminal window
# Using amqp-tools
amqp-declare-queue \
--url="amqps://mantis:password@rabbitmq.example.com:5671" \
--cacert=/etc/mantis/certs/ca-cert.pem \
--cert=/etc/mantis/certs/rabbitmq-client-cert.pem \
--key=/etc/mantis/certs/rabbitmq-client-key.pem \
--queue=test
# Using Python
python3 -c "
import ssl
import pika
context = ssl.create_default_context(cafile='/etc/mantis/certs/ca-cert.pem')
context.load_cert_chain('/etc/mantis/certs/rabbitmq-client-cert.pem', '/etc/mantis/certs/rabbitmq-client-key.pem')
params = pika.ConnectionParameters(
host='rabbitmq.example.com',
port=5671,
credentials=pika.PlainCredentials('mantis', 'password'),
ssl_options=pika.SSLOptions(context)
)
connection = pika.BlockingConnection(params)
print('Connected successfully')
connection.close()
"
Terminal window
# List TLS listeners
sudo rabbitmqctl eval 'rabbit_networking:active_listeners().'
# Check connection TLS info
sudo rabbitmqctl list_connections name ssl ssl_protocol ssl_cipher
/opt/mantis/scripts/renew-rabbitmq-certs.sh
#!/bin/bash
SSL_DIR="/etc/rabbitmq/certs"
DAYS_BEFORE_EXPIRY=30
# Check if certificate needs renewal
EXPIRY=$(openssl x509 -enddate -noout -in $SSL_DIR/server-cert.pem | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
if [ $DAYS_LEFT -lt $DAYS_BEFORE_EXPIRY ]; then
echo "Certificate expires in $DAYS_LEFT days, renewing..."
# Generate new certificate
openssl req -new -key $SSL_DIR/server-key.pem -out $SSL_DIR/server-cert.csr \
-subj "/CN=rabbitmq.example.com/O=Mantis"
openssl x509 -req -in $SSL_DIR/server-cert.csr \
-CA $SSL_DIR/ca-cert.pem -CAkey $SSL_DIR/ca-key.pem \
-CAcreateserial -out $SSL_DIR/server-cert.pem \
-days 365 -sha256 -extfile $SSL_DIR/server_san.ext
# Reload RabbitMQ
sudo systemctl reload rabbitmq-server
echo "Certificate renewed successfully"
else
echo "Certificate valid for $DAYS_LEFT more days"
fi
Terminal window
# Weekly check
0 0 * * 0 /opt/mantis/scripts/renew-rabbitmq-certs.sh >> /var/log/rabbitmq/cert-renewal.log 2>&1
Error: TLS handshake failed

Causes:

  1. Certificate hostname mismatch
  2. Expired certificate
  3. CA certificate not trusted

Solutions:

  1. Verify SAN includes hostname: openssl x509 -in server-cert.pem -text | grep DNS
  2. Check expiry: openssl x509 -in server-cert.pem -enddate -noout
  3. Verify CA is correct
Error: certificate verify failed

Solutions:

  1. Ensure CA certificate is correct
  2. Check certificate chain is complete
  3. Verify verify_peer settings match on both sides
Error: connection reset by peer

Causes:

  1. TLS version mismatch
  2. Cipher suite incompatibility

Solutions:

  1. Check TLS versions match
  2. Verify cipher suites are compatible