MQTT over cellular is how most remote assets (pump stations, tanks, solar sites, vehicles, environmental monitors) reach the cloud, but the design rules differ from Wi-Fi or Ethernet. Bytes are metered, links drop more often, and carrier networks silently close idle connections. This guide shows you how to estimate a data budget, choose keep alive values, reduce TLS overhead and pick between LTE-M and NB-IoT so your devices stay connected without surprising bills.
Why Cellular Changes MQTT Design
MQTT was originally designed for unreliable, low-bandwidth links, so it is already a good fit: a 2-byte fixed header, persistent TCP connections and publish-subscribe that avoids polling. But three cellular realities shape the details:
- Every byte is billed, including TCP/IP headers, TLS records, acknowledgements and keep alive pings, not just your payload.
- Carrier-grade NAT sits between most devices and the internet and drops idle TCP mappings after a carrier-specific timeout. If MQTT is quiet for longer than that, the connection dies without either end noticing immediately.
- Radio state costs power. Each transmission wakes the modem, and it stays in a high-power state for a while afterwards. Many small sends drain batteries faster than a few batched ones.
LTE-M vs NB-IoT for MQTT
| LTE-M (Cat-M1) | NB-IoT (Cat-NB1/NB2) | |
|---|---|---|
| Throughput | Higher, comfortable for MQTT over TLS | Very low; large handshakes take a long time |
| Latency | Comparable to regular LTE, low enough for interactive commands | High and variable, sometimes seconds |
| Mobility | Supports handover between cells | Designed for stationary devices |
| TCP/TLS suitability | Good | Possible, but often paired with UDP-based protocols |
| Power saving | PSM and eDRX supported | PSM and eDRX supported, very low idle power |
| Coverage | Good, improved over regular LTE | Excellent in deep indoor and underground locations |
For MQTT over TCP and TLS, LTE-M is usually the pragmatic choice: TLS handshakes complete reliably and commands reach the device quickly. NB-IoT shines for stationary devices that send small, infrequent reports from hard-to-reach places; there, many teams use MQTT-SN or another UDP-based protocol, or accept slow connection setup with MQTT over TCP. Check which technologies your carrier supports in your deployment region before choosing hardware; some modems support both and fall back automatically.
Estimating a Data Budget for MQTT over Cellular
Payload size is often the smallest part of the bill. Account for:
- TCP/IP headers on every packet (roughly 40 bytes for IPv4 without options, more with options or IPv6), plus TCP acknowledgements.
- TLS record overhead per message, a few dozen bytes for header, nonce and authentication tag.
- MQTT overhead: fixed header, topic name (often the largest part of a small message), and a PUBACK for QoS 1.
- Keep alive pings: a PINGREQ and PINGRESP are only 2 bytes each, but with TLS, TCP/IP headers and ACKs each round trip costs much more on the wire.
- Connection setup: a full TLS handshake including the server certificate chain is typically several kilobytes. Frequent reconnects can dominate the budget.
The script below gives a rough estimate. The per-packet figures are deliberately conservative assumptions; replace them with values measured on your modem (most modems report bytes sent and received).
# Rough monthly data estimate for an MQTT-over-TLS cellular device.
# Overhead figures are assumptions - measure your own link and adjust.
PAYLOAD_BYTES = 60 # e.g. compact JSON
TOPIC_BYTES = 40 # length of the topic string
MSGS_PER_HOUR = 12 # one reading every 5 minutes
QOS = 1 # QoS 1 adds a PUBACK
KEEPALIVE_S = 240 # pings only matter when the link is otherwise idle
RECONNECTS_PER_DAY = 2 # each one pays a full TLS handshake
TLS_HANDSHAKE_BYTES = 6000 # assumption: cert chain dominates
TCP_IP_PER_PACKET = 52 # IPv4 + TCP with timestamps option
TLS_PER_RECORD = 29 # TLS 1.2 AES-GCM record overhead
TCP_ACK = 52 # pure ACK packet
def bytes_per_publish():
mqtt = 2 + 2 + TOPIC_BYTES + (2 if QOS > 0 else 0) + PAYLOAD_BYTES
out = mqtt + TLS_PER_RECORD + TCP_IP_PER_PACKET
back = TCP_ACK
if QOS > 0:
back = 4 + TLS_PER_RECORD + TCP_IP_PER_PACKET + TCP_ACK # PUBACK + our ACK
return out + back
def bytes_per_ping():
one_way = 2 + TLS_PER_RECORD + TCP_IP_PER_PACKET
return 2 * one_way + 2 * TCP_ACK
msgs = MSGS_PER_HOUR * 24 * 30
gap = 3600 / MSGS_PER_HOUR
pings_per_gap = int(gap // KEEPALIVE_S) # pings needed between messages
pings = pings_per_gap * msgs
total = (msgs * bytes_per_publish()
+ pings * bytes_per_ping()
+ RECONNECTS_PER_DAY * 30 * TLS_HANDSHAKE_BYTES)
print(f"messages/month: {msgs}")
print(f"payload only: {msgs * PAYLOAD_BYTES / 1e6:.2f} MB")
print(f"estimated total: {total / 1e6:.2f} MB")
Running it shows the pattern you will see in the field: the wire total is several times the raw payload, and topic length, acknowledgements and reconnects matter as much as the data itself. That is why the optimisations below focus on overhead.
Keep Alive vs Carrier NAT Timeouts
The MQTT keep alive interval tells the broker how long the client may stay silent. If nothing else is sent, the client sends a PINGREQ before the interval expires. On cellular, keep alive has a second job: keeping the carrier's NAT mapping alive.
- If keep alive is longer than the NAT idle timeout, the mapping is silently dropped. The device still thinks it is connected; the next publish disappears, and the connection is only detected as dead after a TCP timeout. Commands from the cloud never arrive in the meantime.
- If keep alive is much shorter than necessary, you pay for pings you do not need and keep the radio awake.
NAT timeouts differ between carriers and can be just a few minutes. Measure yours: connect with a long keep alive, wait progressively longer between publishes, and note when messages from the cloud stop arriving. Then set keep alive comfortably below that value. If the device only sends data and does not need to receive commands, an alternative is to connect, publish and disconnect, accepting the handshake cost each time, which can be cheaper when reports are hours apart.
Reduce TLS Cost Without Dropping TLS
Never send plaintext MQTT over a public cellular network. Instead, make TLS cheaper:
- Keep connections up when reports are frequent, so the handshake happens rarely.
- Use TLS session resumption if your modem or TLS library supports session IDs or session tickets. A resumed handshake skips the certificate exchange and saves both bytes and time.
- Prefer ECDHE with ECDSA where both sides support it; elliptic-curve keys and signatures are smaller than RSA equivalents.
- Trust only the root you need (for example ISRG Root X1 for Let's Encrypt certificates) so the device stores and validates a minimal chain.
- Avoid reconnect storms. Use exponential backoff with jitter, so a cell outage does not trigger hundreds of back-to-back handshakes.
Shrink Every Message
- Short topics. A topic like
t/pump7/pis far cheaper than a long descriptive hierarchy on every publish. With MQTT 5, topic aliases let the client send the full topic once and then refer to it by a small integer, keeping readable topics without the per-message cost. - Compact payloads. Short JSON keys, integers with implied scaling, or a binary format such as Protobuf (as Sparkplug B uses).
- Batch readings. Send ten readings in one message every ten minutes instead of one per minute; headers are paid once.
- Report by exception. Send only meaningful changes plus a heartbeat; see report by exception.
- QoS selection. QoS 1 adds a PUBACK per message. Use it where delivery matters, QoS 0 for high-rate values where the next reading supersedes the last.
MQTT 5 Session Settings for Intermittent Links
A persistent session lets the broker keep subscriptions and queue QoS 1 messages for the device while it is briefly offline, so commands sent during a dropout are not lost. With paho-mqtt 2.x:
import paho.mqtt.client as mqtt
from paho.mqtt.packettypes import PacketTypes
from paho.mqtt.properties import Properties
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id="YOUR_CLIENT_ID", protocol=mqtt.MQTTv5)
client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
client.tls_set() # system CA store
client.reconnect_delay_set(min_delay=2, max_delay=300)
props = Properties(PacketTypes.CONNECT)
props.SessionExpiryInterval = 3600 # keep session 1 h after a drop
client.connect("YOUR_BROKER_HOST", 8883, keepalive=240,
clean_start=False, properties=props)
client.loop_forever()
Pair this with local buffering for outbound data, because a session only protects messages the broker has received. The store and forward MQTT guide shows a disk-backed queue for exactly that.
Cellular Design Checklist
- Choose LTE-M for TCP/TLS MQTT and interactive commands; consider NB-IoT for stationary, infrequent reporters.
- Measure carrier NAT idle timeout and set keep alive below it.
- Keep TLS, but minimise handshakes and use session resumption where available.
- Shorten topics or use MQTT 5 topic aliases; compact and batch payloads.
- Apply report by exception at the edge.
- Buffer outbound data on the device during outages.
- Use exponential backoff with jitter for reconnects.
- Monitor bytes per device per day so anomalies show up before the invoice.
For a hands-on modem example, see SIM7600 MQTT. For gateway-level architecture, read about the IIoT edge gateway and the industrial IoT with MQTT guide.
Testing Before You Pay for Data
Validate topic structure and payload format on Wi-Fi or Ethernet first, using the public MQTT test broker with a unique prefix such as connectmq/demo/cell-4f8a/.... Then move the device to cellular with production credentials on a managed MQTT broker such as ConnectMQ, which provides TLS endpoints and MQTT 5 features without you operating servers. Comparing hosting options? The cloud MQTT broker guide covers what to look for.
Connect Your Cellular Fleet to ConnectMQ
ConnectMQ supports MQTT 3.1.1 and 5.0 over TLS on port 8883, with session expiry, topic aliases and message expiry for intermittent links. Its $SYS/broker/connection/{clientId}/state topic and portal device views help you see which remote devices are online. Create a free Serverless broker at app.connectmq.com, no credit card required.
Frequently Asked Questions
How much data does MQTT use over cellular?
It depends far more on overhead than payload: TCP/IP and TLS headers, acknowledgements, keep alive pings and reconnect handshakes. Estimate with a model like the script above, then verify with your modem's byte counters.
What keep alive should I use on LTE-M?
Shorter than your carrier's NAT idle timeout, which you should measure. Values of a few minutes are common starting points; adjust after testing.
Is NB-IoT suitable for MQTT?
It can work for small, infrequent messages from stationary devices, but high latency and low throughput make TCP and TLS handshakes slow. LTE-M is the easier fit for standard MQTT over TLS.
Should cellular devices disconnect between reports?
If reports are hours apart and no commands need to reach the device, connect-publish-disconnect can be cheaper. For frequent reports or remote control, keep the connection open with a tuned keep alive.



