A soil moisture sensor in the Sahara. A pipeline monitor in the Arctic. A weather buoy in the Pacific. These devices share one challenge: they must transmit critical data from locations where traditional networks don’t exist. The protocol making this possible? MQTT—and increasingly, it’s doing so through satellite links.

This guide covers MQTT fundamentals, practical implementation patterns, and the emerging frontier of satellite IoT communications.

Why MQTT Dominates IoT

MQTT (Message Queuing Telemetry Transport) was designed in 1999 for monitoring oil pipelines via satellite—making its current resurgence in satellite IoT remarkably fitting. Its design principles remain relevant:

  • Minimal overhead: 2-byte fixed header vs HTTP’s hundreds of bytes
  • Publish-subscribe model: Decouples senders from receivers
  • Quality of Service levels: Guarantees delivery when needed
  • Persistent sessions: Reconnecting clients receive missed messages
  • Last Will and Testament: Automatic notification when devices disconnect

Core Architecture

(PSuebnlsiosrher)(DS(auBStbreasorbckvareesirreb)er)(DSausbhsbcorairbder)

Devices publish to topics—hierarchical strings like farm/greenhouse/temperature. Subscribers express interest in topics using wildcards:

  • farm/greenhouse/# — all greenhouse sensors
  • farm/+/temperature — temperature from any location
  • # — everything

Implementing MQTT: Python Examples

Basic Publisher

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import paho.mqtt.client as mqtt
import json
import time

# Connect to broker
client = mqtt.Client(client_id="sensor-001", protocol=mqtt.MQTTv5)
client.connect("broker.example.com", 1883, keepalive=60)

# Publish sensor readings
while True:
    payload = {
        "temperature": 23.5,
        "humidity": 65,
        "timestamp": time.time()
    }
    
    client.publish(
        topic="farm/greenhouse/climate",
        payload=json.dumps(payload),
        qos=1,  # At least once delivery
        retain=True  # Store last message for new subscribers
    )
    
    time.sleep(60)

Subscriber with Callbacks

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import paho.mqtt.client as mqtt
import json

def on_connect(client, userdata, flags, reason_code, properties):
    print(f"Connected with result code {reason_code}")
    # Subscribe to all farm sensors
    client.subscribe("farm/#", qos=1)

def on_message(client, userdata, msg):
    data = json.loads(msg.payload)
    print(f"[{msg.topic}] Temperature: {data['temperature']}°C")
    
    # Alert on threshold breach
    if data['temperature'] > 35:
        trigger_alert(msg.topic, data)

client = mqtt.Client(client_id="monitor-001", protocol=mqtt.MQTTv5)
client.on_connect = on_connect
client.on_message = on_message

client.connect("broker.example.com", 1883)
client.loop_forever()

Quality of Service Levels

MQTT offers three QoS levels, each with different trade-offs:

QoS 0: Fire and Forget

1
client.publish("sensors/temp", payload, qos=0)
  • No acknowledgment
  • Message may be lost
  • Lowest overhead
  • Use case: Frequent telemetry where occasional loss is acceptable

QoS 1: At Least Once

1
client.publish("sensors/temp", payload, qos=1)
  • Broker acknowledges receipt
  • Message stored until acknowledged
  • May deliver duplicates
  • Use case: Important data where duplicates can be handled

QoS 2: Exactly Once

1
client.publish("orders/new", payload, qos=2)
  • Four-step handshake guarantees single delivery
  • Highest overhead
  • Use case: Financial transactions, commands

Satellite IoT: MQTT Beyond Terrestrial Networks

Here’s where MQTT shines in extreme conditions. Satellite-connected IoT faces unique constraints:

  • High latency: 250-600ms round-trip to geostationary orbit
  • Limited bandwidth: Satellite spectrum is expensive and scarce
  • Power constraints: Remote devices often run on solar/battery
  • Cost per byte: Every transmission has real monetary cost

The MQTT-MFA Approach

Researchers developed MQTT-MFA (Message Filter Aggregator) to handle massive IoT traffic over satellite. The results are striking:

MetricStandard MQTTMQTT-MFA
TCP/IP overhead100%10%
Bandwidth (compressed)100%25%
Broker compatibilityNativeNative (Mosquitto)

The key innovation: edge aggregation. Instead of each sensor maintaining its own TCP connection, a local gateway:

  1. Collects messages from hundreds of sensors
  2. Aggregates by topic
  3. Compresses payloads
  4. Transmits in batches over satellite

Implementation Pattern for Satellite

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import paho.mqtt.client as mqtt
from collections import defaultdict
import zlib
import json
import time

