ESP32 MQTT Tutorial: Complete Guide to Publish-Subscribe IoT Messaging (2026)
If your ESP32 project has outgrown simple HTTP requests and you keep hitting timeouts, dropped connections, or messy polling loops, MQTT is the fix. It's the same lightweight messaging protocol running behind smart home platforms, industrial telemetry systems, and fleet-tracking dashboards — and it runs comfortably on a microcontroller with a few KB of RAM to spare.
This guide walks through everything needed to get an ESP32 talking over MQTT: how the protocol actually works, setting up a broker, writing working publish/subscribe code in Arduino IDE, securing the connection with TLS, and fixing the connection errors almost everyone hits on their first try.
What Is MQTT and Why ESP32 Projects Need It
MQTT (Message Queuing Telemetry Transport) is a publish-subscribe messaging protocol built for constrained devices and unreliable networks. Instead of a device repeatedly asking a server "anything new?" (polling), MQTT flips the model:
- Devices publish messages to named "topics" (like
home/livingroom/temperature) - Other devices or dashboards subscribe to those topics and get pushed the data instantly
- A central broker routes every message between publishers and subscribers
For ESP32 work specifically, MQTT solves three problems that HTTP polling makes painful:
- Battery and bandwidth efficiency — a persistent MQTT connection uses far less overhead than repeated HTTP handshakes.
- Real-time updates — subscribers get pushed data the instant it's published, with no polling delay.
- Many-to-many communication — one sensor can feed ten dashboards, and one dashboard can control fifty devices, all through the same broker.
This is exactly why MQTT sits underneath Home Assistant, Node-RED, and most commercial IoT platforms.
📺 Watch first: if the publish/subscribe concept still feels abstract, this short explainer makes it click fast — What is MQTT? MQTT Essentials Part 1
What You'll Need
- An ESP32 dev board (any variant — ESP32, ESP32-S3, ESP32-C3 all work identically for this)
- Arduino IDE with the ESP32 board package installed
- The
PubSubClientlibrary by Nick O'Leary (install via Library Manager) - An MQTT broker — either a local Mosquitto instance or a free public/cloud broker like HiveMQ Cloud
- A Wi-Fi network with internet access (unless running fully local)
Step 1: Set Up an MQTT Broker
You have two practical options.
Option A — Public test broker (fastest for learning): HiveMQ and EMQX both offer free public brokers for testing. These work instantly but are not private — anyone can subscribe to your topic if they guess it, so never send real credentials or sensitive data through them.
Option B — Local Mosquitto broker (recommended for real projects): Installing Mosquitto on a Raspberry Pi, home server, or your PC gives you a private broker on your own network — the same setup Home Assistant users typically run. On Debian-based systems:
sudo apt update
sudo apt install mosquitto mosquitto-clients
sudo systemctl enable mosquitto
sudo systemctl start mosquittoBy default Mosquitto listens on port 1883 (unencrypted) and 8883 if TLS is configured.
📺 See it running: watching the broker and client talk to each other in real time makes this far less confusing than reading commands alone — MQTT Broker and MQTT Client Explained covers exactly this Mosquitto setup.
Step 2: Wire Up the ESP32 MQTT Code (Publish)
Install PubSubClient from the Arduino Library Manager, then use this as your base publisher — it reads a value (simulated here, swap in your sensor) and publishes it every few seconds.
#include <WiFi.h>
#include <PubSubClient.h>
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* mqtt_server = "192.168.1.100"; // your broker IP
const int mqtt_port = 1883;
const char* topic_pub = "esp32/sensor/temperature";
WiFiClient espClient;
PubSubClient client(espClient);
void setup_wifi() {
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected: " + WiFi.localIP().toString());
}
void reconnect() {
while (!client.connected()) {
Serial.print("Connecting to MQTT...");
String clientId = "ESP32Client-" + String(random(0xffff), HEX);
if (client.connect(clientId.c_str())) {
Serial.println("connected");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" retrying in 5s");
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, mqtt_port);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
static unsigned long lastMsg = 0;
unsigned long now = millis();
if (now - lastMsg > 5000) {
lastMsg = now;
float temperature = 24.5 + random(-10, 10) / 10.0; // replace with real sensor read
char payload[10];
dtostrf(temperature, 4, 2, payload);
client.publish(topic_pub, payload);
Serial.print("Published: ");
Serial.println(payload);
}
}📺 Follow along visually: if you'd rather watch the wiring and upload process before typing anything, this walkthrough covers the same publish/subscribe flow on ESP32/ESP8266 using the Arduino framework — MQTT Tutorial using Arduino Framework
Step 3: Subscribe to a Topic (Receive Commands)
Most real projects need the reverse direction too — controlling the ESP32 remotely. Add a callback function and subscribe to a command topic:
const char* topic_sub = "esp32/led/command";
void callback(char* topic, byte* message, unsigned int length) {
String msg;
for (int i = 0; i < length; i++) {
msg += (char)message[i];
}
Serial.println("Message on topic " + String(topic) + ": " + msg);
if (msg == "ON") {
digitalWrite(LED_BUILTIN, HIGH);
} else if (msg == "OFF") {
digitalWrite(LED_BUILTIN, LOW);
}
}In setup(), register the callback and subscribe after connecting:
client.setCallback(callback);And inside reconnect(), right after the successful client.connect() block:
client.subscribe(topic_sub);Test it from a terminal using the Mosquitto client tools:
mosquitto_pub -h 192.168.1.100 -t "esp32/led/command" -m "ON"If the wiring and code are correct, the onboard LED should switch on instantly.
Understanding QoS Levels
MQTT defines three Quality of Service levels that control delivery guarantees:
- QoS 0 — "fire and forget," no confirmation. Fastest, used for high-frequency sensor data where an occasional dropped reading doesn't matter.
- QoS 1 — "at least once," the broker confirms receipt. The message might arrive more than once, but never zero times. Good default for most ESP32 sensor projects.
- QoS 2 — "exactly once," fully guaranteed but with the most overhead. Reserve this for critical commands like unlocking a door or triggering an alarm.
PubSubClient publishes at QoS 0 by default; for QoS 1, pass true as the retain flag isn't the same thing — QoS handling requires either a broker-side configuration or switching to a library like espMqttClient for full QoS 1/2 support.
Securing MQTT with TLS
Unencrypted MQTT on port 1883 sends topic names and payloads in plain text — fine for local testing, risky for anything internet-facing. To secure it:
- Configure the broker to listen on port 8883 with a valid certificate.
- On the ESP32, swap
WiFiClientforWiFiClientSecureand load the broker's CA certificate. - Update the connection port to 8883 in your
client.setServer()call.
This mirrors the same TLS/SSL hardening approach used for securing any IoT telemetry channel — if you haven't locked down your other ESP32 endpoints yet, that's worth doing across the board, not just for MQTT.
Common ESP32 MQTT Errors and Fixes
- rc=-2 (connection failed): Usually a wrong broker IP or the broker isn't reachable from the ESP32's network. Ping the broker IP from another device on the same network first.
- rc=-4 (connection timeout): Firewall blocking port 1883/8883, or the broker service isn't running.
- rc=5 (not authorized): Broker requires a username/password that wasn't provided in
client.connect(). - Messages not arriving: Double-check topic names are byte-for-byte identical on publisher and subscriber — MQTT topics are case-sensitive and a trailing slash counts as a different topic.
- Random disconnects: Increase the keep-alive interval with
client.setKeepAlive(60)and make sureclient.loop()is called frequently enough in your main loop — longdelay()calls elsewhere will starve it.
Where MQTT Fits Into a Bigger Project
Once an ESP32 is publishing over MQTT, it plugs directly into Home Assistant, Node-RED, Grafana dashboards, or a custom backend with almost no extra code — this is the same messaging layer used in Matter/Thread smart home setups and most production-grade edge AI telemetry pipelines. Getting comfortable with topics, retained messages, and last-will notifications now pays off on every future connected-device project.
Final Thoughts
MQTT looks intimidating from the outside but the core loop — connect, subscribe, publish, callback — is genuinely just a few lines once the broker is running. Start local with Mosquitto, get publish and subscribe working with the code above, then layer on TLS and QoS 1 once the project moves off your desk and onto the network permanently.
Want to put your tech skills to work outside the workshop? If side income is on your radar alongside your hardware projects, it's worth checking out petlifehacks.page — a resource with practical ways to start earning online, laid out step by step for beginners.
