Back to Blog
IoT Engineering

MQTT vs HTTP for IoT: Which Protocol Wins in Production?

Choosing between MQTT and HTTP can make or break an IoT deployment — the wrong choice adds latency, drains batteries, and balloons cloud costs. This guide gives engineers a clear decision framework backed by real benchmarks.

January 22, 2024
11 min read
MQTTHTTPIoT ProtocolsQoS

MQTT vs HTTP for IoT: Which Protocol Wins in Production?

Picking a communication protocol for an IoT system feels like it should be straightforward. In practice, the wrong choice costs you battery life, data reliability, and real money on cloud bills. At CodeCaracal we've shipped systems using both — and sometimes both at once. Here's the honest comparison.

Why the Protocol Choice Matters More Than You Think

A microcontroller waking up, sending a reading, and going back to deep sleep is a completely different workload from a streaming sensor publishing 10 readings per second. No single protocol fits both. The decision cascades into hardware selection, cloud architecture, and mobile app design.

Connection Overhead: Where HTTP Hurts the Most

Every HTTP request requires a full TCP handshake (or TLS handshake for HTTPS, which adds two more round trips). On a cellular NB-IoT link with 500 ms RTT, that's over 1.5 seconds of overhead before your 50-byte payload even leaves the device.

MQTT is a persistent connection protocol. The broker handshake happens once at startup. After that, publishing a telemetry message costs as little as 2 bytes of fixed header plus your payload. No headers, no method lines, no status codes.

HTTP POST (TLS, minimal headers) ≈ 800–2000 bytes per request
MQTT PUBLISH (QoS 0)            ≈  4–10 bytes overhead per message

For a device sending one reading per minute, HTTP is perfectly fine. For a device sending 10 readings per second, MQTT saves ~95% of bandwidth.

QoS: MQTT's Built-In Reliability Ladder

HTTP has no native retry semantics — your application code must handle failures. MQTT bakes three Quality-of-Service levels into the protocol:

| Level | Name | Guarantee | |-------|------|-----------| | QoS 0 | Fire-and-forget | Message may be lost | | QoS 1 | At-least-once | Delivered ≥1 times, may duplicate | | QoS 2 | Exactly-once | Delivered exactly once |

For sensor telemetry where losing an occasional reading is acceptable, QoS 0 is ideal — zero acknowledgement overhead. For actuator commands (open valve, trigger alarm), QoS 1 ensures the command lands. For billing or metering data, QoS 2 prevents double-counting at the cost of a four-message handshake.

See the deep-dive in [MQTT QoS 0, 1, and 2 Explained](/blog/mqtt-qos-levels-explained) for full protocol diagrams.

The Pub/Sub Model vs Request/Response

HTTP is fundamentally request/response. Every reading requires the device to initiate a connection. This breaks down immediately when you need to send a command *to* a device — you need long-polling, server-sent events, or WebSockets on top of HTTP to achieve push.

MQTT's pub/sub model solves this naturally. The device subscribes to devices/{deviceId}/commands at startup. The cloud publishes to that topic whenever it needs to send a command. The device receives it within milliseconds, with no polling loop required.

// Node.js: send a command to a specific device
const mqtt = require('mqtt')
const client = mqtt.connect('mqtts://your-broker:8883', {
  cert: fs.readFileSync('client.crt'),
  key: fs.readFileSync('client.key'),
  ca: fs.readFileSync('ca.crt'),
})

function sendCommand(deviceId, command) { const topic = `devices/${deviceId}/commands` const payload = JSON.stringify({ action: command, timestamp: Date.now(), correlationId: crypto.randomUUID(), }) client.publish(topic, payload, { qos: 1 }, (err) => { if (err) console.error('Command delivery failed:', err) }) }

sendCommand('sensor-42', 'reset_calibration')

With HTTP, achieving the same bi-directional communication requires a webhook receiver on the device side — which means the device needs a public IP or a reverse tunnel. That's a significant complexity and security burden.

Battery Implications

Connection establishment is the single largest energy consumer in most radio stacks. WiFi association + DHCP + TLS handshake on an ESP32 typically consumes 300–500 mA for 1–3 seconds. Deep sleep draws 10–150 µA.

For battery-powered devices that wake every 5 minutes, HTTP over TLS is workable — the radio is on for ~2 seconds per cycle. For devices needing sub-minute intervals, a persistent MQTT connection costs a continuous ~20 mA in idle listen mode, which is often cheaper than repeated reconnection overhead.