class SatelliteAggregator:
    def __init__(self, satellite_broker, batch_interval=300):
        self.buffer = defaultdict(list)
        self.batch_interval = batch_interval
        self.satellite_client = mqtt.Client(client_id="sat-gateway")
        self.satellite_client.connect(satellite_broker, 1883)
        
    def on_local_message(self, client, userdata, msg):
        """Collect messages from local sensors"""
        self.buffer[msg.topic].append({
            "payload": msg.payload.decode(),
            "timestamp": time.time()
        })
    
    def transmit_batch(self):
        """Compress and send aggregated data over satellite"""
        if not self.buffer:
            return
            
        # Aggregate all buffered messages
        batch = dict(self.buffer)
        self.buffer.clear()
        
        # Compress for satellite transmission
        compressed = zlib.compress(
            json.dumps(batch).encode(),
            level=9
        )
        
        # Single satellite transmission for all sensors
        self.satellite_client.publish(
            topic="gateway/batch",
            payload=compressed,
            qos=1  # Ensure delivery over expensive link
        )
        
        print(f"Transmitted {len(compressed)} bytes over satellite")

Optimizing for Satellite Constraints

Report by exception: Only transmit when values change significantly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class ThresholdFilter:
    def __init__(self, threshold=0.5):
        self.last_values = {}
        self.threshold = threshold
    
    def should_transmit(self, sensor_id, value):
        if sensor_id not in self.last_values:
            self.last_values[sensor_id] = value
            return True
            
        delta = abs(value - self.last_values[sensor_id])
        if delta > self.threshold:
            self.last_values[sensor_id] = value
            return True
            
        return False

Priority queuing: Critical alerts bypass batching:

1
2
3
4
5
6
7
def publish_with_priority(self, topic, payload, priority="normal"):
    if priority == "critical":
        # Immediate satellite transmission
        self.satellite_client.publish(topic, payload, qos=2)
    else:
        # Buffer for next batch
        self.buffer[topic].append(payload)

Security Best Practices

IoT devices are attack targets. Secure your MQTT deployment:

TLS Encryption

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
client = mqtt.Client(client_id="secure-sensor")

# Configure TLS
client.tls_set(
    ca_certs="/etc/ssl/certs/ca.pem",
    certfile="/etc/ssl/certs/client.pem",
    keyfile="/etc/ssl/private/client.key"
)

client.connect("broker.example.com", 8883)  # TLS port

Authentication

1
2
3
4
client.username_pw_set(
    username="sensor-001",
    password="strong-random-password"
)

Topic-Based Authorization

Configure your broker (Mosquitto example):

#uttuttsoosoo/eppepperiiriitcccccsd/ewrarwmnreserosiahaisotdbdtqreoeu-casi0sorect0emdnot1nmsmosaom/onraardsncss/dl//#s.gg/crr#oeeneefnnhhoouussee/#

Scaling MQTT: Broker Clustering

For production deployments handling millions of devices:

EMQX Cluster Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# emqx.conf
cluster {
    name = "iot-cluster"
    discovery_strategy = static
    static {
        seeds = ["emqx@node1.example.com", "emqx@node2.example.com"]
    }
}

listeners.tcp.default {
    bind = "0.0.0.0:1883"
    max_connections = 1000000
}

Load Balancing with HAProxy

fbraocnktbmdembossseioenoapeeenndfddltrrrddeaeaivvvumnoeeem*tlqtcnrrrq:cttcet1p_tpteeet8b_lcmmm_8acepqqqf3cla-xxxrkusc123oesthnntcennntdeocoooernkdddnmneeedq123t:::t111_888c888l333uscccthhheeeerccckkk

Monitoring and Observability

Track broker health and message flow:

1
2
3
4
5
6
7
8
9
# Subscribe to broker system topics
def monitor_broker(client):
    client.subscribe("$SYS/#")
    
def on_sys_message(client, userdata, msg):
    if "clients/connected" in msg.topic:
        print(f"Connected clients: {msg.payload.decode()}")
    elif "messages/received" in msg.topic:
        print(f"Messages/sec: {msg.payload.decode()}")

Key metrics to track:

  • Connected clients
  • Messages per second (in/out)
  • Subscription count
  • Retained messages
  • Packet drop rate

Conclusion

MQTT’s lightweight design makes it ideal for IoT deployments across the connectivity spectrum—from factory floors with reliable WiFi to remote installations dependent on satellite links. The protocol’s publish-subscribe model, combined with edge aggregation techniques like MQTT-MFA, enables efficient data collection even when every byte has significant cost.

Key takeaways:

  • Choose QoS carefully: Match delivery guarantees to data criticality
  • Aggregate at the edge: Reduce satellite costs with local buffering
  • Report by exception: Transmit only meaningful changes
  • Secure everything: TLS, authentication, and ACLs are non-negotiable

At Sajima Solutions, we design IoT architectures that work from the edge to the cloud—including satellite-connected deployments in the world’s most remote locations. Contact us to discuss your IoT infrastructure needs.