Streaming & Messaging AWS

LavinMQ Message Broker 2.10.4 on AWS User Guide

| Product: LavinMQ Message Broker 2.10.4 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the LavinMQ Message Broker 2.10.4 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg streaming and messaging listing on AWS Marketplace; you pick it as the software version when you subscribe.

LavinMQ is an open source (Apache-2.0) message broker that speaks AMQP 0-9-1, the protocol RabbitMQ clients use. Producers publish messages to exchanges, the broker routes them into queues, and consumers read from the queues. Any AMQP 0-9-1 client library works with it: pika (Python), amqplib (Node.js), the RabbitMQ Java and .NET clients, Bunny (Ruby) and others.

What the cloudimg image adds:

  • AMQP over TLS only. Clients on the network reach the broker on 5671 (AMQPS: TLS 1.3 or 1.2 with modern AEAD cipher suites only). Plain AMQP (5672), MQTT (1883 and 8883) and the plain HTTP console port are switched off in LavinMQ's own configuration, so nothing answers on them even if a security group allows them (Step 10).
  • A certificate made on your instance. At first boot the broker gets a TLS key and an end-entity certificate for this instance's own names and addresses. It can only vouch for this broker and can never sign other certificates. You pin it in your clients (Step 5), or replace it with one from your own CA or Let's Encrypt (Step 12).
  • No default account. LavinMQ's built-in guest user is never created. First boot generates two logins unique to your instance: cloudimg-admin, the administrator, which works the console and HTTP API but can use AMQP only from the instance itself; and cloudimg-app, an application user limited to the virtual host cloudimg-app and to queues and exchanges whose names start with app..
  • A broker that refuses to run unsafely. Before every start a configuration gate refuses an unsafe change (a plain-text listener, a console bound to the network, guest, a weak or missing password hash, clustering). After every start a guard checks the running broker (users, listeners, anonymous and guest logins) and stops it if anything is wrong. If first boot cannot finish, the broker never serves.
  • The management console over HTTPS. The console listens on 127.0.0.1 only. nginx serves it on 443 with the same certificate, refuses requests for host names that are not this instance's, and blocks cross-site requests.
  • No telemetry, and no access to the instance metadata service. LavinMQ has no telemetry and its metrics port is off. The broker, nginx and the boot service run with the EC2 instance metadata address blocked (Step 11).
  • A pinned supply chain. LavinMQ 2.10.4 comes from its vendor's signed packagecloud apt repository, with the repository key pinned by fingerprint and the package version held. Ubuntu 24.04 LTS is fully patched at build time.

What is on the image: LavinMQ 2.10.4 (the lavinmq package; data in /var/lib/lavinmq, configuration in /etc/lavinmq/lavinmq.ini), nginx as the HTTPS front door of the console, and the cloudimg helpers lavinmq-probe (an AMQP test client), lavinmq-selfcheck, lavinmq-set-hostname, lavinmq-install-cert and a lavinmqctl wrapper.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows:
Port Protocol Source Purpose
22 TCP your admin network SSH
443 TCP your admin network the management console and HTTP API (HTTPS)
5671 TCP the networks your applications connect from AMQP over TLS (AMQPS)

Nothing else needs to be open. Ports 5672, 15672, 1883 and 80 are not served by the broker, so opening them achieves nothing.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick LavinMQ Message Broker 2.10.4 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (or larger for many connections or high message rates), your key pair and a security group as above.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=lavinmq-broker}]'

The image requires IMDSv2 (session tokens). First boot takes under a minute after the instance is running: the boot service reads the instance's EC2 names, creates the logins and the certificate, then the broker starts and the guard checks it.

Step 2: Connect to your instance

Version SSH user
LavinMQ Message Broker 2.10.4 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

The login banner shows where the logins and the certificate are.

Step 3: Check the services and the listening set

