Workload · Running an MQTT broker

Your own Mosquitto MQTT server, safe to put on the internet

To run your own Mosquitto MQTT server for devices out on the internet, you give it a listener, a list of users with rules for which topics each may use, and an encrypted port, 8883, because Mosquitto 2.0 and later turn away anonymous clients from other machines and plain port 1883 would carry every password in readable form. Below you set that up on a server of your own, with a free Let's Encrypt certificate, a nightly copy and a way back.

The short version
  1. Fresh install: Mosquitto 2 talks only to the machine itself. Add a listener, and nobody gets in without a username
  2. Port 1883 stays on 127.0.0.1; devices outside use 8883 with TLS
  3. A free certificate that renews itself and is handed to the broker each time
  4. Each device touches only its own topics; a display reads them all

Will Mosquitto let your devices in straight away?

No, and that is deliberate. Mosquitto's guide to moving from 1.x to 2.0 spells out two situations. Start the broker with no configuration, or with a file that has no listener line, and it listens on 127.0.0.1 and ::1 only: programs on the same computer can connect, and in this one case without a username. Once your file has a listener, it is open on every network interface, but allow_anonymous is false unless you say otherwise, so a client with no username is turned away.

The guide offers three ways in: a password file with an access list, a plugin that checks logins, or switching allow_anonymous to true, which it keeps for a genuine need. You will take the first. One more 2.0 change matters: right after reading its main configuration the broker switches to the low-privilege mosquitto account, so the password file, access list, certificates and saved-message file must be ones that account can read.

Two doors: one for the machine, one for the world

PortWhat it carriesWho can reach it here
1883Plain MQTT; usernames and passwords readable on the wireOnly this server: bound to 127.0.0.1, no firewall rule
8883MQTT inside TLS, the encrypted versionEveryone, after a username and password
80The Let's Encrypt check at issue and renewalOpen, though only certbot listens, and only then

Whatever crosses the internet on 1883 can be read along the way, passwords too; scripts on the server can still use it locally. SSH stays open through ufw's OpenSSH rule.

How much server does a broker need?

The docs give no number; Mosquitto's home page calls the broker fit for anything from a single-board computer to a full server. What fills the machine is how many clients stay connected and how many messages pile up for clients that are slow or away. You can cap both:

  • max_queued_messages: 1000 per client by default, for QoS 1 and 2 messages waiting beyond those being delivered.
  • max_connections: no cap unless you set one, though the system's open-file limit still applies.
  • max_packet_size: 2,000,000 bytes by default from version 2.1, no cap before.

Our advice: start modest and watch memory with real devices.

The server that fits

Your broker is the address every device remembers, so it should stay put. With a dedicated server you rent one whole physical computer; it runs your work and nobody else's. You log in as root, nobody meters the port, and there is no allowance to run over. A ready server is yours within 30 minutes of approval. Building one to order takes longer: hours when we already hold the parts, working days when they have to be bought in. You manage the server; we keep the hardware and the network running.

  • Want to be able to rescue it? Choose a rack server, a blade, or an m510 or m710x cartridge: those come with the out-of-band console, a screen and power button that work even when SSH will not answer. Every other cartridge offers power control and nothing more.
  • Skip the ARM cartridge: it installs Ubuntu 16.04 or 18.04 only, and these steps need 24.04 or later.
  • Pick the city nearest your devices; our locations page lists all five.

One IPv4 address comes with the server, and IPv6 at no charge. Point the broker's DNS name at it before step one, since the certificate check has to reach this machine by that name.

Step one: firewall, broker and a free certificate

Mosquitto's download page says Ubuntu ships it in its standard repositories; a PPA called mosquitto-dev carries newer releases. This block turns the firewall on first, installs the broker, its test clients and certbot, and asks Let's Encrypt for a certificate in standalone mode, where certbot briefly runs its own small web server on port 80.

