IoT & Messaging
    Open Source

    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

    ProjectEclipse Mosquitto 2.x
    LicenseEPL/EDL
    Recommended PlanRamNode Cloud VPS 1 GB+ (handles tens of thousands of idle clients)
    OSUbuntu 24.04 LTS or Debian 12
    ProtocolsMQTT 3.1.1 / 5.0, MQTT-over-TLS, WebSockets
    Estimated Setup Time30–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
    1

    Prepare the Server

    Update and install dependencies
    apt update && apt full-upgrade -y
    apt install -y ufw curl gnupg ca-certificates software-properties-common
    Configure UFW
    sudo 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 enable

    Port 1883 is opened temporarily for initial testing. We'll close it once TLS is verified.

    2

    Install Mosquitto

    Distro package (stable)
    sudo apt install -y mosquitto mosquitto-clients
    Or the official PPA for 2.1.x
    sudo add-apt-repository ppa:mosquitto-dev/mosquitto-ppa
    sudo apt update
    sudo apt install -y mosquitto mosquitto-clients

    By 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.

    3

    Build the Configuration

    Use a drop-in file under /etc/mosquitto/conf.d/ so package upgrades don't clobber your work.

    /etc/mosquitto/conf.d/default.conf
    # 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 mqtt
    4

    Create Users with mosquitto_passwd

    Create the password file
    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/passwd

    The -c flag creates the file — drop it for additional users so existing entries aren't overwritten.

    5

    ACLs for Topic-Level Permissions

    Without ACLs, every authenticated user can read and write every topic — almost never what you want for IoT fleets.

    /etc/mosquitto/aclfile
    # 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/#
    Apply
    sudo chown mosquitto:mosquitto /etc/mosquitto/aclfile
    sudo chmod 0640 /etc/mosquitto/aclfile
    sudo systemctl restart mosquitto
    sudo systemctl status mosquitto
    6

    Quick Local Test

    Subscribe (terminal 1)
    mosquitto_sub -h localhost -t 'test/hello' -u admin -P 'yourpassword' -v
    Publish (terminal 2)
    mosquitto_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.

    7

    TLS with Let's Encrypt

    Issue the certificate
    sudo apt install -y certbot
    sudo certbot certonly --standalone -d mqtt.example.com \
      --agree-tos --no-eff-email -m you@example.com

    Mosquitto can't read Let's Encrypt's files directly — use a renewal hook to copy them with the right ownership.

    /etc/letsencrypt/renewal-hooks/deploy/mosquitto.sh
    #!/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 mosquitto
    Make executable and run once
    sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/mosquitto.sh
    sudo /etc/letsencrypt/renewal-hooks/deploy/mosquitto.sh
    Append TLS listener to default.conf
    # 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.2
    Verify from a remote machine
    mosquitto_sub -h mqtt.example.com -p 8883 -t 'test/#' \
      -u admin -P 'yourpassword' --capath /etc/ssl/certs/ -v
    8

    WebSockets for Browser Clients

    Append to default.conf
    # 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.2

    Browser clients connect to wss://mqtt.example.com:8083/mqtt.

    9

    Close Plaintext Port 1883

    Drop the firewall rule
    sudo ufw delete allow 1883/tcp

    In default.conf, either comment out the listener 1883 block or rebind to localhost only:

    Localhost-only listener
    listener 1883 127.0.0.1
    10

    Tuning, Monitoring & Bridging

    Production tuning in default.conf
    max_connections -1
    message_size_limit 1048576
    max_queued_messages 1000
    persistent_client_expiration 30d

    Subscribe 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 debug and look for "ACL denying access"
    • TLS handshake failures: use fullchain.pem (not cert.pem); verify the mosquitto user can read the key
    • Renewals don't propagate: run sudo certbot renew --dry-run and 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