systemctl is-active lavinmq-metadata-names lavinmq-firstboot lavinmq nginx
sudo ss -tlnp | grep -E ':(22|80|443|1883|5671|5672|8883|15672) '
sudo ss -ulnp | grep -E 'lavinmq|nginx' || echo 'no UDP socket'
active
active
active
active
LISTEN 0      128        127.0.0.1:15672      0.0.0.0:*    users:(("lavinmq",pid=1070,fd=17))
LISTEN 0      511          0.0.0.0:443        0.0.0.0:*    users:(("nginx",pid=1073,fd=5),("nginx",pid=1072,fd=5),("nginx",pid=1071,fd=5))
LISTEN 0      4096         0.0.0.0:22         0.0.0.0:*    users:(("sshd",pid=1144,fd=3),("systemd",pid=1,fd=57))
LISTEN 0      128          0.0.0.0:5671       0.0.0.0:*    users:(("lavinmq",pid=1070,fd=16))
LISTEN 0      4096            [::]:22            [::]:*    users:(("sshd",pid=1144,fd=4),("systemd",pid=1,fd=58))
no UDP socket

lavinmq-metadata-names reads this instance's EC2 names for the certificate, lavinmq-firstboot prepares the logins and the certificate, lavinmq is the broker (AMQPS on 5671 for everyone, its console backend on 127.0.0.1:15672 for nginx only), and nginx serves the console on 443.

Step 4: Your endpoint and logins

Everything generated for your instance is in /root/lavinmq-credentials.txt (readable by root only). View it all with sudo cat /root/lavinmq-credentials.txt. The lines that are not secret are:

sudo grep -E '^LAVINMQ_(HOST|AMQPS_ENDPOINT|AMQPS_PORT|MGMT_URL|PRIVATE_IP|ADMIN_USER|APP_USER|APP_VHOST|APP_PREFIX|TLS_CERT_FILE|TLS_CERT_SHA256|TLS_PUBKEY_SHA256)=' /root/lavinmq-credentials.txt
LAVINMQ_HOST=44.212.4.95
LAVINMQ_AMQPS_ENDPOINT=amqps://44.212.4.95:5671
LAVINMQ_AMQPS_PORT=5671
LAVINMQ_MGMT_URL=https://44.212.4.95/
LAVINMQ_PRIVATE_IP=172.31.82.141
LAVINMQ_ADMIN_USER=cloudimg-admin
LAVINMQ_APP_USER=cloudimg-app
LAVINMQ_APP_VHOST=cloudimg-app
LAVINMQ_APP_PREFIX=app.
LAVINMQ_TLS_CERT_FILE=/etc/lavinmq/tls/lavinmq.crt
LAVINMQ_TLS_CERT_SHA256=8F:BB:1F:9D:6B:ED:3C:C3:87:4B:CE:5C:F2:9B:09:89:F2:AA:33:E3:18:89:DB:E4:60:20:66:FF:E3:77:E1:F6
LAVINMQ_TLS_PUBKEY_SHA256=286c5846075aa06fbd14e3b2248d999851645c70c587d0d6059c629cd3291f91

The file also holds LAVINMQ_ADMIN_PASSWORD and LAVINMQ_APP_PASSWORD. Give applications the cloudimg-app login or a scoped user of their own (Step 8), never the administrator's. Keep passwords in files with mode 0600, not in shell history or on command lines (a command line is visible to every user on a machine through ps).

Step 5: Check the certificate and pin it in your client

The broker's certificate is self-signed, made on your instance:

sudo openssl x509 -in /etc/lavinmq/tls/lavinmq.crt -noout -subject -enddate -ext subjectAltName,basicConstraints
sudo openssl x509 -in /etc/lavinmq/tls/lavinmq.crt -noout -fingerprint -sha256

CA:FALSE means it is an end-entity certificate that cannot sign anything else. The SHA-256 fingerprint is the LAVINMQ_TLS_CERT_SHA256 value in your credentials file; the same certificate is served on 5671 and on 443. Copy the certificate (it is public; the private key never leaves the instance) to each client machine:

client-host$ ssh -i <your-key.pem> ubuntu@<public-ip> 'sudo cat /etc/lavinmq/tls/lavinmq.crt' > lavinmq.crt
client-host$ openssl x509 -in lavinmq.crt -noout -fingerprint -sha256

Compare the fingerprint with LAVINMQ_TLS_CERT_SHA256 before you trust the file. Then configure your client to trust only this file (its CA file or trust store), with host name checking on. Your client will then refuse any other server, including one in the middle that presents a different certificate.