Certbot's user guide says it keeps certificates in folders only root can open (mode 0700), and the current Mosquitto manual says the broker refuses sensitive files whose path runs through a symlink. So a small hook copies both files to /etc/mosquitto/certs, hands them to the mosquitto account, and nudges the broker with a HUP so it loads the new pair. The idea is mosquitto-copy.sh from Mosquitto's own repository, which already reads $RENEWED_LINEAGE. What we changed: no domain check, install copies with the owner and permissions already applied (the certs folder gets 700), and certbot receives the hook through --deploy-hook, run by sh, not from its renewal-hooks folder. Certbot remembers the hook for every renewal. Running the block twice does no harm.

# Run as root on Ubuntu 24.04 or 26.04. First fill in your broker's DNS name and your email address.
N=broker.example.net; E=you@example.net
if [ "$N" = broker.example.net ] || [ "$E" = you@example.net ]; then echo "STOP - fill in N and E first"
else
sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw && ufw allow OpenSSH && ufw --force enable &&
apt-get update && apt-get install -y mosquitto mosquitto-clients certbot &&
ufw allow 8883/tcp && ufw allow 80/tcp &&
printf '%s\n' 'set -e' 'K=/etc/mosquitto/certs' 'install -d -o mosquitto -g mosquitto -m 700 $K' \
 'install -m 600 -o mosquitto -g mosquitto $RENEWED_LINEAGE/fullchain.pem $K/server.pem' \
 'install -m 600 -o mosquitto -g mosquitto $RENEWED_LINEAGE/privkey.pem $K/server.key' \
 'pkill -HUP -x mosquitto || true' > /usr/local/sbin/mosquitto-certs.sh &&
certbot certonly --standalone -n --agree-tos -m "$E" -d "$N" \
 --deploy-hook "sh /usr/local/sbin/mosquitto-certs.sh" &&
test -s /etc/mosquitto/certs/server.pem && echo "step one done" || echo "STOP - something above did not work"
fi

Step two: tell the broker who may say what

Next you swap the package's settings file (kept as mosquitto.conf.dist) for a short one: no anonymous access, a password file and access list, saved messages in /var/lib/mosquitto/, and the two listeners from the table. Our picks, not defaults: a save every 300 seconds rather than 1800, so a crash costs a few minutes of retained messages at most, and forgetting clients absent 14 days (the default is never). With no include_dir line, conf.d is ignored; with no log_dest, logs land in journalctl -u mosquitto.

In the access list, %u means the username that connected: a sensor signed in as kitchen may publish and subscribe under sensors/kitchen/ and nowhere else, while display reads every sensor. The block makes random passwords for both, saves them in /root/mqtt-accounts.txt (root only) and hands them to mosquitto_passwd -b, which the manual warns exposes a password on the command line; here your history gets only the variable name, and the process list the value for an instant.

# Run as root after "step one done". Refuses a second run. Creates two accounts, starts the broker.
C=/etc/mosquitto/mosquitto.conf; S=/root/mqtt-accounts.txt; W=/etc/mosquitto/passwd
if [ -e $C.dist ] || [ -e $S ]; then echo "STOP - $C.dist or $S exists, so this ran before"
else
P1=$(openssl rand -hex 16) && P2=$(openssl rand -hex 16) && [ -n "$P1" ] && [ -n "$P2" ] &&
install -m 600 /dev/null $S && printf 'display %s\nkitchen %s\n' "$P1" "$P2" > $S &&
mv $C $C.dist &&
cat > $C <<'EOF' &&
allow_anonymous false
password_file /etc/mosquitto/passwd
acl_file /etc/mosquitto/acl
persistence true
persistence_location /var/lib/mosquitto/
autosave_interval 300
persistent_client_expiration 14d
listener 1883 127.0.0.1
listener 8883
certfile /etc/mosquitto/certs/server.pem
keyfile /etc/mosquitto/certs/server.key
tls_version tlsv1.2
EOF
printf '%s\n' 'pattern readwrite sensors/%u/#' 'user display' 'topic read sensors/#' > /etc/mosquitto/acl &&
umask 077 && mosquitto_passwd -c -b $W display "$P1" && mosquitto_passwd -b $W kitchen "$P2" &&
chown mosquitto:mosquitto $W /etc/mosquitto/acl && chmod 600 $W /etc/mosquitto/acl &&
install -d -o mosquitto -g mosquitto -m 700 /var/lib/mosquitto &&
systemctl restart mosquitto && systemctl is-active mosquitto && ss -ltn | grep -q '127.0.0.1:1883 ' &&
! ss -ltn | grep -Eq '(0\.0\.0\.0|\[::\]|\*):1883 ' && echo "broker running, 1883 local only" ||
echo "STOP - read journalctl -u mosquitto -n 30"
fi

