Skip to content

MQTT Brokers Guide

Django DeviceHub communicates with IoT devices through MQTT brokers. This guide covers broker configuration, EMQX and Mosquitto setup, TLS, shared subscriptions, multi-broker setups, and the built-in HTTP authentication endpoints.

Broker Configuration

Brokers are configured in DJANGO_DEVICEHUB["BROKERS"]. Each entry is a named broker with its connection parameters:

DJANGO_DEVICEHUB = {
    "BROKERS": {
        "default": {
            "ENGINE": "django_devicehub.brokers.mqtt.MQTTBroker",
            "HOST": "localhost",
            "PORT": 1883,
            "USERNAME": "",
            "PASSWORD": "",
            "CLIENT_ID": "",
            "KEEPALIVE": 60,
            "USE_TLS": False,
            "CA_CERTS": "",
            "CERTFILE": "",
            "KEYFILE": "",
            "USE_SHARED_SUBSCRIPTIONS": True,
            "SHARE_GROUP": "django-devicehub",
            "AUTH_BACKEND": False,
        }
    },
}

Configuration keys

Key Type Default Description
ENGINE str "django_devicehub.brokers.mqtt.MQTTBroker" Broker backend class
HOST str "localhost" Broker hostname
PORT int 1883 Broker port (1883 plain, 8883 TLS)
USERNAME str "" Server-side credentials for the Django listener
PASSWORD str "" Server-side password
CLIENT_ID str "" MQTT client ID (auto-generated if empty)
KEEPALIVE int 60 MQTT keepalive interval in seconds
USE_TLS bool False Enable TLS encryption
CA_CERTS str "" Path to CA certificate file
CERTFILE str "" Path to client certificate (mutual TLS)
KEYFILE str "" Path to client private key (mutual TLS)
USE_SHARED_SUBSCRIPTIONS bool True Use MQTT 5 shared subscriptions
SHARE_GROUP str "django-devicehub" Shared subscription group name
AUTH_BACKEND bool False Enable HTTP auth endpoints for this broker

EMQX Setup

EMQX is recommended for production. It supports MQTT 5.0, shared subscriptions, clustering, and HTTP authentication natively.

Docker Compose

services:
  emqx:
    image: emqx/emqx:5.8
    ports:
      - "1883:1883"     # MQTT
      - "8083:8083"     # WebSocket
      - "18083:18083"   # Dashboard
    environment:
      EMQX_NAME: devicehub

HTTP Authentication

EMQX can delegate device authentication to Django DeviceHub via HTTP. Enable AUTH_BACKEND in your broker config:

DJANGO_DEVICEHUB = {
    "BROKERS": {
        "default": {
            "HOST": "emqx",
            "PORT": 1883,
            "AUTH_BACKEND": True,
        }
    },
}

Add the auth URLs to your project:

# urls.py
urlpatterns = [
    path("iot/", include("django_devicehub.urls")),
]

This exposes two endpoints:

  • POST /iot/broker/auth/ -- authenticate device credentials
  • POST /iot/broker/acl/ -- authorize topic access

Configure EMQX to use these endpoints (via EMQX Dashboard or config file):

# emqx.conf
authentication = [
  {
    mechanism = password_based
    backend = http
    method = post
    url = "http://django-app:8000/iot/broker/auth/"
    body {
      username = "${username}"
      password = "${password}"
    }
    headers {
      content-type = "application/json"
    }
  }
]

authorization {
  sources = [
    {
      type = http
      method = post
      url = "http://django-app:8000/iot/broker/acl/"
      body {
        username = "${username}"
        topic = "${topic}"
        action = "${action}"
      }
      headers {
        content-type = "application/json"
      }
    }
  ]
}

How the auth endpoints work

Authentication (BrokerAuthView): Receives {"username": "...", "password": "..."}, searches all registered device models for a matching mqtt_username, verifies the password hash, and returns {"result": "allow"} or {"result": "deny"}. On success, the device is marked online.

ACL (BrokerACLView): Receives {"username": "...", "topic": "...", "action": "pub"|"sub"}. Devices can only:

  • Publish to their own data and status topics
  • Subscribe to their own cmd topic

This prevents devices from reading other devices' data or sending unauthorized commands.

Mosquitto Setup

Mosquitto is a lightweight alternative suitable for development and small deployments.

Docker Compose

services:
  mosquitto:
    image: eclipse-mosquitto:2
    ports:
      - "1883:1883"
    volumes:
      - ./mosquitto.conf:/mosquitto/config/mosquitto.conf

