Eclipse Mosquitto is a lightweight open source broker for MQTT, the publish-subscribe protocol used by sensors, smart home hubs and other IoT devices. Clients publish messages to topics such as sensors/greenhouse/temperature, and the broker delivers them to every client subscribed to that topic. In this tutorial you will install Mosquitto on Ubuntu 24.04, require usernames and passwords, limit what each device can publish or read with an access control list, and encrypt connections on port 8883 with a free Let's Encrypt certificate.
Prerequisites
To follow this tutorial you need:
- A server running Ubuntu 24.04 LTS, for example a CubePath VPS. 512 MB of RAM is enough for thousands of idle device connections.
- A non-root user with
sudoprivileges and UFW enabled. - A domain name with an A record pointing to your server's public IP, used for the TLS certificate. This guide uses
mqtt.your_domain. - Port 80 reachable from the internet during certificate issuance and renewal.
Step 1 - Installing Mosquitto
Mosquitto 2.0 and its command line clients are in the Ubuntu repositories:
sudo apt update
sudo apt install -y mosquitto mosquitto-clients
The package starts the broker and enables it at boot. Check the service:
sudo systemctl status mosquitto --no-pager
● mosquitto.service - Mosquitto MQTT Broker
Loaded: loaded (/usr/lib/systemd/system/mosquitto.service; enabled; preset: enabled)
Active: active (running) since ...
Since version 2.0, Mosquitto with no listener configured accepts connections only from localhost, so nothing is exposed yet. Test it with a subscriber and a publisher. In one terminal:
mosquitto_sub -h localhost -t 'test/#' -v
In a second terminal:
mosquitto_pub -h localhost -t test/hello -m 'Hello MQTT'
The subscriber prints the topic and message:
test/hello Hello MQTT
Press CTRL+C to stop the subscriber.
Step 2 - Creating MQTT users
Each device or application should have its own credentials, so you can revoke one without touching the others. Create the password file with a first user, admin. The -c flag creates the file and overwrites it if it exists, so use it only this once:
sudo mosquitto_passwd -c /etc/mosquitto/passwd admin
Add a user for a sensor and one for a dashboard that only reads data, without -c:
sudo mosquitto_passwd /etc/mosquitto/passwd sensor01
sudo mosquitto_passwd /etc/mosquitto/passwd dashboard
Each command prompts for the password twice. The file stores salted hashes, not the passwords. Mosquitto warns if the file is readable by other users, so restrict it:
sudo chown mosquitto:mosquitto /etc/mosquitto/passwd
sudo chmod 600 /etc/mosquitto/passwd
Step 3 - Defining access control rules
Without an ACL, any authenticated user can read and write every topic, which means one compromised sensor can inject fake commands for all devices. Create an ACL file:
sudo nano /etc/mosquitto/acl
# Applies to every authenticated user: each device may publish
# under sensors/<its username>/ and read its own command topic
pattern write sensors/%u/#
pattern read commands/%u/#
# Administrator: everything, including the $SYS broker statistics
user admin
topic readwrite #
topic read $SYS/#
# Dashboard: read-only access to all sensor data
user dashboard
topic read sensors/#
How the rules work:
patternlines apply to all users, and%uis replaced by the username of the connected client. Usersensor01can publish tosensors/sensor01/temperaturebut not tosensors/sensor02/temperature.topiclines after auserline apply only to that user.- The
#wildcard does not match topics that start with$, so the broker statistics under$SYSneed their own rule. - Anything not allowed is denied.
Set the same ownership and permissions as the password file:
sudo chown mosquitto:mosquitto /etc/mosquitto/acl
sudo chmod 600 /etc/mosquitto/acl
Step 4 - Enabling authentication
On Ubuntu, /etc/mosquitto/mosquitto.conf already sets persistence and logging and includes every .conf file in /etc/mosquitto/conf.d/. Keep your settings in a separate file there so package upgrades do not conflict with them:
sudo nano /etc/mosquitto/conf.d/default.conf
# Plain MQTT only on localhost, for local tools and testing
listener 1883 localhost
# Global security settings
allow_anonymous false
password_file /etc/mosquitto/passwd
acl_file /etc/mosquitto/acl
Restart the broker and check that it came up:
sudo systemctl restart mosquitto
sudo systemctl status mosquitto --no-pager
An anonymous client is now refused:
mosquitto_pub -h localhost -t test/hello -m 'anonymous'
Connection error: Connection Refused: not authorised.
Error: The connection was refused.
Subscribe as the dashboard user, replacing dashboard_password with the password you set in step 2:
mosquitto_sub -h localhost -u dashboard -P 'dashboard_password' -t 'sensors/#' -v
In another terminal, publish as sensor01 to its own topic:
mosquitto_pub -h localhost -u sensor01 -P 'sensor01_password' -t sensors/sensor01/temperature -m '21.4'
sensors/sensor01/temperature 21.4
Now try to publish to another sensor's topic:
mosquitto_pub -h localhost -u sensor01 -P 'sensor01_password' -t sensors/sensor02/temperature -m '99'
The command exits normally, because with MQTT 3.1.1 the broker cannot tell the client that a publish was rejected, but the dashboard receives nothing. The broker log records the denial:
sudo grep Denied /var/log/mosquitto/mosquitto.log | tail -n 1
... Denied PUBLISH from auto-XXXX (d0, q0, r0, m0, 'sensors/sensor02/temperature', ... (2 bytes))
Step 5 - Getting a TLS certificate
MQTT on port 1883 sends usernames, passwords and data in plain text. Devices on the internet must connect over TLS on port 8883. Install Certbot from the Ubuntu repositories:
sudo apt install -y certbot
Open port 80 for the HTTP challenge and request a certificate with Certbot's standalone web server. Replace mqtt.your_domain with your domain:
sudo ufw allow 80/tcp
sudo certbot certonly --standalone -d mqtt.your_domain
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/mqtt.your_domain/fullchain.pem
Key is saved at: /etc/letsencrypt/live/mqtt.your_domain/privkey.pem
The files in /etc/letsencrypt/live are readable only by root, while the broker runs as the mosquitto user. Rather than loosening those permissions, copy the certificate into a directory owned by mosquitto every time Certbot renews it. Create a deploy hook, which Certbot runs after each successful renewal:
sudo nano /etc/letsencrypt/renewal-hooks/deploy/mosquitto.sh
#!/usr/bin/env bash
set -euo pipefail
domain="mqtt.your_domain"
dest="/etc/mosquitto/certs"
# Only act on renewals of the MQTT certificate
if [[ "${RENEWED_LINEAGE:-}" != "/etc/letsencrypt/live/${domain}" ]]; then
exit 0
fi
install -d -m 0750 -o mosquitto -g mosquitto "$dest"
install -m 0644 -o mosquitto -g mosquitto "${RENEWED_LINEAGE}/fullchain.pem" "${dest}/fullchain.pem"
install -m 0600 -o mosquitto -g mosquitto "${RENEWED_LINEAGE}/privkey.pem" "${dest}/privkey.pem"
systemctl restart mosquitto
Make it executable and run it once by hand to copy the certificate you just obtained:
sudo chmod 755 /etc/letsencrypt/renewal-hooks/deploy/mosquitto.sh
sudo RENEWED_LINEAGE=/etc/letsencrypt/live/mqtt.your_domain /etc/letsencrypt/renewal-hooks/deploy/mosquitto.sh
ls -l /etc/mosquitto/certs
-rw-r--r-- 1 mosquitto mosquitto 2848 ... fullchain.pem
-rw------- 1 mosquitto mosquitto 241 ... privkey.pem
Certbot's systemd timer (certbot.timer) renews the certificate automatically. Confirm that renewal, including the hook, works:
sudo certbot renew --dry-run
Step 6 - Enabling the TLS listener
Add a second listener to the same configuration file:
sudo nano /etc/mosquitto/conf.d/default.conf
# Plain MQTT only on localhost, for local tools and testing
listener 1883 localhost
# MQTT over TLS for devices
listener 8883
certfile /etc/mosquitto/certs/fullchain.pem
keyfile /etc/mosquitto/certs/privkey.pem
tls_version tlsv1.2
# Global security settings
allow_anonymous false
password_file /etc/mosquitto/passwd
acl_file /etc/mosquitto/acl
tls_version tlsv1.2 sets the minimum version: TLS 1.2 and 1.3 clients are accepted. The authentication and ACL settings are global, so they apply to both listeners. Restart the broker and open port 8883:
sudo systemctl restart mosquitto
sudo ufw allow 8883/tcp
Confirm Mosquitto is listening on both ports:
sudo ss -tlnp | grep mosquitto
LISTEN 0 100 127.0.0.1:1883 0.0.0.0:* users:(("mosquitto",pid=...,fd=5))
LISTEN 0 100 0.0.0.0:8883 0.0.0.0:* users:(("mosquitto",pid=...,fd=6))
LISTEN 0 100 [::]:8883 [::]:* users:(("mosquitto",pid=...,fd=7))
Test a TLS connection using the domain name, as a remote device would. --capath /etc/ssl/certs tells the client to trust the system CA store, which includes Let's Encrypt:
mosquitto_sub -h mqtt.your_domain -p 8883 --capath /etc/ssl/certs \
-u dashboard -P 'dashboard_password' -t 'sensors/#' -v
mosquitto_pub -h mqtt.your_domain -p 8883 --capath /etc/ssl/certs \
-u sensor01 -P 'sensor01_password' -t sensors/sensor01/humidity -m '48'
sensors/sensor01/humidity 48
Step 7 - Connecting a device from Python
Most MQTT libraries work the same way: connect with TLS, authenticate, then publish or subscribe. This example uses the Eclipse Paho client. Install it in a virtual environment:
sudo apt install -y python3-venv
python3 -m venv ~/mqtt-demo
~/mqtt-demo/bin/pip install paho-mqtt
Create a script that simulates sensor01 sending a temperature reading every 10 seconds:
nano ~/sensor.py
import json
import random
import time
import paho.mqtt.client as mqtt
BROKER = "mqtt.your_domain"
USERNAME = "sensor01"
PASSWORD = "sensor01_password"
TOPIC = f"sensors/{USERNAME}/temperature"
def on_connect(client, userdata, flags, reason_code, properties):
print(f"Connected: {reason_code}")
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=USERNAME)
client.username_pw_set(USERNAME, PASSWORD)
client.tls_set() # verify the broker with the system CA store
client.on_connect = on_connect
client.connect(BROKER, 8883, keepalive=60)
client.loop_start()
try:
while True:
reading = {"celsius": round(random.uniform(19, 23), 1), "ts": int(time.time())}
client.publish(TOPIC, json.dumps(reading), qos=1)
time.sleep(10)
except KeyboardInterrupt:
pass
finally:
client.loop_stop()
client.disconnect()
Run it while your dashboard subscriber from step 6 is still open:
~/mqtt-demo/bin/python ~/sensor.py
Connected: Success
The subscriber shows a new JSON reading every 10 seconds. qos=1 asks the broker to acknowledge each message, and the client resends it if the acknowledgment does not arrive.
Step 8 - Monitoring the broker
Mosquitto publishes its own statistics under the $SYS/ topic tree, which the admin user can read:
mosquitto_sub -h localhost -u admin -P 'admin_password' -t '$SYS/broker/clients/connected' -t '$SYS/broker/messages/received' -v -C 2
$SYS/broker/clients/connected 2
$SYS/broker/messages/received 57
-C 2 exits after two messages. The broker log is at /var/log/mosquitto/mosquitto.log; it records every connection, disconnection and denied operation.
Troubleshooting
The service fails to start after a configuration change. Run sudo journalctl -u mosquitto -n 30 to see the error. A common cause is a typo in a file path or a certificate the mosquitto user cannot read.
Connection Refused: not authorised with the right password. The password file was recreated with -c, or the user was added to a different file. List users with sudo cut -d: -f1 /etc/mosquitto/passwd, then restart Mosquitto after adding users so it reloads the file.
TLS clients fail with certificate verify failed. The client is connecting by IP address or to a name not on the certificate. Always use the exact domain the certificate was issued for, and make sure the device has an up-to-date CA bundle.
Conclusion
Your Mosquitto broker now accepts only authenticated clients, restricts each device to its own topics, and encrypts traffic from the internet with an automatically renewed Let's Encrypt certificate. From here you can add more devices with mosquitto_passwd, connect your broker to a time-series database such as InfluxDB through Telegraf's MQTT consumer, or add a WebSockets listener so browser dashboards can subscribe directly.