If it stops part-way, put the original back with cd /etc/mosquitto && mv mosquitto.conf.dist mosquitto.conf, remove /root/mqtt-accounts.txt, and run it again.

Try it with two terminals. Replace broker.example.net with your broker's name, then watch over TLS: P=$(grep '^display ' /root/mqtt-accounts.txt | cut -d' ' -f2) && mosquitto_sub -L "mqtts://display:$P@broker.example.net/sensors/#" -v. Since 2.0 the clients trust the system's certificate authorities for mqtts://. Then send a reading as the sensor: P=$(grep '^kitchen ' /root/mqtt-accounts.txt | cut -d' ' -f2) && mosquitto_pub -L "mqtt://kitchen:$P@127.0.0.1/sensors/kitchen/temp" -m 21.5. It appears in the first terminal; one aimed at sensors/hall/temp by kitchen should not. Store both passwords in your password manager, then delete the file.

New device later? mosquitto_passwd /etc/mosquitto/passwd hall && chmod 600 /etc/mosquitto/passwd && chown mosquitto: /etc/mosquitto/passwd && pkill -HUP -x mosquitto; the authentication guide says the broker rereads the file on that signal. Password files are meant for a modest, stable list of users; for users, groups and roles you manage remotely, the guide suggests the dynamic security plugin. Browser clients need one more listener with protocol websockets and the certificate lines.

A nightly copy, and getting it back

Everything the broker remembers lives in /etc/mosquitto (settings, users, rules, certificate copies) and /var/lib/mosquitto/mosquitto.db (subscriptions, queues and retained messages). The manual says SIGUSR1 makes the broker write that file at once, so the nightly job sends that signal, pauses five seconds, packs both folders with the hook and the job, confirms mosquitto.db made it into the archive, and only then deletes copies older than ten days. Your private key and password hashes are inside, so the folder is mode 700.

# Run as root. Creates /etc/cron.d/mqtt-backup: every night at 04:10, ten days kept, old copies removed only after a good one.
install -d -m 700 /var/backups/mqtt &&
printf '%s\n' 'SHELL=/bin/bash' 'PATH=/usr/sbin:/usr/bin:/sbin:/bin' \
 '10 4 * * * root umask 077; B=/var/backups/mqtt; T=$(date +\%F-\%H\%M\%S); pkill -USR1 -x mosquitto; sleep 5; tar -czf $B/broker-$T.tgz -C / etc/mosquitto var/lib/mosquitto usr/local/sbin/mosquitto-certs.sh etc/cron.d/mqtt-backup && tar -tzf $B/broker-$T.tgz | grep -q mosquitto.db && find $B -name "broker-*.tgz" -mtime +10 -delete || echo "$T backup FAILED, see $B" >> $B/errors.log' \
 > /etc/cron.d/mqtt-backup && chmod 644 /etc/cron.d/mqtt-backup && echo "nightly copy scheduled" || echo "STOP - the job was not created"