Rule of thumb:

  • Interval > 5 minutes → wake-sleep with HTTP or MQTT reconnect
  • Interval < 2 minutes → persistent MQTT connection
  • HTTP Where It Belongs: Device Provisioning and Bulk Uploads

    HTTP isn't wrong — it's just wrong for telemetry. We use it extensively for:

  • Device provisioning: REST API call to register a new device, fetch its certificate, write to flash
  • OTA firmware: HTTP chunked download is battle-tested and widely supported
  • Batch uploads: Devices that store data offline and bulk-POST on reconnection
  • Webhook callbacks: Third-party integrations that expect HTTP
  • // Node.js: Express endpoint to accept batch sensor upload
    app.post('/api/v1/readings/batch', express.json({ limit: '1mb' }), async (req, res) => {
      const { deviceId, readings } = req.body
      // readings: [{ ts, temperature, humidity }, ...]

    if (!readings?.length) return res.status(400).json({ error: 'Empty batch' })

    const points = readings.map(r => ({ measurement: 'environment', tags: { deviceId }, fields: { temperature: r.temperature, humidity: r.humidity }, timestamp: new Date(r.ts), }))

    await influxWriteApi.writePoints(points) await influxWriteApi.flush()

    res.json({ accepted: points.length }) })

    The Hybrid Architecture

    Production systems at CodeCaracal almost always use both protocols. The decision tree:

    Is the device battery-powered AND sends data > every 5 min?
      ├─ YES → persistent MQTT with QoS 0 telemetry
      └─ NO  → wake-sleep, evaluate interval:
                < 2 min  → persistent MQTT
                > 5 min  → HTTP POST or MQTT reconnect

    Does the system need cloud-to-device commands? └─ YES → MQTT required (subscribe to command topic)

    Is this firmware OTA or bulk historical upload? └─ YES → HTTP

    Is this device registration or provisioning? └─ YES → HTTP REST

    Real Benchmarks: What We've Seen in Production

    Across systems we've deployed for industrial and agricultural clients:

    | Metric | MQTT (QoS 0) | HTTPS POST | |--------|-------------|------------| | Message overhead | 4–10 bytes | 800–2000 bytes | | Latency (LTE, connected) | 8–20 ms | 300–800 ms | | Battery (ESP32, 30s interval) | ~12 mA avg | ~18 mA avg | | Cloud egress cost | Very low | Moderate | | Broker/infra complexity | Moderate | Low |

    The 8–20 ms MQTT latency assumes a maintained connection. MQTT with a fresh reconnect approaches HTTP latency, so connection pooling matters.

    AWS IoT Core: The Managed Middle Ground

    If you're deploying on AWS, IoT Core speaks MQTT natively and also exposes an HTTPS endpoint for devices that can't maintain persistent connections. This hybrid endpoint is excellent for low-cost cellular modules that only support HTTP. The payload format is identical — you get the same Rules Engine routing regardless of which transport the device uses.

    For a full walkthrough of connecting ESP32 to AWS IoT Core over MQTT, see the [ESP32 MQTT AWS IoT Core Production Guide](/blog/esp32-mqtt-aws-iot-core-production-guide).

    Making the Final Call

    Use MQTT when:

  • Devices publish frequently (< 5-minute intervals)
  • You need cloud-to-device commands
  • Bandwidth or battery is constrained
  • You want QoS guarantees at protocol level
  • Use HTTP when:

  • Devices wake rarely (> 10-minute intervals)
  • You're integrating with third-party systems that expect REST
  • You're doing OTA or provisioning
  • Your team is more REST-native and broker ops overhead isn't justified
  • Use both when (the honest answer for most production systems): your architecture has multiple device classes with different requirements.

    Need help with IoT protocol architecture? [Contact Code Caracal](/contact) — we've shipped these systems for clients across 15+ countries.

    ⚡
    Written by CodeCaracal Engineering

    We write from production experience — every technique in our articles has been deployed to real clients. No academic theory.

    Let's Build Together

    Got an IoT challenge?
    We've shipped it.

    Whether you need a fleet to track, a factory to monitor, or a farm to automate — our team has done it before and we'd love to build it with you. Typical response time: under 24 hours.

    No upfront commitment99.9% uptime SLANDA on requestFixed-price options