Jump to: install Mosquitto, passwords, access control, TLS or troubleshooting.
Before you start
Eclipse Mosquitto is a small, widely used MQTT broker. Sensors, microcontrollers and home automation software connect to it to publish readings and receive commands. This guide installs it, turns off anonymous access, gives each device its own account and permissions, adds TLS, and tests every step with the command-line clients.
You need a Linux machine that stays on: a Raspberry Pi, a small VM or the same host that runs Home Assistant. Mosquitto uses very little memory and CPU, so it shares a machine happily. The examples cover both the Debian package and Docker; pick one.
A few MQTT terms come up throughout:
- Broker: the server, Mosquitto. Clients never talk to each other directly; everything goes through the broker.
- Topic: a slash-separated name a message is published to, such as
home/garage/temperature. Topics don't have to be created in advance. - Subscribe: ask the broker for messages on a topic or pattern.
+matches one level (home/+/temperature),#matches everything below (home/#). - QoS: delivery guarantee. 0 is at most once, 1 is at least once, 2 is exactly once.
- Retained message: the broker keeps the last message on a topic and hands it to every new subscriber straight away.
Install Mosquitto
sudo apt update
sudo apt install mosquitto mosquitto-clients
mosquitto -h | head -n 1 # mosquitto version 2.x
systemctl status mosquitto --no-pager
The packaged service reads /etc/mosquitto/mosquitto.conf, which includes every .conf file in /etc/mosquitto/conf.d/. Put your settings in a file there, so package upgrades don't overwrite them.
mkdir -p ~/stacks/mosquitto/config ~/stacks/mosquitto/data ~/stacks/mosquitto/log
cd ~/stacks/mosquitto
sudo chown -R 1883:1883 data log
name: mosquitto
services:
mosquitto:
image: eclipse-mosquitto:2
restart: unless-stopped
ports:
- "1883:1883"
- "8883:8883"
volumes:
- ./config:/mosquitto/config
- ./data:/mosquitto/data
- ./log:/mosquitto/log
The image runs Mosquitto as UID 1883, which is why the data and log directories belong to that user. In the Docker setup, the configuration file is config/mosquitto.conf, and paths inside it start with /mosquitto/. Translate the Debian paths below accordingly.
Port 1883 is published on every interface here, because devices on your LAN need to reach it. Don't forward it through your router to the internet. If devices outside your home need the broker, use a VPN such as WireGuard or the TLS listener with strong passwords, never plain 1883.
Why Mosquitto 2 refuses outside connections
Since version 2.0, Mosquitto with no listener configured accepts connections only from the machine it runs on, and anonymous access is off unless you turn it on. A device on the LAN gets Connection refused, and the log says:
Starting in local only mode. Connections will only be possible from clients running on this machine.
That's deliberate. Older guides fix it with allow_anonymous true, which lets anyone on your network read every sensor and publish commands to every device. Configure a listener with authentication instead.
Passwords for every client
Create the configuration. On Debian, this is /etc/mosquitto/conf.d/local.conf:
per_listener_settings false
allow_anonymous false
password_file /etc/mosquitto/passwd
acl_file /etc/mosquitto/acl
listener 1883
protocol mqtt
persistence true
persistence_location /var/lib/mosquitto/
log_dest syslog
log_type error
log_type warning
log_type notice
connection_messages true
Give each device and each program its own account. If a sensor's flash is ever read out, you can revoke that one account without reconfiguring everything else. -c creates a new file and overwrites any existing one, so use it only for the first account:
sudo mosquitto_passwd -c /etc/mosquitto/passwd homeassistant
sudo mosquitto_passwd /etc/mosquitto/passwd garage-sensor
sudo mosquitto_passwd /etc/mosquitto/passwd admin
sudo chown root:mosquitto /etc/mosquitto/passwd
sudo chmod 640 /etc/mosquitto/passwd
The file stores salted hashes, not the passwords. Mosquitto 2.0 and later use PBKDF2 with SHA-512 by default. With Docker, run the same command inside the container: docker compose exec mosquitto mosquitto_passwd -c /mosquitto/config/passwd homeassistant.
Access control lists
Passwords decide who can connect. The ACL file decides what each account can read and write. Without one, every authenticated account can publish to every topic, including commands for devices it has nothing to do with.
# Home Assistant reads everything and sends commands
user homeassistant
topic readwrite homeassistant/#
topic read home/#
topic write home/+/set
topic readwrite esphome/#
# The garage sensor may only publish its own readings and read its own commands
user garage-sensor
topic write home/garage/#
topic read home/garage/set
# Admin account for debugging from the command line
user admin
topic readwrite #
# A pattern rule: every client may publish its online/offline status under its own name
pattern write status/%u
- Rules apply to the
userline above them until the nextuserline. readallows subscribing,writeallows publishing, andreadwriteallows both.patternrules apply to every user.%uis replaced with the username and%cwith the client ID.- Anything not explicitly allowed is denied. A denied publish is dropped silently, so a device seems to work while nothing arrives. Check the ACL first when messages go missing.
sudo chown root:mosquitto /etc/mosquitto/acl
sudo chmod 640 /etc/mosquitto/acl
sudo systemctl restart mosquitto
sudo journalctl -u mosquitto -n 20 --no-pager
Test from the command line
Open two terminals. In the first, subscribe as the admin account and print each topic with its message:
mosquitto_sub -h 127.0.0.1 -u admin -P 'admin-password' -t 'home/#' -v
In the second, publish as the sensor:
mosquitto_pub -h 127.0.0.1 -u garage-sensor -P 'sensor-password' -t home/garage/temperature -m 18.4
The first terminal prints home/garage/temperature 18.4. Now try something the ACL forbids:
mosquitto_pub -h 127.0.0.1 -u garage-sensor -P 'sensor-password' -t home/kitchen/light/set -m ON
Nothing arrives in the first terminal, which shows the ACL is working. Passwords typed with -P end up in your shell history. That's acceptable on a test machine; for scripts, put the settings in a file that only you can read and load them with mosquitto_sub -o, or use environment variables.
Finally, test from another machine on the LAN, using the broker's address in place of 127.0.0.1. That proves the listener and the firewall are right, not just the local socket.
Designing topics
Topic names are permanent in practice: once devices and automations use them, renaming means changing everything at once. Spend a few minutes on a scheme first.
- Go from general to specific:
home/garage/temperature, nottemperature/garage. Wildcards then work naturally:home/garage/#is everything in the garage. - One value per topic. Publishing
18.4on…/temperatureand61on…/humidityis simpler to consume than a JSON object, unless the values always change together. - Separate state from commands: the device publishes its state on
home/garage/doorand listens onhome/garage/door/set. The ACL can then allow the device to write state and read commands, and nothing else. - Avoid a leading slash (
/home/...). It creates an empty first level that makes wildcards awkward. - Avoid spaces and
#or+in names.
Retained messages and last will
A dashboard that starts up has to wait for each sensor's next reading before it knows the value. Publishing with the retain flag fixes that: the broker keeps the latest message and sends it to new subscribers immediately.
mosquitto_pub -h 127.0.0.1 -u garage-sensor -P 'sensor-password' -t home/garage/temperature -m 18.4 -r
To delete a retained message, publish an empty retained message to the same topic: -r -n.
A last will tells the broker what to publish if a client disconnects without saying goodbye, for example because it lost power. Combined with a retained "online" message on connect, it gives every device an accurate availability status:
mosquitto_sub -h 127.0.0.1 -u garage-sensor -P 'sensor-password' -t home/garage/set \
--will-topic status/garage-sensor --will-payload offline --will-retain
Libraries on microcontrollers, and firmware such as ESPHome, set the last will automatically. The ESPHome guide shows it in action.
TLS on port 8883
On a trusted home LAN, plain MQTT with passwords is a reasonable trade-off: many small devices handle TLS poorly. For anything crossing a network you don't control, add a TLS listener. With a certificate for a real domain, for example from Let's Encrypt with certbot, add:
listener 8883
protocol mqtt
certfile /etc/mosquitto/certs/fullchain.pem
keyfile /etc/mosquitto/certs/privkey.pem
tls_version tlsv1.2
Mosquitto runs as the mosquitto user and can't read certbot's files under /etc/letsencrypt directly. Copy them into /etc/mosquitto/certs from a certbot deploy hook, with ownership root:mosquitto and mode 640, and restart Mosquitto in the same hook so it loads the renewed certificate. Then test with the real hostname, so the certificate is actually checked:
mosquitto_sub -h mqtt.example.net -p 8883 --cafile /etc/ssl/certs/ca-certificates.crt \
-u admin -P 'admin-password' -t 'home/#' -v
Don't switch off the client's certificate check to get past a certificate error. It then accepts any certificate, which removes the protection TLS was added for. Fix the hostname or the certificate chain instead.
WebSockets for browser clients
Browser-based MQTT tools connect over WebSockets rather than raw TCP. Add a listener if you use one:
listener 9001
protocol websockets
The same users and ACLs apply. Put it behind a reverse proxy with HTTPS if it's reachable beyond the LAN.
Choosing a QoS level
The QoS level is set separately by the publisher and by each subscriber, and the broker delivers at the lower of the two. Higher levels add messages back and forth, so they cost battery and airtime on small devices.
| QoS | Guarantee | Good for |
|---|---|---|
| 0 | At most once. Sent and forgotten. | Frequent sensor readings, where the next one arrives soon anyway |
| 1 | At least once. Resent until acknowledged, so duplicates are possible. | Commands and state changes, provided the receiver handles a repeat safely |
| 2 | Exactly once, using a four-step handshake. | Rarely needed at home; counters or billing-style events where a duplicate would be wrong |
QoS 1 only helps across a disconnection if the client uses a persistent session: it connects with a fixed client ID and the clean-session flag off. The broker then queues QoS 1 and 2 messages for it while it's away, and keeps them across restarts because persistence true is set. The mosquitto_sub options for this are -i client-id -c -q 1. Queued messages use memory and disk, so max_queued_messages in the configuration caps how many the broker keeps for each offline client.
Watching the broker
Mosquitto publishes its own statistics on topics under $SYS/: connected clients, message rates, memory use. Read them with an account the ACL allows to see them:
mosquitto_sub -h 127.0.0.1 -u admin -P 'admin-password' -t '$SYS/broker/clients/connected' -t '$SYS/broker/messages/received' -v
The single quotes stop the shell treating $SYS as a variable. For alerting, a TCP probe on port 1883 from Uptime Kuma or the Blackbox Exporter tells you when the broker stops accepting connections.
Troubleshooting
Connection refused from another machine
Either there's no listener 1883 line, so Mosquitto is in local-only mode, or a firewall blocks the port. sudo ss -ltnp | grep 1883 should show Mosquitto listening on 0.0.0.0, not 127.0.0.1.
Connection Refused: not authorised
Wrong username or password, or the password file wasn't reloaded. Check the log with sudo journalctl -u mosquitto, which records each failed login with the client's address.
A client connects, but its messages never arrive
Almost always the ACL. Mosquitto drops publishes the account isn't allowed to make, without telling the client. Subscribe as the admin account to # to confirm nothing arrives, then check the rule for that user and topic. Remember that ACL changes need a restart.
Clients keep disconnecting and reconnecting
Two devices are using the same client ID. The broker allows one connection per ID and disconnects the older one each time the other connects. Give every device a unique client ID.
Mosquitto won't start after an edit
Run it in the foreground to see the error: sudo mosquitto -c /etc/mosquitto/mosquitto.conf -v. A setting placed before its listener line, or a file it can't read, is the usual cause.
Next steps
- ESPHome: Your First Device: firmware that publishes sensor readings to this broker.
- Home Assistant in Docker: connect Home Assistant to the broker with the
homeassistantaccount. - ESP32 Deep Sleep on Battery: battery sensors that wake, publish and sleep again.
- The mosquitto.conf manual lists every option.