Next day, /var/backups/mqtt should hold one archive and no errors.log. Pull copies to another machine with rsync -a (never --delete). Moving a broker from elsewhere works the same way; our migration page covers the cut-over. To bring a copy back on a fresh server, as root:

  1. Run step one only, put an archive in /root, put its name in A, such as A=/root/broker-2026-11-01-041000.tgz, and test it: tar -tzf "$A" | grep -q var/lib/mosquitto/mosquitto.db && echo ok. No ok, no restore.
  2. Move an old /root/before or /root/unpacked away first. Then keep what is there now: `[ ! -e /root/before ] && [ ! -e /root/unpacked ] && systemctl stop mosquitto && mkdir -m 700 /root/before && cp -a /etc/mosquitto /root/before/etc-mosquitto && cp -a /var/lib/mosquitto /root/before/lib-mosquitto && test -e /root/before/etc-mosquitto/mosquitto.conf && echo kept || echo "STOP - nothing kept, do not go on"`.
  3. In that shell, unpack to a side folder and copy in what you need: `test -e /root/before/etc-mosquitto/mosquitto.conf && mkdir -m 700 /root/unpacked && tar -xzf "$A" -C /root/unpacked && cd /root/unpacked/etc/mosquitto && install -m 600 -o mosquitto -g mosquitto passwd acl /etc/mosquitto/ && install -m 644 mosquitto.conf /etc/mosquitto/ && install -m 600 -o mosquitto -g mosquitto ../../var/lib/mosquitto/mosquitto.db /var/lib/mosquitto/ && cp ../cron.d/mqtt-backup /etc/cron.d/ && systemctl start mosquitto && sleep 1 && systemctl is-active --quiet mosquitto && echo restored || echo "STOP - go back with step 4"`. The certificate from step one stays.
  4. Changed your mind? systemctl stop mosquitto && cp -a /root/before/etc-mosquitto/. /etc/mosquitto/ && cp -a /root/before/lib-mosquitto/. /var/lib/mosquitto/ && systemctl start mosquitto.

Updates, the licence, and when to pick something else

Before an update, copy mosquitto.db aside: a newer broker may save it in a format an older one cannot read, the manual warns. Then apt update && apt install --only-upgrade mosquitto mosquitto-clients. When your package reaches 2.1, read that ChangeLog entry first: the packet limit falls to 2,000,000 bytes, a broken password file or access list on reload now stops the broker, and the per_listener_settings, acl_file and password_file options are deprecated in favour of plugins, due to go in 3.0. They still work in 2.1, which adds mosquitto --test-config.

On the licence, in our general reading and not legal advice: Mosquitto's LICENSE.txt offers two licences and you choose, the EPL 2.0 or the EDL 1.0 (the EDL being what SPDX calls BSD-3-Clause). The EDL is permissive: pass copies on, changed or not, and you keep the copyright notice, the list of conditions and the disclaimer; supplying source is not required. The EPL is weak copyleft: distribute the program in any form under it and its source must be made available under the EPL too. Running the broker that your devices, or your customers' devices, connect to hands nobody a copy.

Know its limits. One broker on one server can fail on its own; the bridge options in mosquitto.conf pass chosen topics to a second broker, which in our view connects brokers rather than mirroring one. And if what you really have is applications handing work to each other, with queues and per-message acknowledgements, a RabbitMQ server is likely the better tool.

Questions people ask

Clients connect on the server but not from outside. What is wrong?
Probably no listener is configured, so Mosquitto 2 listens on 127.0.0.1 and ::1 only. Add one, give each client a username (anonymous ones are then refused), and check that ufw allows the port.
Is port 1883 safe to open if every client has a password?
Not really, because on plain 1883 the password itself travels unencrypted and anyone on the path can read it. Bind 1883 to the loopback address, or one on a private network, and send outside clients to 8883 with TLS, as this page does.
Can I use my own certificate authority instead of Let's Encrypt?
Yes. The mosquitto-tls manual gives the openssl commands for a CA, a server key and a signed server certificate, and warns that CA, server and client certificates need different subject details. Point certfile and keyfile at your server files; every client then needs your CA certificate, because it is not in their system store.

Give your devices a broker of their own

Choose a server, put Ubuntu 24.04 or 26.04 on it, aim your broker's name at it, then follow steps one and two.

Get a server for your broker

Read next