The certificate covers the instance's public and private IPv4 addresses and EC2 DNS names, localhost and 127.0.0.1. An EC2 stop and start gives the instance a new public IP. At the next boot the certificate is re-issued for the new names with the same key: clients that pin the public key (LAVINMQ_TLS_PUBKEY_SHA256) keep working, while clients that pin the certificate file need the new copy.

Step 6: Test on the instance

lavinmq-probe is a small AMQP client. It reads passwords from a file (or from the credentials file with --identity) and trusts only the broker's certificate:

sudo lavinmq-probe connect --identity app
sudo lavinmq-probe roundtrip --identity app --queue app.guide-test
sudo lavinmq-probe declare --identity app --queue orders || true
sudo lavinmq-probe connect --identity app --vhost / || true
OPEN ok vhost=cloudimg-app tls=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384
ROUNDTRIP OK queue=app.guide-test bytes=55 tls=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384
CHANNEL-CLOSE 403 ACCESS_REFUSED - User 'cloudimg-app' doesn't have permissions to queue 'orders'
REFUSED 530 NOT_ALLOWED - 'cloudimg-app' doesn't have access to '/'

The application user logs in, declares a queue named app.guide-test, publishes a message and reads it back. It is refused a queue outside app.* and the / virtual host (lavinmq-probe exits non-zero on a refusal, hence || true).

Step 7: Connect a client from another machine

On the client machine, put the application password in a file that only you can read, and copy the certificate as in Step 5:

client-host$ umask 077
client-host$ ssh -i <your-key.pem> ubuntu@<public-ip> "sudo sed -n 's/^LAVINMQ_APP_PASSWORD=//p' /root/lavinmq-credentials.txt" > app.password

This example uses Python and pika (sudo apt-get install python3-pika on Ubuntu, or pip install pika):

# pika_demo.py - publish and consume through your cloudimg LavinMQ broker over AMQPS (pika 1.x).
# Usage: python3 pika_demo.py <broker-host> <lavinmq.crt> <user> <password-file> <vhost> <queue>
# The password is read from a file, never the command line; TLS trusts ONLY the broker's own certificate.
import ssl
import sys

import pika

host, cafile, user, pwfile, vhost, queue = sys.argv[1:7]
with open(pwfile) as f:
    password = f.read().strip()

ctx = ssl.create_default_context(cafile=cafile)      # verify against this one certificate, nothing else
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
params = pika.ConnectionParameters(
    host=host, port=5671, virtual_host=vhost,
    credentials=pika.PlainCredentials(user, password),
    ssl_options=pika.SSLOptions(ctx, server_hostname=host),
)
conn = pika.BlockingConnection(params)
ch = conn.channel()
ch.queue_declare(queue=queue, durable=True)
ch.basic_publish(exchange="", routing_key=queue, body=b"hello from pika",
                 properties=pika.BasicProperties(delivery_mode=2))
method, props, body = ch.basic_get(queue=queue, auto_ack=True)
print("received:", body.decode())
conn.close()
client-host$ python3 pika_demo.py <public-ip> lavinmq.crt cloudimg-app app.password cloudimg-app app.pika-demo
received: hello from pika

Use the name the certificate covers as <broker-host>: the public or private IP, an EC2 DNS name, or your own DNS name once you have added it (Step 12). The AMQP URL form is amqps://cloudimg-app:<password>@<public-ip>:5671/cloudimg-app. Most libraries accept it, but build it inside your program from the password file rather than writing it in a configuration file or on a command line.

Step 8: Management over HTTPS, and scoped users and virtual hosts

Open https://<public-ip>/ in a browser and sign in as cloudimg-admin with LAVINMQ_ADMIN_PASSWORD. The certificate is self-signed, so the browser warns until you trust lavinmq.crt or install a CA certificate (Step 12).

The LavinMQ management console sign-in page served over HTTPS by nginx

The console overview after signing in as cloudimg-admin: connections, channels, queues and message rates

The Queues page listing a durable queue named app.pika-demo in the cloudimg-app virtual host

The Virtual hosts page showing the default virtual host and cloudimg-app

