MQTT is the backbone of modern IoT communication, and learning to use MQTT with ESP32 unlocks a whole ecosystem of connected sensors, dashboards, and automation. Unlike HTTP, which is request-response and stateful, MQTT is a lightweight publish/subscribe protocol that keeps persistent connections to a broker and delivers messages in near-real time with minimal overhead. This tutorial walks you through everything from installing a Mosquitto broker to publishing DHT11 temperature data from an ESP32 and visualising it in Node-RED.
Table of Contents
What is MQTT? The Publish/Subscribe Model Explained
MQTT (Message Queuing Telemetry Transport) was originally designed at IBM for monitoring oil pipelines over satellite connections in the 1990s. Its design goals were simple: minimal bandwidth, low battery usage, and reliable delivery over unreliable networks. Today it is the standard messaging protocol for IoT.
The core concept is the broker: a central server that all devices connect to. Devices can publish messages to a named topic (e.g., home/livingroom/temperature), and any other device that has subscribed to that topic receives the message instantly. The publisher and subscriber never need to know about each other – they only know about topics.
This decoupled architecture makes IoT systems modular and scalable. You can add a new subscriber (say, a phone app) without changing any of your sensor nodes at all.
Why MQTT for IoT?
Compared to HTTP REST APIs, MQTT offers several advantages for IoT deployments:
- Lightweight: The minimum MQTT packet header is just 2 bytes. An HTTP request header alone is hundreds of bytes.
- Persistent connection: The ESP32 maintains a single TCP connection to the broker. HTTP requires a new connection per request (unless using HTTP/2), which is expensive in CPU and time.
- Push delivery: The broker pushes messages to subscribers instantly. HTTP polling wastes bandwidth and power.
- QoS guarantees: MQTT supports three Quality of Service levels for message delivery guarantees (explained below).
- Last Will and Testament (LWT): You can configure the broker to publish a message automatically if your device disconnects unexpectedly – perfect for detecting sensor node failures.
Setting Up Mosquitto Broker
Mosquitto is the most widely used open-source MQTT broker. You can run it on a Raspberry Pi (recommended for a home server), on a Linux VPS, or use a free cloud broker like HiveMQ Cloud.
Install on Raspberry Pi / Ubuntu:
# Install Mosquitto
sudo apt update
sudo apt install mosquitto mosquitto-clients
# Enable and start
sudo systemctl enable mosquitto
sudo systemctl start mosquitto
# Create a password file
sudo mosquitto_passwd -c /etc/mosquitto/passwd myuser
# Enter password when prompted
# Edit config: /etc/mosquitto/mosquitto.conf
# Add these lines:
listener 1883
allow_anonymous false
password_file /etc/mosquitto/passwd
# Restart broker
sudo systemctl restart mosquitto
# Test from another terminal
mosquitto_sub -h localhost -t "test/#" -u myuser -P mypassword
# In another terminal:
mosquitto_pub -h localhost -t "test/hello" -m "Hello MQTT" -u myuser -P mypassword
Note your Raspberry Pi’s local IP address (e.g., 192.168.1.100) – this is the broker address you will use in your ESP32 sketch. Use hostname -I to find it.
ESP32 MQTT Client with PubSubClient
The most popular MQTT library for Arduino/ESP32 is PubSubClient by Nick O’Leary. Install it via the Arduino IDE Library Manager: Sketch > Include Library > Manage Libraries > search “PubSubClient”.
Basic connection template:
#include <WiFi.h>
#include <PubSubClient.h>
const char* ssid = "YourWiFiSSID";
const char* password = "YourWiFiPassword";
const char* mqttServer = "192.168.1.100"; // Raspberry Pi IP
const int mqttPort = 1883;
const char* mqttUser = "myuser";
const char* mqttPass = "mypassword";
WiFiClient espClient;
PubSubClient client(espClient);
void reconnect() {
while (!client.connected()) {
Serial.print("Connecting to MQTT...");
if (client.connect("ESP32Client", mqttUser, mqttPass)) {
Serial.println("connected!");
client.subscribe("home/bedroom/cmd"); // Subscribe to control topic
} else {
Serial.printf("failed (rc=%d), retrying in 5s
", client.state());
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) delay(500);
Serial.println("WiFi connected. IP: " + WiFi.localIP().toString());
client.setServer(mqttServer, mqttPort);
}
void loop() {
if (!client.connected()) reconnect();
client.loop(); // Handle incoming messages
}
Publishing DHT11 Sensor Data
Now let us add a DHT11 temperature and humidity sensor to the ESP32 and publish readings to MQTT topics every 30 seconds.
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <ArduinoJson.h> // Library Manager: ArduinoJson by Benoit Blanchon
#define DHTPIN 4
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
// ... (WiFi/MQTT constants from previous example)
WiFiClient espClient;
PubSubClient client(espClient);
void publishSensorData() {
float temp = dht.readTemperature();
float hum = dht.readHumidity();
if (isnan(temp) || isnan(hum)) return;
StaticJsonDocument<128> doc;
doc["temperature"] = temp;
doc["humidity"] = hum;
doc["device"] = "bedroom1";
char payload[128];
serializeJson(doc, payload);
client.publish("home/bedroom1/sensors", payload, true); // retained=true
Serial.printf("Published: %s
", payload);
}
unsigned long lastPublish = 0;
void loop() {
if (!client.connected()) reconnect();
client.loop();
if (millis() - lastPublish > 30000) { // Every 30 seconds
publishSensorData();
lastPublish = millis();
}
}
Setting the retained flag to true is important: it means the broker stores the last message on this topic, so any new subscriber (like a phone app opening for the first time) immediately receives the current temperature value without waiting for the next publish cycle.
Subscribing to Control Commands
The real power of MQTT comes when the ESP32 also listens for commands. Add a callback function to handle incoming messages on the subscribed topic:
#define RELAY_PIN 26
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String msg;
for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
Serial.printf("[%s] %s
", topic, msg.c_str());
if (String(topic) == "home/bedroom1/relay") {
if (msg == "ON") {
digitalWrite(RELAY_PIN, LOW);
} else if (msg == "OFF") {
digitalWrite(RELAY_PIN, HIGH);
}
}
}
void setup() {
// ... WiFi setup ...
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, HIGH);
client.setServer(mqttServer, mqttPort);
client.setCallback(mqttCallback); // Register callback
}
// In reconnect(), subscribe to the relay command topic:
// client.subscribe("home/bedroom1/relay");
Now you can control the relay from any MQTT client – your phone, Node-RED, Home Assistant, or a Python script – by publishing "ON" or "OFF" to home/bedroom1/relay.
QoS Levels Explained
MQTT offers three Quality of Service levels that control message delivery guarantees:
- QoS 0 – At most once (fire and forget): The message is sent once with no acknowledgment. If the network drops the packet, the message is lost. Best for: high-frequency sensor data where occasional loss is acceptable (temperature readings every 5 seconds).
- QoS 1 – At least once: The broker acknowledges receipt (PUBACK). If no ack is received, the message is retransmitted. The subscriber may receive duplicates. Best for: control commands where missing a command would be problematic but receiving it twice is harmless.
- QoS 2 – Exactly once: A 4-way handshake (PUBLISH, PUBREC, PUBREL, PUBCOMP) guarantees exactly one delivery. Highest overhead. Best for: financial transactions, billing events, or commands where a duplicate would cause real harm (e.g., triggering an alarm).
In PubSubClient, specify QoS in the publish call: client.publish(topic, payload, retained) defaults to QoS 0. For QoS 1, use the overloaded form: client.publish(topic, payload, length, retained) – PubSubClient currently supports QoS 0 and 1 only; QoS 2 requires a more capable library like Arduino-MQTT or esp-mqtt.
Node-RED Dashboard
Node-RED is a visual, browser-based programming tool that runs on Node.js. It makes building IoT dashboards trivially easy by dragging and dropping nodes on a canvas.
Installation on Raspberry Pi:
# Install Node-RED (official script handles Node.js too)
bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered)
# Install dashboard module
cd ~/.node-red
npm install node-red-dashboard
# Start Node-RED
node-red-start
# Access at: http://<RPi-IP>:1880
In Node-RED: drag in an mqtt in node, configure your broker (server, port, username, password), set the topic to home/bedroom1/sensors. Connect it to a json parser node, then to a gauge dashboard widget for temperature and another for humidity. Also add an mqtt out node connected to a button dashboard widget to send relay ON/OFF commands. Deploy – your dashboard is live at http://<RPi-IP>:1880/ui.
MQTT Topic Best Practices
Well-designed topic structures make large IoT deployments manageable. Follow these conventions:
- Use forward slashes for hierarchy:
home/floor1/bedroom1/temperaturerather than flat names. - Include device type and location:
home/{room}/{sensor_type} - Separate commands from telemetry: Use
home/bedroom1/sensorsfor data andhome/bedroom1/cmd/relayfor commands. - Use wildcards for subscriptions:
home/#subscribes to all topics under home.home/+/temperaturesubscribes to temperature from all rooms (single level wildcard). - Avoid leading slashes:
/home/bedroom1creates an empty first segment – avoid this. - Keep topics lowercase: Topic names are case-sensitive; consistent lowercase prevents bugs.
- Status topics: Use
home/bedroom1/statusas the Last Will and Testament topic with payload"offline"– the broker publishes this if your device disconnects unexpectedly.
Security: TLS and Authentication
By default, MQTT transmits data in plain text on port 1883. For any deployment beyond a purely local network, you must add security:
Password authentication: Already covered above with Mosquitto’s password file. Always enable this even on local networks – your neighbors can connect to your broker if they are on the same WiFi network.
TLS encryption (MQTTS, port 8883): Use Mosquitto with a Let’s Encrypt certificate (or self-signed for LAN). On the ESP32 side, use the WiFiClientSecure library and load the CA certificate. TLS adds ~4KB overhead per handshake but fully encrypts all sensor data and commands.
Cloud MQTT brokers: If you do not want to manage your own broker, HiveMQ Cloud (free tier: 100 connections, 10GB/month) and Adafruit IO (free: 30 feeds, 30 messages/minute) provide TLS-secured MQTT endpoints with zero setup. Just change your broker address, port (8883), and credentials in the ESP32 sketch.
Frequently Asked Questions
Q: What is the maximum message size in MQTT?
The MQTT specification allows messages up to 268 MB, but in practice ESP32 and embedded devices are limited by RAM. PubSubClient defaults to a 256-byte buffer – increase it with client.setBufferSize(512) before connecting if you need to publish larger payloads like JSON with many fields.
Q: Can two ESP32 devices communicate directly via MQTT without a broker?
No, MQTT requires a broker. However, you can run a minimal broker on one of the ESP32s using the WEMOS or uMQTTBroker library, but this is unstable for production use. For direct ESP32-to-ESP32 communication without a broker, use ESP-NOW (Espressif proprietary), which enables direct peer-to-peer messaging with latency under 1 ms and no infrastructure required.
Q: How do I handle MQTT reconnection without blocking the loop?
Replace the while(!client.connected()) blocking reconnect loop with a non-blocking approach: check millis() and attempt reconnect only every 5 seconds. This keeps your sensor reading and other loop tasks running even when the broker is temporarily unreachable.
Q: Is MQTT compatible with Home Assistant?
Yes. Home Assistant has native MQTT integration and supports MQTT Discovery – if your ESP32 publishes a configuration payload to homeassistant/sensor/{device_id}/config, Home Assistant automatically creates the entity in its UI. This is the recommended way to integrate DIY IoT devices with Home Assistant without writing any YAML configuration.
Q: What happens to retained messages when I restart the broker?
Mosquitto persists retained messages to disk by default (persistence true in config, stored in /var/lib/mosquitto/mosquitto.db). Retained messages survive broker restarts, so subscribers always get the last known value even after a broker reboot.
Build Your IoT Project
Shop ESP32, sensors, and wireless modules at Zbotic.in – fast shipping across India.
Add comment