Every IoT project eventually reaches the same decision point:
Should the device communicate with the cloud over MQTT or HTTP?
Both protocols are mature, widely supported, and capable of moving device data reliably. The more useful question is not which protocol is better, but which communication model fits the device.
I have used MQTT for production edge clients running on deployed robot units and warehouse sensors. I have also used HTTP for a building-management monitoring backend and a smart-entry system built with Flask.
Both worked. They simply created very different systems.
This article explains the trade-offs I saw in practice: bandwidth overhead, connection behavior, command latency, power consumption, infrastructure, fan-out, and operational complexity.
The architectural difference that drives everything else
HTTP is built around a request-response model. A client sends a request, and a server returns a response.
MQTT is built around persistent connections and publish-subscribe messaging. A device connects to a broker, publishes messages to topics, and subscribes to topics it wants to receive.
That difference shapes almost every other trade-off.
With HTTP, a device usually addresses a specific API endpoint:
POST /api/readings
GET /devices/robot-01/commands
With MQTT, the device communicates through topics:
warehouse/sensor-01/temperature
robots/robot-01/commands
The publisher does not need to know which services consume a message. The broker handles routing.
Bandwidth and message overhead
The difference becomes easy to see when you compare a small sensor reading.
An HTTP request might look like this:
POST /api/sensors/temperature HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer <token>
Content-Length: 52
{"device_id": "sensor_01", "temperature": 23.5}
The payload is small, but the request also carries headers. Authentication tokens can make those headers much larger than the sensor reading itself.
An MQTT PUBLISH packet carries:
Fixed header
Topic name
Packet identifier, when required by QoS
Properties, in MQTT 5
Payload
For a short topic and a small payload, the MQTT packet can be substantially smaller than an equivalent HTTP request. The MQTT fixed header starts at two bytes, although the complete packet is larger once the topic, payload, QoS fields, and any MQTT 5 properties are included.
The comparison also depends on connection reuse.
HTTP/1.1 supports persistent connections by default, so a well-configured client does not need to open a new TCP and TLS connection for every request. HTTP/2 can also multiplex many requests over one connection. Poorly configured clients, short-lived processes, network timeouts, and reconnects can still cause repeated handshakes, but “one request equals one new connection” is not an accurate general rule.
MQTT still has an advantage for frequent, small messages because its application-layer framing is compact and the connection is normally kept open for ongoing messaging.
For one device publishing every few minutes, the absolute difference may not matter. Across hundreds of devices publishing several times per second, it can become significant.
Receiving commands: polling versus subscriptions
The biggest practical difference I encountered was server-to-device communication.
A basic HTTP API is client-initiated. The device sends a request before the server can return anything. A simple implementation therefore polls for commands:
import time
import requests
def poll_for_commands(device_id: str, token: str, interval_seconds: int = 5) -> None:
while True:
response = requests.get(
f"https://api.example.com/devices/{device_id}/commands",
headers={"Authorization": f"Bearer {token}"},
timeout=10,
)
response.raise_for_status()
for command in response.json():
handle_command(command)
time.sleep(interval_seconds)
At a five-second interval, that is 17,280 requests per device per day, including requests that return no commands. A newly created command may also wait almost five seconds before the next poll.
HTTP-based systems can avoid simple polling with techniques such as long polling, Server-Sent Events, or WebSockets. Those are valid designs, but they add a different connection-management model to the application.
MQTT supports this pattern directly. The device subscribes once, and the broker forwards matching messages over the existing connection:
import json
import paho.mqtt.client as mqtt
DEVICE_ID = "robot-01"
BROKER_HOST = "mqtt.example.com"
def on_connect(client, userdata, flags, reason_code, properties=None):
client.subscribe(f"devices/{DEVICE_ID}/commands", qos=1)
def on_message(client, userdata, message):
command = json.loads(message.payload.decode("utf-8"))
handle_command(command)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=DEVICE_ID)
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER_HOST, 1883, keepalive=60)
client.loop_forever()
For the telepresence robot, this mattered. Camera and movement-related commands needed low and predictable latency. A five-second polling loop was not acceptable, while an MQTT subscription matched the communication pattern naturally.
For truly hard real-time control, however, neither MQTT nor a general-purpose cloud connection should be treated as a deterministic control bus. Network delay, broker load, retransmission, and connectivity loss still need to be considered.
Reliability and QoS
MQTT offers three protocol-level Quality of Service levels:
- QoS 0: delivered at most once
- QoS 1: delivered at least once
- QoS 2: delivered exactly once between MQTT protocol peers
QoS 1 is useful when losing a message is worse than processing a duplicate. The application must therefore be able to handle duplicate delivery safely.
QoS 2 reduces duplicate delivery at the MQTT protocol layer, but it adds more packet exchanges and does not automatically make the entire application workflow exactly once. A message can still be processed twice if application state, database writes, retries, or downstream integrations are not idempotent.
HTTP can also be reliable, but the retry and deduplication behavior is usually designed at the application layer. For example, a device can retry a failed POST with an idempotency key, while the API stores the key to avoid inserting the same reading twice.
The important point is that neither protocol removes the need to design failure handling.
Power consumption is workload-dependent
It is tempting to say that MQTT always uses less power because its packets are smaller. The real answer is more nuanced.
Radio state often matters more than payload size. Establishing a cellular or Wi-Fi connection, negotiating TCP and TLS, and waiting for network timers can consume more energy than transmitting a short reading.
A continuously connected MQTT client can avoid repeated connection setup and receive commands immediately. That can be efficient for devices that communicate frequently or must remain reachable.
However, maintaining a live MQTT connection requires periodic keepalive traffic and may prevent some devices or modems from entering their deepest sleep states. MQTT PINGREQ and PINGRESP packets maintain the connection; they do not allow a device to turn its radio fully off while keeping the same TCP connection alive.
For a battery-powered sensor that wakes once an hour, uploads one reading, and never receives commands, a short HTTPS request may be simpler and just as efficient. The device must reconnect either way after deep sleep.
MQTT persistent sessions are still valuable for intermittently connected devices. A broker can preserve subscriptions and queue eligible QoS messages while the client is offline, subject to the broker configuration, protocol version, session settings, and retention limits. When the device reconnects and resumes the session, it can receive the stored messages.
So the useful rule is:
- Frequent bidirectional communication often favors MQTT.
- Rare uplink-only communication may favor HTTP.
- Measure on the actual radio, modem, network, and sleep schedule before making a battery-life claim.
Infrastructure and operational complexity
HTTP is easy to start with because almost every platform already supports it.
A small ingestion API can be only a few lines:
import sqlite3
import time
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.post("/api/readings")
def receive_reading():
data = request.get_json()
with sqlite3.connect("readings.db") as connection:
connection.execute(
"""
INSERT INTO readings (timestamp, device_id, value)
VALUES (?, ?, ?)
""",
(time.time(), data["device_id"], data["value"]),
)
return jsonify({"status": "ok"}), 201
The tooling is familiar: curl, Postman, browser developer tools, reverse proxies, API gateways, serverless functions, status codes, and standard observability platforms.
MQTT requires a broker. Mosquitto is a popular lightweight open-source option, while managed services such as AWS IoT Core and HiveMQ Cloud reduce the operational burden.
Running a broker means thinking about:
- Device authentication
- TLS certificates
- Topic-level authorization
- Session persistence
- Message expiry and retained messages
- Broker storage
- Monitoring and capacity
- Reconnection storms
- High availability
A single broker instance can become a single point of failure, but MQTT itself does not require a single-instance deployment. Production systems can use clustered brokers, load-balanced endpoints, replicated state, and managed broker services.
HTTP is not automatically immune to failure either. A single Flask process is also a single point of failure. HTTP infrastructure is often easier to scale horizontally because stateless APIs and load balancers are so widely supported, but both architectures require deliberate redundancy.
Fan-out is where MQTT feels fundamentally different
MQTT is especially useful when one message has several consumers.
A sensor can publish once:
sensors/warehouse/temp01
Several services can subscribe independently:
Dashboard
Alerting service
Time-series database
Analytics pipeline
Anomaly detector
The sensor does not need to know that these consumers exist. A new consumer can be added without changing device firmware.
With HTTP, the device can still send one request to an ingestion service, but that service must then distribute the event. This is commonly done with a queue, event bus, stream, or webhook system.
That architecture can be excellent. It simply means that HTTP handles device ingestion while another system provides asynchronous fan-out.
Debugging and developer experience
HTTP is usually easier to inspect manually.
You can reproduce a request with curl, view status codes, inspect headers, and test an endpoint without maintaining a long-lived client session.
MQTT debugging is still manageable, but it requires different tools and concepts:
mosquitto_sub -h broker.example.com -t 'sensors/#' -v
mosquitto_pub -h broker.example.com \
-t 'devices/robot-01/commands' \
-m '{"action":"stop"}' \
-q 1
You also need to understand wildcard subscriptions, retained messages, session state, QoS acknowledgements, duplicate delivery, and topic permissions.
In my experience, HTTP produces a faster first prototype. MQTT often produces a cleaner final design once the system becomes continuously connected, bidirectional, or event-driven.
A practical decision framework
Choose HTTP when:
- The device integrates with an existing REST API.
- Communication is infrequent and mostly device-to-cloud.
- The device uploads large payloads, images, logs, or firmware files.
- Simple debugging and minimal infrastructure matter most.
- Server-to-device messaging is unnecessary or can tolerate polling.
Choose MQTT when:
- Devices must receive commands or configuration changes quickly.
- Messages are small and frequent.
- Network links are unreliable or intermittent.
- Multiple services need the same device events.
- Session state, topic subscriptions, and QoS are useful.
- Bandwidth efficiency matters across a large fleet.
Use both when the workloads differ.
That is the design I now prefer for many IoT systems:
- MQTT for telemetry, commands, device status, and real-time events
- HTTP for firmware downloads, image uploads, administration, and third-party integrations
The broker handles operational messaging. The API handles resource-oriented workflows and large transfers.
Final takeaway
The decision is not really “MQTT or HTTP?”
It is:
Does this device behave more like an API client, or more like a participant in a live event system?
For an occasional one-way upload, HTTP is often the simplest answer.
For a connected fleet that publishes continuously, receives commands, survives intermittent networks, and feeds several downstream services, MQTT usually fits better.
In production, the strongest architecture is often not choosing one protocol everywhere. It is giving each protocol the job it was designed to do.
Have you used MQTT and HTTP in the same IoT system? I would be interested to hear where you drew the boundary between them.
Top comments (0)