Give each application its own user, limited to its own virtual host and name prefix, rather than sharing one login. You can do it in the console (Virtual hosts, then Users), or with the HTTP API as below. Do not use lavinmqctl add_user or change_password: they take the password as a command-line argument, which other users on the instance can see with ps and which is kept in your shell history.

On the instance, keep the administrator login in a curl configuration file that only you can read, and copy the certificate where your user can read it:

umask 077
sudo cat /etc/lavinmq/tls/lavinmq.crt > ~/lavinmq.crt
printf 'user = "cloudimg-admin:%s"\n' "$(sudo sed -n 's/^LAVINMQ_ADMIN_PASSWORD=//p' /root/lavinmq-credentials.txt)" > ~/.lavinmq-admin.curl
api() { curl -sS --cacert ~/lavinmq.crt -K ~/.lavinmq-admin.curl -H 'Content-Type: application/json' "$@"; }
api -o /dev/null -w '%{http_code}\n' https://localhost/api/overview
200

Each block below defines the same small api helper, so you can run them one at a time. Create a virtual host orders, a user orders-svc with a random password kept in a 0600 file, and permissions limited to names starting with orders.:

umask 077
api() { curl -sS --cacert ~/lavinmq.crt -K ~/.lavinmq-admin.curl -H 'Content-Type: application/json' "$@"; }
api -o /dev/null -w '%{http_code}\n' -X PUT https://localhost/api/vhosts/orders
openssl rand -base64 30 | tr -d '/+=\n' | head -c 32 > ~/orders-svc.password
python3 -c 'import json,sys; print(json.dumps({"password": open(sys.argv[1]).read().strip(), "tags": ""}))' ~/orders-svc.password > ~/orders-svc.json
api -o /dev/null -w '%{http_code}\n' -X PUT --data @"$HOME/orders-svc.json" https://localhost/api/users/orders-svc
shred -u ~/orders-svc.json
api -o /dev/null -w '%{http_code}\n' -X PUT --data '{"configure":"^orders\\.","write":"^(orders\\..*|amq\\.default)?$","read":"^orders\\."}' https://localhost/api/permissions/orders/orders-svc
201
201
201

The write pattern also allows amq.default, the default exchange, so the application can publish straight to its own queues by name. Try the new user:

sudo lavinmq-probe roundtrip --user orders-svc --password-file ~/orders-svc.password --vhost orders --queue orders.created
sudo lavinmq-probe declare --user orders-svc --password-file ~/orders-svc.password --vhost orders --queue app.x || true
sudo lavinmq-probe connect --user orders-svc --password-file ~/orders-svc.password --vhost cloudimg-app || true
ROUNDTRIP OK queue=orders.created bytes=55 tls=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384
CHANNEL-CLOSE 403 ACCESS_REFUSED - User 'orders-svc' doesn't have permissions to queue 'app.x'
REFUSED 530 NOT_ALLOWED - 'orders-svc' doesn't have access to 'cloudimg-app'

Hand ~/orders-svc.password to the application's secret store, then delete it with shred -u ~/orders-svc.password. To remove the example again:

api() { curl -sS --cacert ~/lavinmq.crt -K ~/.lavinmq-admin.curl -H 'Content-Type: application/json' "$@"; }
api -o /dev/null -w '%{http_code}\n' -X DELETE https://localhost/api/users/orders-svc
api -o /dev/null -w '%{http_code}\n' -X DELETE https://localhost/api/vhosts/orders
shred -u ~/orders-svc.password

