MQTT vs HTTP for IoT: Which Protocol Should Your Firmware Use?
Most IoT tutorials default to HTTP without explaining why. Here's a head-to-head comparison of MQTT and HTTP from a firmware engineer who deployed both.
Every IoT tutorial eventually reaches the moment where sensor data needs to leave the device. Most reach for HTTP without a second thought. After deploying both protocols across dozens of production devices — from industrial sensor gateways to ESP32-based edge nodes — here's the honest comparison.
The Core Difference
HTTP and MQTT solve fundamentally different problems:
| HTTP | MQTT | |
|---|---|---|
| Model | Request / Response | Publish / Subscribe |
| Connection | Short-lived per request | Persistent TCP |
| Overhead per message | ~800 bytes (headers) | ~2 bytes minimum |
| Direction | Client initiates only | Bidirectional |
| Offline handling | None | QoS + persistent sessions |
| Broker required | No | Yes |
HTTP on Embedded Devices: The Real Cost
HTTP looks simple. In practice, for a device sending sensor data every 30 seconds:
1// Every single reading does ALL of this:
2// 1. TCP handshake — 1 round trip
3// 2. TLS handshake (HTTPS) — 2-3 round trips
4// 3. HTTP headers — ~600-800 bytes sent
5// 4. Response headers — ~400-600 bytes received
6// 5. TCP teardown — 1 round trip
7
8esp_err_t send_sensor_reading(float temperature) {
9 esp_http_client_config_t config = {
10 .url = "https://api.example.com/sensors/data",
11 .method = HTTP_METHOD_POST,
12 };
13 esp_http_client_handle_t client = esp_http_client_init(&config);
14
15 char payload[64];
16 snprintf(payload, sizeof(payload), "{\"temp\": %.2f}", temperature);
17
18 esp_http_client_set_post_field(client, payload, strlen(payload));
19 esp_err_t err = esp_http_client_perform(client); // blocks for 200-800ms
20
21 esp_http_client_cleanup(client);
22 return err;
23}For 2 readings per minute, that's 2,880 TLS handshakes per day. On cellular with a per-connection cost, this adds up fast. On battery, it's a device killer.
MQTT: Persistent Connection, Tiny Payloads
MQTT maintains a single TCP connection. Every publish after the initial handshake costs just a few bytes:
1#include "mqtt_client.h"
2
3static esp_mqtt_client_handle_t mqtt_client = NULL;
4
5void mqtt_init(void) {
6 esp_mqtt_client_config_t mqtt_cfg = {
7 .broker.address.uri = "mqtts://your-broker.example.com:8883",
8 .credentials.username = "device-001",
9 .credentials.authentication.password = "secret",
10 .session.keepalive = 60, // ping broker every 60s to keep conn alive
11 .session.disable_clean_session = true, // persist subscriptions across reconnects
12 };
13
14 mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
15 esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
16 esp_mqtt_client_start(mqtt_client);
17}
18
19void send_sensor_reading(float temperature) {
20 char payload[32];
21 snprintf(payload, sizeof(payload), "%.2f", temperature);
22
23 // After first connection: this is ~10-20 bytes on the wire
24 esp_mqtt_client_publish(
25 mqtt_client,
26 "devices/sensor-001/temperature", // topic
27 payload, // payload
28 0, // len 0 = use strlen
29 1, // QoS 1 = at least once delivery
30 0 // retain = false
31 );
32}After the initial TLS handshake, every publish is near-instant. No re-handshaking. No overhead. The broker receives the message and routes it to any subscribers — your backend, another device, a dashboard.
QoS Levels: Getting Delivery Guarantees Right
MQTT has three Quality of Service levels. Picking the wrong one is a common production mistake:
| QoS | Guarantee | Use When |
|---|---|---|
| 0 — At most once | Fire and forget, no ACK | High-frequency data where loss is acceptable (e.g. live telemetry at 10Hz) |
| 1 — At least once | Broker ACKs receipt, may duplicate | Sensor readings, alerts — most IoT telemetry |
| 2 — Exactly once | 4-way handshake, no duplicates | Financial transactions, command execution |
1// QoS 0 — no guarantee, minimum overhead
2esp_mqtt_client_publish(client, "sensors/temp", "23.5", 0, 0, 0);
3
4// QoS 1 — broker confirms receipt (use this for most telemetry)
5esp_mqtt_client_publish(client, "sensors/temp", "23.5", 0, 1, 0);
6
7// QoS 2 — exactly once (expensive, use only when duplication is harmful)
8esp_mqtt_client_publish(client, "commands/executed", "reboot", 0, 2, 0);Production rule: Use QoS 1 for all sensor telemetry. The duplicate risk is negligible and your backend can deduplicate by timestamp. QoS 2's 4-way handshake adds latency you don't need for sensor data.
Last Will and Testament: Free Device Health Monitoring
One of MQTT's most underused features — the broker publishes a message on your behalf if the device disconnects unexpectedly:
1esp_mqtt_client_config_t mqtt_cfg = {
2 .broker.address.uri = "mqtts://broker.example.com:8883",
3 .session.last_will = {
4 .topic = "devices/sensor-001/status",
5 .msg = "offline",
6 .msg_len = 7,
7 .qos = 1,
8 .retain = true, // broker stores this so late subscribers see it
9 },
10};When your device connects, publish "online" to the same topic. Any backend or dashboard subscribing to devices/+/status instantly knows which devices are live — for free, with no polling.
When to Use Which
1Ongoing sensor telemetry → MQTT (QoS 1, persistent session)
2Device receives commands → MQTT (subscribe to command topic)
3Device health monitoring → MQTT (Last Will + retain)
4One-time boot configuration → HTTP GET
5Device registration on first boot → HTTP POST
6Firmware OTA trigger → MQTT (trigger) + HTTP (binary download)Key Takeaways
- HTTP re-handshakes on every request — on battery or cellular, this is expensive
- MQTT maintains one persistent connection — publish costs ~2 bytes of overhead after connect
- QoS 1 is the right default for most IoT telemetry — fire-and-forget QoS 0 loses data silently
- Last Will gives you free device health monitoring — no extra infrastructure needed
- Use both — MQTT for ongoing data flow, HTTP for boot-time configuration and OTA binary downloads
Go from Arduino to Production Firmware
The ESP32-IDF Workshop covers ESP-IDF from scratch — tasks, queues, OTA, Wifi management, and deploying firmware that doesn't break at 3am.
Frequently Asked Questions
Quick answers to common questions

I build things that run on chips and the software that talks to them. ESP32, STM32, FreeRTOS, FastAPI, TinyML — from bare-metal firmware to cloud backends to on-device inference. Based in Bengaluru. Founder of Analog Data.