Basic config

# mosquitto.conf
listener 1883
allow_anonymous true   # for development only

For production, use the mosquitto-auth-plug or mosquitto-go-auth plugin to delegate authentication to Django DeviceHub's HTTP endpoints.

TLS Configuration

Enable TLS to encrypt communication between devices and the broker:

DJANGO_DEVICEHUB = {
    "BROKERS": {
        "default": {
            "HOST": "mqtt.example.com",
            "PORT": 8883,
            "USE_TLS": True,
            "CA_CERTS": "/path/to/ca.crt",
        }
    },
}

Mutual TLS (mTLS)

For client certificate authentication, provide both the certificate and key:

DJANGO_DEVICEHUB = {
    "BROKERS": {
        "default": {
            "HOST": "mqtt.example.com",
            "PORT": 8883,
            "USE_TLS": True,
            "CA_CERTS": "/path/to/ca.crt",
            "CERTFILE": "/path/to/client.crt",
            "KEYFILE": "/path/to/client.key",
        }
    },
}

Shared Subscriptions

MQTT 5.0 shared subscriptions distribute messages across multiple listeners, enabling horizontal scaling. When USE_SHARED_SUBSCRIPTIONS is True (the default), topics are subscribed as:

$share/django-devicehub/things/weatherstation/+/data

This means if you run 3 instances of iot_listen, each message is delivered to exactly one instance rather than all three.

Customizing the share group

DJANGO_DEVICEHUB = {
    "BROKERS": {
        "default": {
            "USE_SHARED_SUBSCRIPTIONS": True,
            "SHARE_GROUP": "my-app-workers",
        }
    },
}

Disabling shared subscriptions

For single-instance deployments or brokers that do not support MQTT 5.0:

"USE_SHARED_SUBSCRIPTIONS": False,

Multiple Brokers

You can configure multiple brokers for different device types or environments:

DJANGO_DEVICEHUB = {
    "BROKERS": {
        "default": {
            "HOST": "mqtt.internal.example.com",
            "PORT": 1883,
        },
        "lorawan": {
            "HOST": "lora-broker.example.com",
            "PORT": 8883,
            "USE_TLS": True,
            "CA_CERTS": "/path/to/ca.crt",
        },
        "cloud": {
            "HOST": "mqtt.cloud-provider.com",
            "PORT": 8883,
            "USE_TLS": True,
            "USERNAME": "cloud-user",
            "PASSWORD": "cloud-pass",
        },
    },
}

Assign device types to specific brokers via Meta:

class LoRaNode(DeviceType):
    class Meta:
        broker = "lorawan"

class CloudSensor(DeviceType):
    class Meta:
        broker = "cloud"

Start listeners for specific brokers:

python manage.py iot_listen --broker default
python manage.py iot_listen --broker lorawan
python manage.py iot_listen --broker cloud

MQTTBroker API

The MQTTBroker class wraps paho-mqtt with MQTT 5.0 support. Key methods:

from django_devicehub.brokers.mqtt import MQTTBroker

broker = MQTTBroker(name="default")

broker.connect()                          # connect to the configured host
broker.subscribe("things/+/+/data")       # subscribe to a topic pattern
broker.publish("things/ws/001/cmd",       # publish a message
               {"command": "reboot"},
               qos=1,
               correlation_id="abc-123",
               user_properties={"source": "admin"})
broker.loop_start()                       # start background message processing
broker.loop_stop()                        # stop background thread
broker.loop_forever()                     # blocking message loop
broker.disconnect()                       # disconnect from broker
broker.set_message_handler(callback)      # set the on_message callback

The broker automatically subscribes to all registered device type topics on connect via _subscribe_all().

Writing a Custom Broker

Extend BaseBroker to support other protocols:

from django_devicehub.brokers.base import BaseBroker

class CoapBroker(BaseBroker):
    def connect(self):
        ...

    def disconnect(self):
        ...

    def subscribe(self, topic, qos=1):
        ...

    def publish(self, topic, payload, qos=1, **kwargs):
        ...

    def loop_forever(self):
        ...

    def loop_start(self):
        ...

    def loop_stop(self):
        ...

Register it in settings:

DJANGO_DEVICEHUB = {
    "BROKERS": {
        "coap": {
            "ENGINE": "myapp.brokers.CoapBroker",
            "HOST": "coap.example.com",
            "PORT": 5683,
        }
    },
}