IoT Connectivity Protocols Compared: MQTT, HTTP, CoAP, LoRaWAN, Zigbee and More
A deep technical comparison of IoT communication protocols — MQTT, HTTP, CoAP, WebSocket, LoRaWAN, Zigbee, and AMQP. Covers packet structure, QoS, power budgets, range, broker setup, and real code examples to help you pick the right protocol for every IoT scenario.
Choosing the Right IoT Protocol
The communication protocol you choose for an IoT project determines power consumption, network topology, scalability, latency, and integration complexity. Choose wrong and you'll either drain batteries in weeks instead of years, saturate a narrow-band link with overhead, or spend months reimplementing a data pipeline.
This guide covers the six protocols you'll actually encounter in production IoT deployments — with packet-level detail, real setup examples, and a decision framework based on real project experience.
Protocol Overview: The Quick Reference
Before diving deep, here's the complete comparison at a glance:
| Protocol | Transport | Topology | Range | Data Rate | Power | Best For |
|---|---|---|---|---|---|---|
| MQTT | TCP | Pub/Sub (via broker) | LAN/WAN | 100s kbps–Mbps | Low | Sensor telemetry, device commands |
| HTTP/REST | TCP | Request/Response | LAN/WAN | Mbps+ | Medium-High | Web integration, APIs |
| CoAP | UDP | Request/Response | LAN | 100s kbps | Very Low | Constrained microcontrollers |
| WebSocket | TCP | Full-duplex | LAN/WAN | Mbps+ | Medium | Real-time dashboards, control |
| LoRaWAN | LoRa RF | Star (via gateway) | 2–15 km | 0.3–50 kbps | Extremely Low | Remote sensors, no WiFi/cellular |
| Zigbee | IEEE 802.15.4 | Mesh | 10–100 m | 250 kbps | Low | Indoor mesh, home automation |
| AMQP | TCP | Pub/Sub (via broker) | LAN/WAN | Mbps+ | Medium | Enterprise, financial, guaranteed delivery |
MQTT — The IoT Workhorse
MQTT (Message Queuing Telemetry Transport) was designed by IBM in the late 1990s for satellite telemetry links — high latency, limited bandwidth, unreliable connections. Those constraints make it the ideal protocol for IoT sensor networks today.
How MQTT Works
MQTT uses a publish/subscribe architecture through a central broker:
[Sensor A] --publish--> [Broker] --forward--> [Dashboard]
[Sensor B] --publish--> [Broker] --forward--> [Alert System]
[Phone App] <--subscribe-- [Broker] <--publish-- [Any Sensor]
Devices don't know about each other. The broker decouples producers from consumers — sensors can be replaced, dashboards can be added, and nothing else changes.
MQTT Packet Structure
An MQTT PUBLISH packet is remarkably compact:
Fixed header: 1–2 bytes (packet type + flags)
Topic length: 2 bytes
Topic string: variable (e.g., "sensors/floor1/temp")
Payload: variable (your data, any encoding)
Total overhead: ~5 bytes for a minimal publish
Compare that to an HTTP POST: headers alone are typically 200–800 bytes. For a sensor sending a temperature reading every 10 seconds over a metered cellular connection, this matters enormously.
Topic Structure Design
Topics are hierarchical strings separated by /. Design them for scalability:
# Good structure: site/building/floor/room/metric
sensors/plant-a/building-2/floor-1/temperature
sensors/plant-a/building-2/floor-1/humidity
devices/router-01/status
devices/router-01/config/ack
# Wildcards in subscriptions:
sensors/plant-a/# # all sensors in plant-a
sensors/+/building-2/+/temp # temperature on floor 1 of any building
Never put device IDs in topics as numbers only — use meaningful names. sensors/123/456 is impossible to debug at 2am.
QoS Levels — Choosing Correctly
QoS 0 — At most once (fire and forget)
- No acknowledgement
- Message may be lost if broker is down
- Lowest overhead
- Use for: frequent readings where losing one value doesn't matter
(temperature every 10s, GPS coordinates, heartbeats)
QoS 1 — At least once
- Broker acknowledges receipt
- Message may be delivered more than once (handle duplicates)
- Use for: commands, status changes, alerts
QoS 2 — Exactly once
- 4-way handshake guarantees single delivery
- Highest overhead
- Use for: financial transactions, meter readings, billing data
For most IoT telemetry, QoS 0 or QoS 1 is correct. QoS 2 is rarely needed on embedded devices.
Setting Up Mosquitto MQTT Broker
Mosquitto is the reference MQTT broker — lightweight, runs on a Raspberry Pi:
# Install on Debian/Ubuntu
sudo apt install mosquitto mosquitto-clients
# Basic configuration: /etc/mosquitto/mosquitto.conf
listener 1883
allow_anonymous false
password_file /etc/mosquitto/passwd
# Create a user
sudo mosquitto_passwd -c /etc/mosquitto/passwd iotuser
# Enter password when prompted
sudo systemctl enable --now mosquittoTest it immediately:
# Terminal 1 — subscribe
mosquitto_sub -h localhost -t "sensors/#" -u iotuser -P yourpassword -v
# Terminal 2 — publish a test reading
mosquitto_pub -h localhost -t "sensors/lab/temperature" -m '{"temp": 23.4, "unit": "C"}' \
-u iotuser -P yourpasswordPython MQTT Publisher (Sensor Side)
import paho.mqtt.client as mqtt
import json
import time
import random
BROKER = "192.168.1.10"
PORT = 1883
TOPIC = "sensors/lab/temperature"
client = mqtt.Client(client_id="sensor-lab-01", clean_session=False)
client.username_pw_set("iotuser", "yourpassword")
client.connect(BROKER, PORT, keepalive=60)
client.loop_start()
while True:
reading = {
"value": round(random.uniform(20.0, 25.0), 1),
"unit": "C",
"ts": int(time.time())
}
client.publish(TOPIC, json.dumps(reading), qos=1, retain=False)
time.sleep(10)MQTT over TLS (Production Requirement)
Never run MQTT without TLS in production — credentials and sensor data are plaintext:
# /etc/mosquitto/mosquitto.conf additions
listener 8883
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key
require_certificate false # set true for mutual TLS# Client with TLS
client.tls_set(ca_certs="/path/to/ca.crt")
client.connect(BROKER, 8883)HTTP/REST — When Familiarity Wins
HTTP is not optimal for IoT, but it's universally supported and easy to debug. Use it when:
- Devices have sufficient power and bandwidth (mains-powered, ethernet-connected)
- You're integrating with existing web APIs or cloud platforms
- Development speed matters more than efficiency
- You're building a prototype before choosing a long-term protocol
HTTP Overhead Reality Check
A minimal HTTP POST to send a sensor reading:
POST /api/readings HTTP/1.1\r\n → 26 bytes
Host: api.example.com\r\n → 22 bytes
Content-Type: application/json\r\n → 31 bytes
Authorization: Bearer eyJ...\r\n → 50–200 bytes
Content-Length: 35\r\n → 20 bytes
\r\n → 2 bytes
{"temp":23.4,"ts":1700000000} → 35 bytes
─────────
Total: ~186–336 bytes minimum
An equivalent MQTT publish: ~30 bytes total including topic.
For a sensor on a 2G GPRS link paying per kilobyte, this is the difference between days and months of data costs.
HTTP Long Polling vs MQTT for Real-Time
When you need a device to receive commands in near-real-time, HTTP requires polling:
# HTTP polling — inefficient
while True:
response = requests.get("https://api.example.com/device/cmd/pending")
if response.json()["has_command"]:
execute(response.json()["command"])
time.sleep(5) # poll every 5 secondsWith MQTT, the broker pushes immediately:
# MQTT subscription — command arrives the moment it's published
def on_message(client, userdata, msg):
execute(json.loads(msg.payload))
client.subscribe("devices/my-device-01/commands", qos=1)For command delivery, MQTT wins on latency, bandwidth, and battery.
CoAP — Protocol for Constrained Devices
CoAP (Constrained Application Protocol, RFC 7252) was designed for devices with as little as 10 KB of RAM. It mimics HTTP's RESTful structure but runs over UDP, which eliminates TCP's handshake overhead.
CoAP vs HTTP: The Key Differences
Feature HTTP CoAP
────────────────────────────────────────
Transport TCP UDP
Header size 200–800B 4 bytes fixed
Connection Persistent Connectionless
Security TLS DTLS
Multicast No Yes
Discovery No Yes (/.well-known/core)
CoAP Message Types
CON (Confirmable) → must be acknowledged (like QoS 1)
NON (Non-confirmable) → fire and forget (like QoS 0)
ACK (Acknowledgement) → response to CON
RST (Reset) → something went wrong
CoAP in Practice with MicroPython
On a bare ESP32 with MicroPython (128 KB RAM available):
import usocket as socket
import ubinascii
import ujson
# CoAP PUT request — minimal implementation
def coap_put(server, port, path, payload):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(5)
# CoAP header: Ver=1, Type=CON(0), TKL=0, Code=0.03 PUT, MID=random
header = bytes([0x40, 0x03, 0x00, 0x01])
# URI-Path option
option = bytes([0xB0 | len(path)]) + path.encode()
# Content-Format option (application/json = 50)
content_opt = bytes([0xC1, 0x32])
# Payload marker + data
data = bytes([0xFF]) + ujson.dumps(payload).encode()
packet = header + option + content_opt + data
sock.sendto(packet, (server, port))
try:
response = sock.recv(64)
return response[1] & 0x1F # response code
except:
return None
finally:
sock.close()CoAP shines in 6LoWPAN networks (IPv6 over 802.15.4 radio) where even TCP is too heavy. For everything else — ESP32, Raspberry Pi, any device with reasonable RAM — MQTT is simpler.
WebSocket — Full-Duplex for Dashboards
WebSocket upgrades an HTTP connection to a persistent full-duplex channel. It's not really an IoT sensor protocol — it's the right choice for the dashboard side of an IoT system:
[Sensors] → MQTT → [Backend] → WebSocket → [Browser Dashboard]
The browser can't run MQTT natively. A WebSocket bridge (Node.js, Python ASGI) subscribes to MQTT topics and pushes updates to connected browsers in real-time.
// Browser: receive sensor updates via WebSocket
const ws = new WebSocket("wss://dashboard.example.com/ws");
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
updateGauge(data.sensor_id, data.value);
};# Server: bridge MQTT → WebSocket (FastAPI + paho)
import asyncio
import paho.mqtt.client as mqtt
from fastapi import WebSocket
connected_clients = set()
def on_mqtt_message(client, userdata, msg):
reading = msg.payload.decode()
for ws in connected_clients.copy():
asyncio.create_task(ws.send_text(reading))
mqtt_client = mqtt.Client()
mqtt_client.on_message = on_mqtt_message
mqtt_client.connect("localhost", 1883)
mqtt_client.subscribe("sensors/#")
mqtt_client.loop_start()LoRaWAN — Long Range, Minimal Power
LoRaWAN is for sensors that must survive years on a battery, deployed outdoors across distances that WiFi and Zigbee can't cover: farms, water infrastructure, railway trackside sensors, smart city monitoring.
LoRaWAN Architecture
[End Node] ──LoRa RF──> [Gateway] ──Ethernet/4G──> [Network Server] ──> [Application]
Multiple gateways can receive from the same end node — the network server deduplicates. End nodes don't connect to a specific gateway; they broadcast and gateways relay.
Radio Performance
LoRa uses chirp spread spectrum (CSS) modulation with configurable spreading factor (SF7–SF12):
| Spreading Factor | Range (urban) | Range (rural) | Data Rate | Time on Air |
|---|---|---|---|---|
| SF7 | ~2 km | ~5 km | 5.5 kbps | 56 ms |
| SF9 | ~5 km | ~9 km | 1.4 kbps | 370 ms |
| SF12 | ~10 km | ~15 km | 250 bps | 2.8 s |
Higher SF = longer range, lower data rate, more time on air = more battery. Use the lowest SF that gives you reliable coverage.
Power Budget for a LoRaWAN Sensor Node
Sleeping: 5 µA
SNMP measurement: 1 mA × 50ms = 0.05 mAs
LoRa transmit: 40 mA × 370ms = 14.8 mAs (SF9)
LoRa receive: 11 mA × 1000ms = 11 mAs
Per 15-minute cycle: ~26 mAs = 0.0072 mAh
2× AA battery (3000 mAh): 3000/0.0072 = ~417,000 cycles → ~11.9 years
Real-world with PCB leakage and cold weather: expect 3–8 years.
LoRaWAN Payload Design
With SF9 at 125 kHz, your maximum payload is 115 bytes per uplink (EU868 duty cycle). Design compact payloads:
import struct
# Pack temperature (int16, 0.01°C resolution), humidity (uint8, %), battery (uint8, %)
def encode_payload(temp_c, humidity_pct, battery_pct):
temp_raw = int(temp_c * 100)
return struct.pack(">hBB", temp_raw, int(humidity_pct), int(battery_pct))
payload = encode_payload(23.4, 65, 87)
# Result: 6 bytes — vastly more efficient than JSONOn the server side, decode with the same struct format. Never send JSON over LoRaWAN.
Zigbee — Indoor Mesh Networking
Zigbee operates on IEEE 802.15.4 at 2.4 GHz (same band as WiFi and Bluetooth), using a mesh topology where each node can relay for others. This extends coverage without wiring and eliminates single points of failure.
Zigbee Network Roles
Coordinator (1 per network) — forms and manages the network, usually a hub
Router — relays traffic, usually mains-powered
End Device — sleeps most of the time, battery-powered sensors
Zigbee vs WiFi for Indoor Sensors
| Zigbee | WiFi | |
|---|---|---|
| Power (end device) | ~30 µA average | 1–10 mA average |
| Network nodes | 65,000+ | 255 (practical) |
| Mesh | Yes | No (star topology) |
| Range per hop | 10–100 m | 30–100 m |
| Integration complexity | Higher | Lower |
| Existing ecosystem | Smart home devices | Everything else |
Zigbee is the right choice when you have many battery-powered sensors in a building and want a mesh that doesn't depend on WiFi infrastructure. For industrial or outdoor use, LoRaWAN or cellular is usually better.
AMQP — Enterprise-Grade Messaging
AMQP (Advanced Message Queuing Protocol) is what MQTT becomes when you need enterprise features: guaranteed delivery, message routing, dead-letter queues, transactions, and fine-grained access control.
RabbitMQ is the most common AMQP broker. Use AMQP when:
- Messages cannot be lost (billing, audit logs, commands with consequences)
- You need complex routing (messages go to different queues based on content)
- Multiple teams own different parts of the pipeline
- Compliance requires message acknowledgement and replay
import pika
connection = pika.BlockingConnection(
pika.ConnectionParameters('localhost', credentials=pika.PlainCredentials('user', 'pass'))
)
channel = connection.channel()
# Durable queue — survives broker restart
channel.queue_declare(queue='sensor_readings', durable=True)
channel.basic_publish(
exchange='',
routing_key='sensor_readings',
body='{"sensor": "temp-01", "value": 23.4}',
properties=pika.BasicProperties(delivery_mode=2) # persistent
)AMQP is heavier than MQTT — not suitable for resource-constrained devices. Run MQTT on the device side and bridge to AMQP on the server when you need enterprise guarantees.
The Decision Framework
Work through these questions in order:
1. Power constraints?
Battery-powered, years of life needed?
→ Long range (km): LoRaWAN
→ Short range (m): Zigbee or MQTT (QoS 0)
→ Indoor, mesh: Zigbee
Mains powered or frequent charging acceptable?
→ Continue to question 2
2. Range and connectivity?
No WiFi/cellular, need km range? → LoRaWAN
Indoor mesh, many nodes? → Zigbee
WiFi/Ethernet available? → Continue to question 3
3. Data pattern?
Continuous telemetry, many devices, needs to scale? → MQTT
One-off requests, web API integration needed? → HTTP/REST
Extremely constrained MCU (8-bit, <32KB RAM)? → CoAP
Real-time browser dashboard? → WebSocket (bridge from MQTT)
Enterprise, cannot lose messages? → AMQP
4. Full stack example
A typical production IoT architecture uses multiple protocols in concert:
[LoRaWAN sensors] ──LoRa──> [LoRa Gateway]
[Zigbee sensors] ──Zigbee─> [Zigbee Hub/Bridge]
[WiFi sensors] ──MQTT──> [Mosquitto Broker]
[Industrial PLCs] ──Modbus─> [Protocol Gateway]
│
[Node-RED / Bridge]
│
[InfluxDB + Grafana]
│
[WebSocket] ──> [Browser Dashboard]
Each layer uses the protocol best suited to its constraints.
Practical Setup: MQTT + InfluxDB + Grafana Stack
This is the most common stack for production IoT monitoring. Here's the integration:
# MQTT subscriber → InfluxDB writer
import paho.mqtt.client as mqtt
from influxdb_client import InfluxDBClient, Point
from influxdb_client.client.write_api import SYNCHRONOUS
import json
influx = InfluxDBClient(url="http://localhost:8086", token="your-token", org="iot")
write_api = influx.write_api(write_options=SYNCHRONOUS)
def on_message(client, userdata, msg):
try:
data = json.loads(msg.payload.decode())
# Topic format: sensors/{site}/{metric}
parts = msg.topic.split("/")
site, metric = parts[1], parts[2]
point = (
Point("sensor_reading")
.tag("site", site)
.tag("metric", metric)
.field("value", float(data["value"]))
)
write_api.write(bucket="iot_metrics", record=point)
except Exception as e:
print(f"Error: {e}")
client = mqtt.Client()
client.username_pw_set("iotuser", "yourpassword")
client.on_message = on_message
client.connect("localhost", 1883)
client.subscribe("sensors/#", qos=1)
client.loop_forever()In Grafana, create a dashboard querying InfluxDB's Flux query language:
from(bucket: "iot_metrics")
|> range(start: -1h)
|> filter(fn: (r) => r._measurement == "sensor_reading" and r.site == "plant-a")
|> aggregateWindow(every: 1m, fn: mean)Security Considerations
MQTT Security Checklist
- Enable TLS (port 8883, not 1883) — prevents eavesdropping
- Use per-device credentials — never share a community password
- Enable ACLs — devices should only publish to their own topics
- Use QoS 1+ for commands — don't let control messages get lost
- Set
clean_session=Falsefor critical subscribers — they queue messages during downtime
LoRaWAN Security
LoRaWAN uses two layers of AES-128 encryption by default (network and application keys). Each device has a unique DevEUI, AppEUI, and AppKey. Never share AppKeys across devices — a compromised node can spoof all others with the same key.
Frequently Asked Questions
Which IoT protocol is best for battery-powered sensors?
For short-range battery-powered sensors (indoor, within 100m), MQTT with QoS 0 over WiFi or Zigbee are the best choices. MQTT's lightweight publish/subscribe model keeps overhead minimal and QoS 0 avoids acknowledgement round-trips. Zigbee consumes ~30 µA average versus 1–10 mA for WiFi, so for multi-year deployments without mains power, Zigbee wins indoors. For outdoor sensors across large areas (farms, cities, trackside), LoRaWAN is the clear choice — 2–15 km range with sensors running 3–8 years on AA batteries.
What is the difference between MQTT and HTTP for IoT?
MQTT uses a publish/subscribe pattern via a central broker: devices publish to topics and subscribers receive updates automatically with no polling. HTTP uses request/response: the client must actively ask for data each time. MQTT has ~5 bytes of overhead per message versus 200–800 bytes for HTTP headers. MQTT maintains persistent connections and has three QoS levels for reliability. HTTP is heavier but universally supported and easier to integrate with existing web APIs. Choose MQTT for continuous telemetry at scale; choose HTTP for simple API integration or prototyping with mains-powered devices.
Can I use multiple IoT protocols in the same project?
Yes — this is the standard production architecture. A typical stack uses LoRaWAN for outdoor remote sensors, Zigbee for indoor mesh sensor networks, MQTT as the central messaging backbone, and WebSocket for browser dashboards. An IoT gateway bridges protocols — receiving data from field devices over LoRa or Zigbee and forwarding over MQTT to your backend. Platforms like Node-RED, ThingsBoard, and AWS IoT Core are designed for exactly this multi-protocol integration.
What is CoAP and when should I use it over MQTT?
CoAP (RFC 7252) is a lightweight RESTful protocol for extremely resource-constrained devices — 8-bit microcontrollers with 10 KB of RAM that can't run TCP stacks. It runs over UDP with a 4-byte fixed header (versus MQTT's TCP overhead). Use CoAP when your device is so constrained it can't maintain a TCP connection, when you need multicast to groups of devices, or when you're deploying over 6LoWPAN (IPv6 over IEEE 802.15.4). For any device with reasonable resources (ESP32, Raspberry Pi), MQTT is simpler and better supported.
How does LoRaWAN differ from Zigbee?
LoRaWAN and Zigbee solve different problems. LoRaWAN is for long-range (2–15 km) outdoor deployments — it uses licensed-free sub-GHz frequencies (EU: 868 MHz, US: 915 MHz) and can penetrate buildings and terrain. Zigbee is for short-range (10–100 m per hop) indoor mesh networks — it operates at 2.4 GHz and forms self-healing mesh topologies where each node relays for others. LoRaWAN is better for sparse sensors spread across large areas; Zigbee is better for dense sensor networks within a building or campus.
Conclusion
No single protocol wins across all IoT scenarios. LoRaWAN is unbeatable for long-range battery sensors. Zigbee is ideal for dense indoor mesh networks. MQTT handles the majority of connected device telemetry. CoAP serves the most constrained microcontrollers. HTTP and WebSocket belong at the integration and dashboard layers. AMQP handles enterprise reliability requirements.
Build your stack in layers: use the right protocol at each layer, and bridge between them at gateways and backend services. A well-designed multi-protocol IoT architecture is more robust, cheaper to operate, and easier to scale than trying to force a single protocol everywhere.
Related: Raspberry Pi as a Network Monitor with SNMP and Grafana | Modbus TCP for Industrial IoT: A Complete Guide | Understanding SNMP: OIDs, MIBs and Polling