← Back to blog
Backend Mar 13, 2026 · 4 min read

Notes on MQTT for a Low-Bandwidth IoT Integration

Notes on MQTT for a Low-Bandwidth IoT Integration

Designing for the connection to drop

Integrating IoT field sensors — such as remote fiber optics telemetry monitors, agricultural sensors, or smart utility meters — requires a fundamental mindset shift. Operating over 2G/NB-IoT metered cellular connections in remote locations means network coverage is patchy, latent, and expensive.

Designing a system around HTTP REST polling under these conditions is a non-starter. A standard HTTP/1.1 POST request over TLS requires multiple TCP round-trips, verbose HTTP headers (often 400+ bytes of metadata for a 10-byte payload), and significant power overhead.

Instead of assuming network stability, we designed our architecture around the premise that disconnection is the normal state of operation, and a connected socket is a transient opportunity to sync state.

Why MQTT fit

MQTT (Message Queuing Telemetry Transport) provided the ideal protocol foundation. Featuring a minimal 2-byte fixed header, pub/sub decoupling, and built-in connection management features, MQTT allowed us to minimize data usage while maintaining strict delivery guarantees where required.

MQTT Low-Bandwidth IoT System Architecture

Quality of Service (QoS) Level Strategy

Selecting the correct QoS level per topic was critical to balancing reliability against cellular data costs:

  • QoS 0 (At Most Once): Used for high-frequency sensor telemetry (e.g., voltage or ambient temperature samples sent every 10 seconds). If a packet drops during a coverage blackout, the broker discards it; subsequent readings arrive seconds later without wasting data on retransmissions.
  • QoS 1 (At Least Once): Used for state-change events and critical alarms (e.g., optical fiber break detected, enclosure tamper alert). The broker retries delivery until acknowledged (PUBACK), paired with client-side message deduplication.
  • QoS 2 (Exactly Once): Intentionally avoided. The 4-step handshake (PUBLISH -> PUBREC -> PUBREL -> PUBCOMP) consumes excessive bandwidth and introduces latency bottlenecks over lossy cellular links.

Retained Messages & Last Will and Testament (LWT)

  • Retained Messages: When a field device publishes its status with the retained flag set, the broker stores the last known message on that topic. When web dashboards or background workers connect, they immediately receive the latest state without polling or waiting for the device's next scheduled reporting cycle.
  • Last Will and Testament (LWT): Devices register an LWT message upon connection (e.g., status: offline). If a device unexpectedly drops offline (due to loss of power or signal loss), the MQTT broker automatically publishes the LWT message to alert monitoring workers.

Cellular NAT Timeout Tuning & Keep-Alive Strategy

The single most impactful configuration lever was tuning the MQTT KeepAlive timer against mobile network operator (MNO) carrier-grade NAT (CGNAT) behaviors.

graph LR
    Device["Cellular IoT Sensor"] <-->|Keep-Alive = 55s| NAT["MNO CGNAT Firewall<br/>(Silent drop after 60s)"]
    NAT <-->|TCP Socket| Broker["EMQX MQTT Broker"]
    Broker -->|Publish| Worker["Laravel Worker Queue"]
  • Keep-Alive Too Short (e.g., 15s): Generated excessive PINGREQ/PINGRESP packets, burning metered data allowances and draining battery power.
  • Keep-Alive Too Long (e.g., 300s): MNO CGNAT firewalls silently dropped idle TCP connections after 60 to 120 seconds without sending TCP FIN packets. Devices remained unaware that their connection was dead for minutes, missing incoming control commands.

By analyzing MNO NAT timeout profiles across target SIM providers, we aligned our MQTT KeepAlive timer to 55 seconds — just below the 60-second CGNAT drop threshold. This kept TCP connections alive with minimal bandwidth overhead.

Payload Optimization: Protocol Buffers over JSON

Textual formats like JSON carry significant bandwidth overhead due to key name repetition and ASCII encoding:

// Verbose JSON Payload: 118 bytes
{"device_id":"sensor_88412","timestamp":1718000000,"temp":24.5,"battery_voltage":3.68,"status":"OK"}

We migrated device telemetry payloads to Protocol Buffers (Protobuf), bit-packing fields into fixed-width binary structs:

syntax = "proto3";

message TelemetryReport {
  string device_id = 1;
  uint64 timestamp = 2;
  float temp = 3;
  float battery_voltage = 4;
  uint32 status_flags = 5;
}

This reduced average telemetry payload size from 118 bytes to 24 bytes — a 79% reduction in data transmission. Over thousands of devices reporting continuously, this saved gigabytes of metered cellular data monthly.

Backend Ingestion: EMQX to Redis Pub/Sub to Laravel

On the cloud infrastructure side, an EMQX broker cluster handles persistent MQTT client connections. Incoming telemetry topics (sensors/+/telemetry) are bridged into Redis Pub/Sub streams, which are consumed asynchronously by Laravel queue workers (php artisan queue:work):

namespace App\Jobs;

use App\Models\SensorReading;
use Illuminate\Contracts\Queue\ShouldQueue;

class ProcessSensorTelemetryJob implements ShouldQueue
{
    public function __construct(
        public string $deviceId,
        public array $telemetryData
    ) {}

    public function handle(): void
    {
        // Batch insert sensor reading and update Redis real-time state
        SensorReading::create([
            'device_id' => $this->deviceId,
            'temperature' => $this->telemetryData['temp'],
            'battery_voltage' => $this->telemetryData['battery_voltage'],
            'created_at' => now(),
        ]);
    }
}

This decoupled ingestion pattern ensured that device connections remained fast and responsive, regardless of database write load or downstream API processing.

Results and Key Lessons

Designing for low-bandwidth cellular IoT required treating network connectivity as intermittent:

  • Data Usage Reduction: Combining binary Protobuf payloads with tuned 55-second keep-alive timers cut cellular data usage by 74%.
  • Instant Connection Recovery: Retained messages and LWT allowed dashboards to display accurate offline/online device statuses within seconds of connectivity changes.
  • Zero Data Loss on Alarms: Leveraging QoS 1 for critical alarm events ensured 100% delivery reliability even over lossy 2G connections.