Deploy Mosquitto MQTT Broker on a VPS
A hardened Eclipse Mosquitto deployment for IoT, telemetry, and pub/sub workloads — with password auth, ACLs, TLS, and WebSockets configured the right way from the start.
At a Glance
| Project | Eclipse Mosquitto 2.x |
| License | EPL/EDL |
| Recommended Plan | RamNode Cloud VPS 1 GB+ (handles tens of thousands of idle clients) |
| OS | Ubuntu 24.04 LTS or Debian 12 |
| Protocols | MQTT 3.1.1 / 5.0, MQTT-over-TLS, WebSockets |
| Estimated Setup Time | 30–45 minutes |
Prerequisites
- A RamNode VPS running Ubuntu 24.04 or Debian 12
- Root or sudo access
- A DNS A record (e.g.
mqtt.example.com) pointing at the VPS - Ports 8883 and 8083 reachable from the internet
Prepare the Server
apt update && apt full-upgrade -y
apt install -y ufw curl gnupg ca-certificates software-properties-commonsudo ufw allow OpenSSH
sudo ufw allow 1883/tcp comment 'MQTT plaintext (test only)'
sudo ufw allow 8883/tcp comment 'MQTT TLS'
sudo ufw allow 8083/tcp comment 'MQTT WebSockets TLS'
sudo ufw allow 80/tcp comment 'HTTP for certbot'
sudo ufw enablePort 1883 is opened temporarily for initial testing. We'll close it once TLS is verified.
Install Mosquitto
sudo apt install -y mosquitto mosquitto-clientssudo add-apt-repository ppa:mosquitto-dev/mosquitto-ppa
sudo apt update
sudo apt install -y mosquitto mosquitto-clientsBy default Mosquitto 2.x binds only to 127.0.0.1:1883 and refuses anonymous connections. That's a security improvement over 1.x — we'll open it deliberately below.
Build the Configuration
Use a drop-in file under /etc/mosquitto/conf.d/ so package upgrades don't clobber your work.
# Persistence
persistence true
persistence_location /var/lib/mosquitto/
# Logging
log_dest file /var/log/mosquitto/mosquitto.log
log_type error
log_type warning
log_type notice
log_type information
connection_messages true
log_timestamp true
# Authentication
allow_anonymous false
password_file /etc/mosquitto/passwd
# Topic-level ACLs
acl_file /etc/mosquitto/aclfile
# Plain TCP (locked down later)
listener 1883
protocol mqttCreate Users with mosquitto_passwd
sudo mosquitto_passwd -c /etc/mosquitto/passwd admin
sudo mosquitto_passwd /etc/mosquitto/passwd sensor01
sudo mosquitto_passwd /etc/mosquitto/passwd dashboard
sudo chown mosquitto:mosquitto /etc/mosquitto/passwd
sudo chmod 0640 /etc/mosquitto/passwdThe -c flag creates the file — drop it for additional users so existing entries aren't overwritten.
ACLs for Topic-Level Permissions
Without ACLs, every authenticated user can read and write every topic — almost never what you want for IoT fleets.
# Admin: full access
user admin
topic readwrite #
# Per-device subtree + a shared command channel
user sensor01
topic readwrite devices/sensor01/#
topic read commands/sensor01
# Dashboard: read-only on telemetry and broker stats
user dashboard
topic read devices/#
topic read $SYS/#sudo chown mosquitto:mosquitto /etc/mosquitto/aclfile
sudo chmod 0640 /etc/mosquitto/aclfile
sudo systemctl restart mosquitto
sudo systemctl status mosquittoQuick Local Test
mosquitto_sub -h localhost -t 'test/hello' -u admin -P 'yourpassword' -vmosquitto_pub -h localhost -t 'test/hello' -m 'it works' -u admin -P 'yourpassword'If you get Connection Refused: not authorised, double-check the password and that allow_anonymous false is set consistently.
TLS with Let's Encrypt
sudo apt install -y certbot
sudo certbot certonly --standalone -d mqtt.example.com \
--agree-tos --no-eff-email -m you@example.comMosquitto can't read Let's Encrypt's files directly — use a renewal hook to copy them with the right ownership.
#!/bin/bash
set -e
DOMAIN="mqtt.example.com"
CERT_DIR="/etc/letsencrypt/live/${DOMAIN}"
TARGET_DIR="/etc/mosquitto/certs"
mkdir -p "${TARGET_DIR}"
cp "${CERT_DIR}/fullchain.pem" "${TARGET_DIR}/fullchain.pem"
cp "${CERT_DIR}/privkey.pem" "${TARGET_DIR}/privkey.pem"
chown -R mosquitto:mosquitto "${TARGET_DIR}"
chmod 0640 "${TARGET_DIR}/fullchain.pem" "${TARGET_DIR}/privkey.pem"
systemctl reload mosquitto || systemctl restart mosquittosudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/mosquitto.sh
sudo /etc/letsencrypt/renewal-hooks/deploy/mosquitto.sh# MQTT over TLS
listener 8883
protocol mqtt
cafile /etc/ssl/certs/ca-certificates.crt
certfile /etc/mosquitto/certs/fullchain.pem
keyfile /etc/mosquitto/certs/privkey.pem
tls_version tlsv1.2mosquitto_sub -h mqtt.example.com -p 8883 -t 'test/#' \
-u admin -P 'yourpassword' --capath /etc/ssl/certs/ -vWebSockets for Browser Clients
# Secure WebSockets
listener 8083
protocol websockets
cafile /etc/ssl/certs/ca-certificates.crt
certfile /etc/mosquitto/certs/fullchain.pem
keyfile /etc/mosquitto/certs/privkey.pem
tls_version tlsv1.2Browser clients connect to wss://mqtt.example.com:8083/mqtt.
Close Plaintext Port 1883
sudo ufw delete allow 1883/tcpIn default.conf, either comment out the listener 1883 block or rebind to localhost only:
listener 1883 127.0.0.1Tuning, Monitoring & Bridging
max_connections -1
message_size_limit 1048576
max_queued_messages 1000
persistent_client_expiration 30dSubscribe to $SYS/# as the admin user to scrape live broker stats. Useful topics for Prometheus/Telegraf:
$SYS/broker/clients/connected$SYS/broker/messages/received$SYS/broker/load/messages/received/1min$SYS/broker/heap/current
For multi-broker topologies, Mosquitto supports native bridging via the connection directive — useful for edge-and-cloud architectures without paid clustering.
Troubleshooting
- Clients connect then immediately disconnect: usually an ACL mismatch — set
log_type debugand look for "ACL denying access" - TLS handshake failures: use
fullchain.pem(notcert.pem); verify the mosquitto user can read the key - Renewals don't propagate: run
sudo certbot renew --dry-runand watch for the deploy hook output - "socket error on client" floods: usually clients with bad keepalive or NAT timeouts — set keepalive ~60s on the client side