Rules the broker enforces whatever you change: no user may be called guest, no user may have the password guest or an empty password, and cloudimg-admin can use AMQP only from the instance itself (default_user_only_loopback). From another machine, manage the broker over HTTPS on 443 with the console or the API (https://<public-ip>/api/... with --cacert lavinmq.crt).

Step 9: The broker refuses to run unsafely

lavinmq.service runs lavinmq-config-gate before every start and lavinmq-guard after it. Check both, and the whole instance, at any time:

sudo lavinmq-config-gate --unit && echo gate-ok
sudo lavinmq-selfcheck | tail -n 1
lavinmq-config-gate: OK (unit)
gate-ok
SELFCHECK OK

If you edit /etc/lavinmq/lavinmq.ini and turn on a plain-text listener (amqp_port, mqtt_port or a non-loopback http_port), enable guest, or remove the per-instance administrator hash, the gate refuses the change and the broker does not start: journalctl -u lavinmq shows the reason. If a user called guest, or a user whose password is guest, ever appears in the running broker, the guard stops it. lavinmq-selfcheck prints one ok: line per area (units, gate and guard, the listening set, the certificate, round trips, refusals, the console, the metadata block) and ends with SELFCHECK OK.

Step 10: Why plain-text 5672, 15672 and 1883 are closed

LavinMQ by default listens for plain AMQP on 5672, MQTT on 1883 and the HTTP console on 15672, all without TLS, and creates guest / guest. On a cloud instance that means passwords and messages in clear text, and a well-known login. This image switches those listeners off in LavinMQ's own configuration rather than relying on a firewall:

  • 5672 (AMQP): off. Use AMQPS on 5671; every maintained AMQP 0-9-1 client supports TLS.
  • 15672 (HTTP console and API): bound to 127.0.0.1 for nginx only. Use https://<host>/ on 443.
  • 1883 / 8883 (MQTT): off. This image is configured as an AMQP broker.

From another machine a connection to 5672, 15672, 1883, 8883, 15671, 15692 or 80 is refused even if your security group allows it.

Step 11: The instance metadata block

The EC2 instance metadata service (169.254.169.254) hands out the instance's IAM role credentials to any process that asks. A broker or web server that could be tricked into fetching a URL must not reach it. lavinmq, nginx and lavinmq-firstboot run with the metadata addresses blocked by systemd (IPAddressDeny). Only lavinmq-metadata-names, which reads the instance's names for the certificate, may reach the metadata service, and it may reach nothing else:

for u in lavinmq nginx lavinmq-firstboot lavinmq-metadata-names; do printf '%-24s deny=%s allow=%s\n' "$u" "$(systemctl show -p IPAddressDeny --value "$u")" "$(systemctl show -p IPAddressAllow --value "$u")"; done

lavinmq-selfcheck proves the block from inside the broker's own control group, where a request to the metadata service gets no answer.

Step 12: Use your own DNS name or a certificate from your CA

Point a DNS name at the instance (an Elastic IP keeps the address across stop and start), then add the name to the certificate:

sudo lavinmq-set-hostname <dns-name>

This re-issues the self-signed certificate with the same key to cover the name, pins nginx to it, updates the endpoint in /root/lavinmq-credentials.txt and restarts the broker. Copy the new lavinmq.crt to clients that pin the certificate file.

To use a certificate from your CA (your own, or a public one such as Let's Encrypt), copy the chain (server certificate first) and its unencrypted PEM key to the instance, then run:

sudo lavinmq-install-cert <certificate-chain.pem> <private-key.pem> <dns-name>

Both endpoints (5671 and 443) then serve your certificate, and clients trust your CA instead of pinning this instance. The boot-time renewal never replaces an installed certificate. sudo lavinmq-set-hostname <name> goes back to a new self-signed certificate.

Logs, files and limits

  • Broker log: journalctl -u lavinmq; console front door: journalctl -u nginx and /var/log/nginx/; first boot: journalctl -u lavinmq-firstboot.
  • Configuration: /etc/lavinmq/lavinmq.ini (checked by the gate on every start). Data: /var/lib/lavinmq. Certificate and key: /etc/lavinmq/tls/.
  • The lavinmqctl wrapper talks to the broker over its control socket: sudo lavinmqctl list_users, sudo lavinmqctl list_vhosts, sudo lavinmqctl list_queues -p cloudimg-app.
  • The broker runs as the unprivileged lavinmq user in a systemd sandbox (no capabilities, a read-only system, a system-call filter, core dumps off).
  • Messages are kept on the instance's EBS volume. Size the volume and the instance for your message volume, and take EBS snapshots for backups.

Support

cloudimg provides support for the image: launch problems, the first-boot setup, the TLS configuration and the cloudimg helpers. Contact support@cloudimg.co.uk. LavinMQ itself is documented at lavinmq.com/documentation.