Streaming & Messaging AWS

Mosquitto 2 MQTT Broker on AWS User Guide

| Product: Mosquitto 2 MQTT Broker on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Mosquitto 2 MQTT Broker 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.

Eclipse Mosquitto is an open source (EPL-2.0 or BSD-3-Clause) message broker for MQTT versions 5.0, 3.1.1 and 3.1, the publish/subscribe protocol used by IoT devices, sensors, home automation and mobile apps. Clients publish messages to topics (such as sensors/17/temperature) and the broker delivers them to every client subscribed to a matching topic filter (such as sensors/+/temperature or sensors/#). Any MQTT client works with it: the mosquitto_pub and mosquitto_sub command-line tools, Eclipse Paho (Python, Java, C, Go, JavaScript), MQTT.js, HiveMQ client libraries and others.

What the cloudimg image adds:

  • MQTT over TLS only. Clients on the network reach the broker on 8883 (MQTT over TLS 1.3 or 1.2 with modern AEAD cipher suites only). Mosquitto's plain-text port 1883 and WebSockets are not configured, 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 anonymous access and no default password. Every client logs in. First boot generates two logins unique to your instance: cloudimg-admin, the operator login, which may use every topic; and cloudimg-app, an application login that an access control list (ACL) confines to the app/ topic tree. Passwords are stored only as salted PBKDF2-SHA512 hashes.
  • Users and topic permissions you can manage safely. mosquitto-user adds a login with its password read from standard input (never a command line) and the topic filters it may use, checks the new files and reloads the broker without dropping connected clients (Step 8).
  • A broker that refuses to run unsafely. Before every start and every reload a configuration gate refuses an unsafe change (anonymous access, a plain-text or extra listener, WebSockets, TLS without a certificate, a missing password file or ACL, bridges, plugins). After every start a guard checks the running broker's sockets and logins and stops it if anything is wrong. If first boot cannot finish, the broker never serves.
  • No telemetry, and no access to the instance metadata service. Mosquitto has no telemetry, and this image configures no bridge. The broker and its boot service run with the EC2 instance metadata address blocked (Step 11).
  • A pinned supply chain. Mosquitto comes from the Eclipse Mosquitto project's signed Ubuntu package archive (the mosquitto-dev PPA), with the archive key pinned by fingerprint and the package versions held. Ubuntu 24.04 LTS is fully patched at build time.

What is on the image: Eclipse Mosquitto 2.1.2 (the mosquitto broker, the mosquitto-clients tools mosquitto_pub, mosquitto_sub and mosquitto_rr, and libmosquitto1; configuration in /etc/mosquitto/mosquitto.conf, logins and ACL in /etc/mosquitto/cloudimg/, data in /var/lib/mosquitto), and the cloudimg helpers mosquitto-probe (an MQTT test client), mosquitto-selfcheck, mosquitto-user, mosquitto-set-hostname, mosquitto-install-cert and mosquitto-config-gate.

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
8883 TCP the networks your MQTT clients connect from MQTT over TLS

Nothing else needs to be open. Ports 1883 (plain MQTT), 8080, 8081 and 9001 (WebSockets) 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 Mosquitto 2 MQTT Broker on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (or larger for many thousands of 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=mosquitto-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
Mosquitto 2 MQTT Broker 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 mosquitto-metadata-names mosquitto-firstboot mosquitto
sudo ss -tlnp | grep -E ':(22|80|443|1883|8080|8081|8883|9001) '
sudo ss -ulnp | grep mosquitto || echo 'no UDP socket'
active
active
active
LISTEN 0      100          0.0.0.0:8883      0.0.0.0:*    users:(("mosquitto",pid=1047,fd=5))
LISTEN 0      4096         0.0.0.0:22        0.0.0.0:*    users:(("sshd",pid=1189,fd=3),("systemd",pid=1,fd=59))
LISTEN 0      100             [::]:8883         [::]:*    users:(("mosquitto",pid=1047,fd=6))
LISTEN 0      4096            [::]:22           [::]:*    users:(("sshd",pid=1189,fd=4),("systemd",pid=1,fd=60))
no UDP socket

mosquitto-metadata-names reads this instance's EC2 names for the certificate, mosquitto-firstboot prepares the logins and the certificate, and mosquitto is the broker, listening on 8883 (IPv4 and IPv6) and nothing else.

Step 4: Your endpoint and logins

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

sudo grep -E '^MOSQUITTO_(HOST|MQTTS_URL|PORT|PRIVATE_IP|ADMIN_USER|APP_USER|APP_TOPICS|TLS_CA_FILE|TLS_CERT_SHA256|TLS_PUBKEY_SHA256)=' /root/mosquitto-credentials.txt
MOSQUITTO_HOST=44.204.185.170
MOSQUITTO_MQTTS_URL=mqtts://44.204.185.170:8883
MOSQUITTO_PORT=8883
MOSQUITTO_PRIVATE_IP=172.31.89.225
MOSQUITTO_ADMIN_USER=cloudimg-admin
MOSQUITTO_APP_USER=cloudimg-app
MOSQUITTO_APP_TOPICS=app/#
MOSQUITTO_TLS_CA_FILE=/etc/mosquitto/tls/mosquitto.crt
MOSQUITTO_TLS_CERT_SHA256=EB:D0:A2:74:C4:B0:EB:34:11:A8:CD:67:13:BB:33:AD:B2:7F:3E:68:4E:D7:87:40:FF:CD:98:E3:8F:58:D3:9D
MOSQUITTO_TLS_PUBKEY_SHA256=6de06a0c9fda4b9eedf6690a35fbeed31b1f687beb5cf159e3f8373b727a86cc

The file also holds MOSQUITTO_ADMIN_PASSWORD and MOSQUITTO_APP_PASSWORD. Give devices and 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/mosquitto/tls/mosquitto.crt -noout -subject -enddate -ext subjectAltName,basicConstraints
sudo openssl x509 -in /etc/mosquitto/tls/mosquitto.crt -noout -fingerprint -sha256
subject=CN = mosquitto-broker, O = cloudimg
notAfter=Feb 10 06:45:22 2029 GMT
X509v3 Subject Alternative Name:
    IP Address:44.204.185.170, DNS:ec2-44-204-185-170.compute-1.amazonaws.com, IP Address:172.31.89.225, DNS:ip-172-31-89-225.ec2.internal, IP Address:127.0.0.1, DNS:localhost
X509v3 Basic Constraints: critical
    CA:FALSE
sha256 Fingerprint=EB:D0:A2:74:C4:B0:EB:34:11:A8:CD:67:13:BB:33:AD:B2:7F:3E:68:4E:D7:87:40:FF:CD:98:E3:8F:58:D3:9D

CA:FALSE means it is an end-entity certificate that cannot sign anything else. The SHA-256 fingerprint is the MOSQUITTO_TLS_CERT_SHA256 value in your credentials file. 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/mosquitto/tls/mosquitto.crt' > mosquitto.crt
client-host$ openssl x509 -in mosquitto.crt -noout -fingerprint -sha256

Compare the fingerprint with MOSQUITTO_TLS_CERT_SHA256 before you trust the file. Then give your client only this file as its CA file (--cafile for mosquitto_pub and mosquitto_sub, tls_set(ca_certs=...) in Paho), with host name checking on. Do not add it to the operating system's trust store: the client should trust this one broker, not every program on the machine. 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 (MOSQUITTO_TLS_PUBKEY_SHA256) keep working, while clients that pin the certificate file need the new copy.

Step 6: Test on the instance

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

sudo mosquitto-probe connect --identity app
sudo mosquitto-probe roundtrip --identity app --topic app/guide-test
sudo mosquitto-probe acl --identity app --topic orders/guide-test || true
sudo mosquitto-probe connect --anonymous || true
CONNACK-OK proto=4 tls=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384
ROUNDTRIP OK topic=app/guide-test qos=1 bytes=43 proto=4 tls=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384
ACL-DENIED sub=0x01 pub=ack leaked=0 control=delivered
REFUSED rc=5 not-authorized proto=4

The application login connects, subscribes to app/guide-test, publishes a message there and receives it back. Outside app/# the broker silently drops what it publishes and delivers nothing to its subscription (MQTT does not tell the client), which acl proves with cloudimg-admin as the observer. A client without a login is refused with CONNACK code 5 (not authorized). mosquitto-probe exits non-zero on a refusal, hence || true.

Step 7: Connect a client from another machine

On the client machine, install the Mosquitto command-line clients (sudo apt-get install mosquitto-clients on Ubuntu or Debian, brew install mosquitto on macOS) and copy the certificate as in Step 5. Then put the connection settings in an options file that only you can read. mosquitto_pub and mosquitto_sub read their default options, one per line, from mosquitto_pub and mosquitto_sub files in the directory named by XDG_CONFIG_HOME (normally ~/.config), so the password is never on a command line. Keeping them in a directory of their own lets you have one per broker and login:

client-host$ umask 077; mkdir -p ~/mqtt-app
client-host$ printf -- '-h <public-ip>\n-p 8883\n--cafile %s/mosquitto.crt\n-u cloudimg-app\n' "$PWD" > ~/mqtt-app/mosquitto_pub
client-host$ ssh -i <your-key.pem> ubuntu@<public-ip> "sudo sed -n 's/^MOSQUITTO_APP_PASSWORD=/-P /p' /root/mosquitto-credentials.txt" >> ~/mqtt-app/mosquitto_pub
client-host$ cp ~/mqtt-app/mosquitto_pub ~/mqtt-app/mosquitto_sub

Subscribe in one terminal and publish from another:

client-host$ XDG_CONFIG_HOME=~/mqtt-app mosquitto_sub -t 'app/demo/#' -q 1 -v
client-host$ XDG_CONFIG_HOME=~/mqtt-app mosquitto_pub -t app/demo/hello -q 1 -m 'hello from mosquitto_pub'
app/demo/hello hello from mosquitto_pub

Use a name the certificate covers as -h: the public or private IP, an EC2 DNS name, or your own DNS name once you have added it (Step 12). This works with the 2.0 clients in Ubuntu 24.04 and the 2.1 clients on the instance; version 2.1 can also take a file with -o <file>. If you subscribe to a topic outside app/# with this login you receive nothing, and what you publish there is dropped.

The same from Python with Eclipse Paho (sudo apt-get install python3-paho-mqtt, or pip install paho-mqtt):

# paho_demo.py - publish and receive through your cloudimg Mosquitto broker over MQTT/TLS (paho-mqtt 1.6 or 2.x).
# Usage: python3 paho_demo.py <broker-host> <mosquitto.crt> <user> <password-file> <topic>
# The password is read from a file, never the command line; TLS trusts ONLY the broker's own certificate.
import ssl
import sys
import threading

import paho.mqtt.client as mqtt

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

got = threading.Event()
try:
    c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
except AttributeError:                       # paho-mqtt 1.x
    c = mqtt.Client()
c.username_pw_set(user, password)
c.tls_set(ca_certs=cafile, tls_version=ssl.PROTOCOL_TLS_CLIENT)   # verify against this one certificate, host name checked
c.on_connect = lambda cl, *a: cl.subscribe(topic, qos=1)
c.on_subscribe = lambda cl, *a: cl.publish(topic, b"hello from paho", qos=1)
c.on_message = lambda cl, u, m: (print("received:", m.topic, m.payload.decode()), got.set())
c.connect(host, 8883)
c.loop_start()
ok = got.wait(15)
c.loop_stop()
c.disconnect()
sys.exit(0 if ok else 1)
client-host$ ssh -i <your-key.pem> ubuntu@<public-ip> "sudo sed -n 's/^MOSQUITTO_APP_PASSWORD=//p' /root/mosquitto-credentials.txt" > app.password
client-host$ python3 paho_demo.py <public-ip> mosquitto.crt cloudimg-app app.password app/paho-demo
received: app/paho-demo hello from paho

The URL form is mqtts://<public-ip>:8883. Some clients accept the login inside the URL (mqtts://cloudimg-app:<password>@...); build it inside your program from the password file rather than writing it in a configuration file or on a command line.

Step 8: Add users and topic permissions

Give each device type or application its own login, limited to its own topics, rather than sharing one. mosquitto-user reads the new password from standard input, writes its PBKDF2-SHA512 hash, adds the user's ACL block, checks both files with the configuration gate and reloads the broker (connected clients stay connected):

umask 077
openssl rand -hex 16 > ~/sensors.password
sudo mosquitto-user add sensors --topic 'sensors/#' < ~/sensors.password
sudo mosquitto-user generate dashboard --read 'sensors/#'
sudo mosquitto-user list
mosquitto-user: sensors set
mosquitto-user: dashboard added; its password is in /root/mosquitto-user-dashboard.password (root only, 0600)
cloudimg-admin           topic readwrite #; topic read $SYS/#
cloudimg-app             topic readwrite app/#
sensors                  topic readwrite sensors/#
dashboard                topic read sensors/#

--topic grants read and write on a topic filter, --read grants read only; give either option as often as you need. Giving them for an existing user replaces its topic access; without them an existing user keeps its access and only its password changes. generate mints a random password into /root/mosquitto-user-<name>.password (root only). Passwords must be at least 16 characters. Try the new logins:

sudo mosquitto-probe roundtrip --user sensors --password-file ~/sensors.password --topic sensors/17/temperature
sudo mosquitto-probe acl --user sensors --password-file ~/sensors.password --topic app/guide-test --control-topic sensors/acl-check || true
ROUNDTRIP OK topic=sensors/17/temperature qos=1 bytes=43 proto=4 tls=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384
ACL-DENIED sub=0x01 pub=ack leaked=0 control=delivered

Hand ~/sensors.password and /root/mosquitto-user-dashboard.password to your applications' secret store, then delete the local copies with shred -u. To remove the example users again:

sudo mosquitto-user delete sensors
sudo mosquitto-user delete dashboard
sudo shred -u /root/mosquitto-user-dashboard.password; shred -u ~/sensors.password

If you manage passwords yourself, use mosquitto_passwd with the password on standard input (never -b, which puts it on the command line), and pass -H sha512-pbkdf2: Mosquitto 2.1's default argon2id hash cannot be made with Ubuntu 24.04's OpenSSL 3.0 and fails with Error: Unable to hash password. Then run sudo systemctl reload mosquitto.

Step 9: The broker refuses to run unsafely

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

sudo mosquitto-config-gate --unit && echo gate-ok
sudo mosquitto-selfcheck
mosquitto-config-gate: OK (unit): listener 8883 TLS (tlsv1.2 minimum), anonymous off, 2 users in the password file, 2 ACL user blocks
gate-ok
  ok: units active: mosquitto-metadata-names, mosquitto-firstboot, mosquitto
  ok: OK: listener 8883 TLS (tlsv1.2 minimum), anonymous off, 2 users in the password file, 2 ACL user blocks
  ok: OK (check): listening 0.0.0.0:8883 [::]:8883, no UDP; anonymous and a random password refused over TLS (rc=5 rc=5 rc=0x87 rc=0x87)
  ok: listening set: 22 (ssh) and 8883 (MQTT over TLS, IPv4 + IPv6) only; no UDP from the broker
  ok: certificate served on 8883 = /etc/mosquitto/tls/mosquitto.crt = credentials file (SHA-256 EB:D0:A2:74:C4:B0:EB:34...); covers 172.31.89.225 127.0.0.1 (endpoint 44.204.185.170)
  ok: MQTT over TLS publish/subscribe round trips (QoS 1): cloudimg-app via 127.0.0.1 and 172.31.89.225 (3.1.1 and 5.0); cloudimg-admin on ops/ (TLSv1.3 cipher=TLS_AES_256_GCM_SHA384)
  ok: refusals: anonymous, wrong passwords and an unknown user (MQTT 3.1.1 CONNACK 5 / 5.0 reason 0x87 not authorized; the broker does not say which part was wrong); cloudimg-app outside app/# receives and leaks nothing; plain MQTT on 8883 not answered; 1883 8080 8081 9001 8884 443 80 closed on 172.31.89.225
  ok: instance metadata blocked inside the broker's cgroup (000) while loopback is open; no outbound connection (no bridge, no telemetry)
SELFCHECK OK

If you edit /etc/mosquitto/mosquitto.conf or the ACL and allow anonymous clients, add a plain-text or second listener, a WebSockets listener, a bridge or a plugin, remove the TLS certificate, the password file or the ACL, the gate refuses the change. On a start the broker does not start; on systemctl reload the running broker keeps its previous settings. journalctl -u mosquitto shows the reason. If the running broker ever listens anywhere but 8883 or accepts an anonymous login, the guard stops it. mosquitto-selfcheck prints one ok: line per area and ends with SELFCHECK OK.

Step 10: Why plain-text 1883 and WebSockets are closed

Mosquitto is often run with a plain-text listener on 1883, where user names, passwords and every message cross the network in clear text, and sometimes with anonymous access. On a cloud instance that means anyone on the path can read and inject messages. This image configures exactly one listener, MQTT over TLS on 8883, in Mosquitto's own configuration rather than relying on a firewall:

  • 1883 (MQTT): not configured. Every maintained MQTT client supports TLS on 8883.
  • WebSockets (often 8080, 8081 or 9001): not configured. A browser application should reach MQTT through your own back end, or you can add a WebSockets listener behind TLS yourself once you have reviewed it (the configuration gate refuses one by default).
  • Anonymous access: refused on every listener (allow_anonymous false).

From another machine a connection to 1883, 8080, 8081, 9001, 8884, 443 or 80 is refused even if your security group allows it, and plain MQTT sent to 8883 gets no answer.

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 network service must not be able to reach it. mosquitto and mosquitto-firstboot run with the metadata addresses blocked by systemd (IPAddressDeny). Only mosquitto-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 mosquitto mosquitto-firstboot mosquitto-metadata-names; do printf '%-26s deny=%s allow=%s\n' "$u" "$(systemctl show -p IPAddressDeny --value "$u")" "$(systemctl show -p IPAddressAllow --value "$u")"; done
mosquitto                  deny=::ffff:169.254.169.254/128 ::ffff:168.63.129.16/128 168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128 allow=
mosquitto-firstboot        deny=169.254.169.254/32 168.63.129.16/32 ::ffff:169.254.169.254/128 fd00:ec2::254/128 ::ffff:168.63.129.16/128 allow=
mosquitto-metadata-names   deny=::/0 0.0.0.0/0 allow=169.254.169.254/32 fd00:ec2::254/128

mosquitto-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 mosquitto-set-hostname <dns-name>

This re-issues the self-signed certificate with the same key to cover the name, updates the endpoint in /root/mosquitto-credentials.txt and restarts the broker. Copy the new mosquitto.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 mosquitto-install-cert <certificate-chain.pem> <private-key.pem> <dns-name>

Port 8883 then serves your certificate, and clients trust your CA instead of pinning this instance. The boot-time renewal never replaces an installed certificate. sudo mosquitto-set-hostname <name> goes back to a new self-signed certificate.

Logs, files and limits

  • Broker log: journalctl -u mosquitto (connections, disconnections and refusals; never a password); first boot: journalctl -u mosquitto-firstboot.
  • Configuration: /etc/mosquitto/mosquitto.conf (checked by the gate on every start and reload). Logins and ACL: /etc/mosquitto/cloudimg/passwd and /etc/mosquitto/cloudimg/acl (root:mosquitto 0640). Certificate and key: /etc/mosquitto/tls/.
  • Retained messages and persistent sessions are saved in /var/lib/mosquitto/mosquitto.db every 5 minutes and on shutdown, so they survive a restart. Size the EBS volume for your retained data and take EBS snapshots for backups.
  • cloudimg-admin may also read the $SYS/# broker statistics, for example XDG_CONFIG_HOME=~/mqtt-admin mosquitto_sub -t '$SYS/broker/clients/connected' -C 1, with an options directory made as in Step 7 for cloudimg-admin and MOSQUITTO_ADMIN_PASSWORD.
  • The broker runs as the unprivileged mosquitto user in a systemd sandbox (no capabilities, a read-only system, a system-call filter, core dumps off).

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. Mosquitto itself is documented at mosquitto.org/documentation.