Eclipse Mosquitto is a lightweight MQTT broker used by sensors, Home Assistant, Node-RED and most IoT stacks. A fresh install only accepts anonymous, unencrypted connections from localhost, which is fine for testing but not for devices that connect over the internet. In this tutorial you will turn a default Mosquitto install on Ubuntu 24.04 into a production broker: TLS with a Let's Encrypt certificate, username and password authentication, per-topic access control lists (ACLs), secure WebSockets for browser clients, and a bridge that forwards selected topics to a second broker.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with a non-root user that has
sudoprivileges. - A DNS A record such as
mqtt.your_domainpointing to the server's public IP. Replacemqtt.your_domainwith your own hostname throughout the guide. - UFW enabled with SSH allowed.
- Ports 80 (certificate issuance), 8883 (MQTT over TLS) and 8884 (MQTT over secure WebSockets) reachable from the internet.
- For the bridge section only: a second MQTT broker you control, reachable over TLS.
Step 1 - Installing Mosquitto
Ubuntu 24.04 ships Mosquitto 2.0 in its main repositories, which supports everything in this guide, including WebSockets. Install the broker and the command-line clients:
sudo apt update
sudo apt install mosquitto mosquitto-clients
The package enables and starts the service automatically. Check that it is running:
systemctl status mosquitto
● mosquitto.service - Mosquitto MQTT Broker
Loaded: loaded (/usr/lib/systemd/system/mosquitto.service; enabled; preset: enabled)
Active: active (running) since ...
Look at the main configuration file before adding anything:
cat /etc/mosquitto/mosquitto.conf
pid_file /run/mosquitto/mosquitto.pid
persistence true
persistence_location /var/lib/mosquitto/
log_dest file /var/log/mosquitto/mosquitto.log
include_dir /etc/mosquitto/conf.d
Persistence and file logging are already on, and every *.conf file in /etc/mosquitto/conf.d/ is loaded. You will leave this file untouched and put all your settings in conf.d. Do not repeat persistence_location or pid_file there: Mosquitto refuses to start when some options are defined twice.
Step 2 - Obtaining a TLS certificate
MQTT credentials travel in clear text unless the connection is encrypted, so TLS is required before exposing the broker. Let's Encrypt certificates are trusted by every standard client and browser, which also matters for WebSockets later.
Install Certbot and open port 80 for the HTTP challenge:
sudo apt install certbot
sudo ufw allow 80/tcp
Request the certificate with the standalone authenticator:
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 under /etc/letsencrypt are only readable by root, and Mosquitto runs as the mosquitto user. Instead of loosening those permissions, copy the certificate into a Mosquitto directory with a Certbot deploy hook, which also runs on every renewal. Create the hook:
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 the certificate that belongs to the broker
if [[ "${RENEWED_LINEAGE}" != "/etc/letsencrypt/live/${domain}" ]]; then
exit 0
fi
install -d -o root -g mosquitto -m 0750 "${dest}"
install -o root -g mosquitto -m 0644 "${RENEWED_LINEAGE}/fullchain.pem" "${dest}/fullchain.pem"
install -o root -g mosquitto -m 0640 "${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 0755 /etc/letsencrypt/renewal-hooks/deploy/mosquitto.sh
sudo RENEWED_LINEAGE=/etc/letsencrypt/live/mqtt.your_domain /etc/letsencrypt/renewal-hooks/deploy/mosquitto.sh
sudo ls -l /etc/mosquitto/certs
-rw-r--r-- 1 root mosquitto 2868 ... fullchain.pem
-rw-r----- 1 root mosquitto 241 ... privkey.pem
Certbot's systemd timer renews the certificate automatically, and the hook reinstalls it and restarts Mosquitto each time.
Step 3 - Creating users and disabling anonymous access
Mosquitto 2.0 applies allow_anonymous and password_file to all listeners as long as per_listener_settings stays at its default (false), so one file is enough for authentication across MQTT, TLS and WebSockets.
Create the password file with a first user called admin. The -c flag creates the file, so use it only for the first user:
sudo mosquitto_passwd -c /etc/mosquitto/passwd admin
Add one account per device or service. These examples are used in the ACL step: a sensor and a Node-RED instance:
sudo mosquitto_passwd /etc/mosquitto/passwd sensor01
sudo mosquitto_passwd /etc/mosquitto/passwd nodered
Each command prompts for a password, so the passwords never end up in your shell history. Mosquitto warns if the file is readable by other users, so restrict it:
sudo chown mosquitto:mosquitto /etc/mosquitto/passwd
sudo chmod 0600 /etc/mosquitto/passwd
Run the chown and chmod again whenever you add or change a user, because mosquitto_passwd rewrites the file.
Step 4 - Writing the ACL file
Authentication decides who may connect; the ACL file decides which topics each user may read or write. In this example, sensors publish to sensors/<username>/... and receive commands on sensors/<username>/cmd/..., Node-RED reads all sensor data and sends commands, and admin can do everything, including reading the broker statistics under $SYS.
sudo nano /etc/mosquitto/acl
# Rules for every authenticated user: %u is replaced by the username
pattern write sensors/%u/#
pattern read sensors/%u/cmd/#
# Node-RED reads all sensors and sends commands
user nodered
topic read sensors/#
topic write sensors/+/cmd/#
# Full access for the administrator; '#' does not match $SYS topics
user admin
topic readwrite #
topic read $SYS/#
With this file, sensor01 can publish to sensors/sensor01/temperature but not to sensors/sensor02/temperature, and it cannot read other devices' data. Set the ownership and permissions:
sudo chown mosquitto:mosquitto /etc/mosquitto/acl
sudo chmod 0600 /etc/mosquitto/acl
Step 5 - Configuring the listeners
Now tie everything together in one file. You will define three listeners:
1883on127.0.0.1only, for services on the same server such as Telegraf or Node-RED.8883for MQTT over TLS from devices on the internet.8884for MQTT over secure WebSockets (WSS) from browsers.
sudo nano /etc/mosquitto/conf.d/secure.conf
# Authentication and authorization (apply to all listeners)
allow_anonymous false
password_file /etc/mosquitto/passwd
acl_file /etc/mosquitto/acl
# Plain MQTT, local clients only
listener 1883 127.0.0.1
# MQTT over TLS
listener 8883
certfile /etc/mosquitto/certs/fullchain.pem
keyfile /etc/mosquitto/certs/privkey.pem
# MQTT over secure WebSockets
listener 8884
protocol websockets
certfile /etc/mosquitto/certs/fullchain.pem
keyfile /etc/mosquitto/certs/privkey.pem
Options such as certfile and protocol belong to the listener line above them, so the order of the lines matters.
Restart the broker and check that it started cleanly:
sudo systemctl restart mosquitto
sudo tail -n 20 /var/log/mosquitto/mosquitto.log
... mosquitto version 2.0.18 starting
... Config loaded from /etc/mosquitto/mosquitto.conf.
... Opening ipv4 listen socket on port 1883.
... Opening ipv4 listen socket on port 8883.
... Opening websockets listen socket on port 8884.
... mosquitto version 2.0.18 running
Confirm the listening sockets. Port 1883 must only be bound to localhost:
sudo ss -tlnp | grep mosquitto
LISTEN 0 100 127.0.0.1:1883 0.0.0.0:* users:(("mosquitto",...))
LISTEN 0 100 0.0.0.0:8883 0.0.0.0:* users:(("mosquitto",...))
LISTEN 0 100 *:8884 *:* users:(("mosquitto",...))
Open the TLS ports in the firewall. Port 1883 stays closed:
sudo ufw allow 8883/tcp
sudo ufw allow 8884/tcp
Step 6 - Testing authentication, TLS and ACLs
Run the tests from any machine with mosquitto-clients installed, or from the server itself. The --capath /etc/ssl/certs option tells the client to verify the broker certificate against the system CA store.
In one terminal, subscribe as nodered to all sensor topics:
mosquitto_sub -h mqtt.your_domain -p 8883 --capath /etc/ssl/certs \
-u nodered -P 'nodered_password' -t 'sensors/#' -v
In a second terminal, publish as sensor01 to its own topic:
mosquitto_pub -h mqtt.your_domain -p 8883 --capath /etc/ssl/certs \
-u sensor01 -P 'sensor01_password' -t 'sensors/sensor01/temperature' -m '21.4'
The subscriber prints the message:
sensors/sensor01/temperature 21.4
Now publish as sensor01 to another device's topic:
mosquitto_pub -h mqtt.your_domain -p 8883 --capath /etc/ssl/certs \
-u sensor01 -P 'sensor01_password' -t 'sensors/sensor02/temperature' -m '99'
The command exits without an error, because MQTT 3.1.1 has no way to report a denied publish, but the subscriber receives nothing. Finally, try a wrong password:
mosquitto_pub -h mqtt.your_domain -p 8883 --capath /etc/ssl/certs \
-u sensor01 -P 'wrong' -t 'sensors/sensor01/temperature' -m '1'
Connection error: Connection Refused: not authorised.
Error: The connection was refused.
NotePassing
-Pon the command line is convenient for testing but leaves the password in your shell history. Real devices should keep their credentials in their own configuration.
Step 7 - Connecting browser clients over WebSockets
The mosquitto_sub and mosquitto_pub tools do not speak WebSockets, so test port 8884 with a browser client such as MQTT.js. A minimal page looks like this:
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
<script>
const client = mqtt.connect("wss://mqtt.your_domain:8884", {
username: "nodered",
password: "nodered_password",
});
client.on("connect", () => {
console.log("connected");
client.subscribe("sensors/#");
});
client.on("message", (topic, payload) => {
console.log(topic, payload.toString());
});
</script>
Open the page, publish a message as in Step 6 and it appears in the browser console. Because the certificate comes from Let's Encrypt, no certificate exceptions are needed.
WarningAnything shipped to a browser is visible to its user. Give browser clients a dedicated account whose ACL only allows the topics that page needs.
Step 8 - Bridging to a remote broker
A bridge makes your broker act as a client of another broker and forward selected topics in one or both directions. A common use is a local broker at a site that sends sensor data to a central broker and receives commands from it.
On the remote broker, create a user for the bridge (for example site1-bridge) and allow it to write site1/sensors/# and read site1/commands/#. Then, on this server, create the bridge configuration:
sudo nano /etc/mosquitto/conf.d/bridge.conf
connection site1-to-central
address central.your_domain:8883
bridge_capath /etc/ssl/certs
remote_username site1-bridge
remote_password your_bridge_password
# topic <pattern> <direction> <qos> <local prefix> <remote prefix>
# Local sensors/# is published remotely as site1/sensors/#
topic # out 1 sensors/ site1/sensors/
# Remote site1/commands/# arrives locally as commands/#
topic # in 1 commands/ site1/commands/
cleansession false
restart_timeout 10 60
notifications true
The local and remote prefixes let each side use its own topic layout: the pattern # is appended to the prefix on each side. cleansession false keeps the remote session, so QoS 1 messages queued during a disconnection are delivered when the bridge reconnects. restart_timeout 10 60 retries with a backoff between 10 and 60 seconds.
The file contains a password, so restrict it and restart:
sudo chown root:mosquitto /etc/mosquitto/conf.d/bridge.conf
sudo chmod 0640 /etc/mosquitto/conf.d/bridge.conf
sudo systemctl restart mosquitto
With notifications true, the bridge publishes its state as a retained message under $SYS/broker/connection/: 1 when connected and 0 when not. Read it as admin, who has access to $SYS:
mosquitto_sub -h mqtt.your_domain -p 8883 --capath /etc/ssl/certs \
-u admin -P 'admin_password' -t '$SYS/broker/connection/#' -v -W 5
$SYS/broker/connection/your-hostname.site1-to-central/state 1
Step 9 - Tuning persistence and logging
Persistence is already enabled in the main file, which stores retained messages, subscriptions and queued QoS 1 and 2 messages in /var/lib/mosquitto/mosquitto.db. By default that file is only written every 30 minutes and on shutdown, so a crash can lose recent state. Create a file with a shorter interval and more useful logs:
sudo nano /etc/mosquitto/conf.d/tuning.conf
# Save the in-memory database every 5 minutes
autosave_interval 300
# Forget persistent sessions of clients not seen for 14 days
persistent_client_expiration 14d
# Readable timestamps in the log file
log_timestamp_format %Y-%m-%dT%H:%M:%S
log_type error
log_type warning
log_type notice
log_type information
Restart the broker and check the log:
sudo systemctl restart mosquitto
sudo tail -n 5 /var/log/mosquitto/mosquitto.log
2026-09-25T10:14:02: mosquitto version 2.0.18 running
2026-09-25T10:14:05: New client connected from 203.0.113.10:51234 as sensor01 (p2, c1, k60, u'sensor01').
To watch broker load, subscribe to a few $SYS counters as admin:
mosquitto_sub -h mqtt.your_domain -p 8883 --capath /etc/ssl/certs -u admin -P 'admin_password' \
-t '$SYS/broker/clients/connected' -t '$SYS/broker/messages/received' -v
Troubleshooting
Mosquitto does not start after a change. The reason is almost always in the last lines of the log or the journal:
sudo tail -n 20 /var/log/mosquitto/mosquitto.log
sudo journalctl -u mosquitto -n 20 --no-pager
Typical messages are Duplicate ... value in configuration (an option you repeated from mosquitto.conf) and Unable to load server key file (wrong path or permissions under /etc/mosquitto/certs).
Clients get not authorised. Check the username and password, then make sure the password file is owned by mosquitto after your last mosquitto_passwd run.
Messages silently disappear. That is the ACL denying a publish or subscribe. Add log_type debug temporarily to tuning.conf, restart, reproduce the problem and look for Denied PUBLISH lines in the log. Remove the line afterwards; debug logging is very verbose.
TLS handshake fails. Check the certificate the broker presents and its expiry date:
openssl s_client -connect mqtt.your_domain:8883 -servername mqtt.your_domain </dev/null 2>/dev/null | openssl x509 -noout -subject -dates
Conclusion
Your Mosquitto broker now only accepts authenticated clients over TLS, restricts every user to its own topics, serves browser clients over secure WebSockets and forwards selected topics to a central broker. As next steps, store the sensor data in InfluxDB with Telegraf, connect Zigbee devices through Zigbee2MQTT, and add a user per new device instead of sharing credentials.
