MQTT vehicle tracking works well because the protocol was designed for exactly this kind of link: small devices sending frequent small messages over unreliable, metered networks. In this guide you will design a topic structure for a fleet, choose compact GPS payloads, tune keep alive and sessions for cellular trackers, detect offline vehicles, and build a live browser map that updates straight from MQTT subscriptions on ConnectMQ.
Why MQTT Fits Fleet Telematics
A cellular tracker in a van spends its life switching cells, entering tunnels and losing coverage in car parks. Each reconnection costs time, power and data. MQTT helps in several ways:
- One persistent connection instead of a new HTTP request per position, so headers and handshakes are paid once.
- Small fixed overhead per message, which matters when every kilobyte is billed.
- Keep alive and Last Will give the server a clear signal when a vehicle goes silent.
- QoS 1 with persistent sessions lets commands reach a vehicle that briefly lost coverage.
- Fan-out through subscriptions: the same position feeds a dispatcher map, a geofencing service and a history writer without the tracker doing anything extra.
For a refresher on the delivery guarantees, see MQTT QoS levels explained.
Topic Design for Vehicle Tracking
Start with a hierarchy that matches how people query the data: by fleet, then by vehicle, then by data type.
fleet/{fleetId}/vehicle/{vehicleId}/position live GPS fixes
fleet/{fleetId}/vehicle/{vehicleId}/status online/offline (retained)
fleet/{fleetId}/vehicle/{vehicleId}/event ignition, harsh braking, door open
fleet/{fleetId}/vehicle/{vehicleId}/diag battery voltage, signal quality
fleet/{fleetId}/vehicle/{vehicleId}/cmd commands to the tracker
This lets a dispatcher map subscribe to fleet/north/vehicle/+/position, an alerting service to fleet/+/vehicle/+/event, and an operations dashboard to fleet/+/vehicle/+/status. Keep identifiers short and stable: a vehicle ID like v0142 rather than a full licence plate with spaces. Avoid putting personally identifiable information, such as a driver's name, into topic names.
Payload Design
Position messages are the bulk of your traffic, so they deserve attention.
| Field | Key | Example | Notes |
|---|---|---|---|
| Timestamp | t |
1792137600 |
Unix seconds from GNSS time, not the server's receive time |
| Latitude | la |
41.01224 |
5 decimal places is roughly a metre |
| Longitude | lo |
28.97601 |
Same precision |
| Speed | s |
48 |
km/h, integer |
| Heading | h |
270 |
Degrees, integer |
| Fix quality | q |
3 |
0 = none, 2 = 2D, 3 = 3D |
| Satellites | n |
9 |
Useful for filtering bad fixes |
A resulting payload is about 90 bytes:
{"t":1792137600,"la":41.01224,"lo":28.97601,"s":48,"h":270,"q":3,"n":9}
Practical rules:
- Always include the device timestamp. Messages buffered during a coverage gap arrive late; the map and history must order by fix time.
- Batch when buffering. After a tunnel, publish one message containing an array of buffered fixes rather than hundreds of individual messages.
- Report adaptively. Every 5 to 15 seconds while moving, every few minutes while parked, and immediately on significant heading changes.
- Drop bad fixes on the device. If fix quality is 0, do not publish a position.
Keep Alive, Sessions and QoS for Cellular Links
- Keep alive: 60 to 120 seconds is a reasonable starting point on cellular. Shorter values detect drops faster but keep the modem awake; carrier NAT timeouts can also silently kill idle connections, so do not go so long that the NAT mapping expires first.
- QoS: QoS 0 is acceptable for high-frequency positions where the next fix replaces the last; QoS 1 for events and commands that must not be lost.
- Persistent sessions: connect with a clean start of false and a session expiry (MQTT 5), for example 10 to 30 minutes, so commands sent during a short outage are delivered on reconnect.
- Message expiry: set an expiry on commands such as "unlock doors" so a vehicle that comes back an hour later does not execute an old instruction.
- Last Will: register a retained
offlinewill on the status topic and publish a retainedonlineafter connecting.
Simulating a Tracker in Python
Before hardware arrives, simulate a vehicle. This script drives along a line, uses a Last Will and publishes positions with paho-mqtt 2.x. It targets the public broker so you can run it immediately; switch to your own credentials and port 8883 for real use.
import json
import math
import time
import uuid
import paho.mqtt.client as mqtt
HOST = "broker.connectmq.com"
PORT = 1883
PREFIX = f"connectmq/demo/{uuid.uuid4().hex[:6]}"
VEHICLE = "v0142"
BASE = f"{PREFIX}/fleet/north/vehicle/{VEHICLE}"
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=f"sim-{VEHICLE}-{uuid.uuid4().hex[:6]}",
protocol=mqtt.MQTTv5)
client.will_set(f"{BASE}/status", "offline", qos=1, retain=True)
def on_connect(client, userdata, flags, reason_code, properties):
print("Connected:", reason_code, "| topic prefix:", PREFIX)
client.publish(f"{BASE}/status", "online", qos=1, retain=True)
client.on_connect = on_connect
client.connect(HOST, PORT, keepalive=90)
client.loop_start()
lat, lon, heading = 41.0100, 28.9700, 45.0
try:
while True:
lat += 0.0003 * math.cos(math.radians(heading))
lon += 0.0003 * math.sin(math.radians(heading))
heading = (heading + 3) % 360
fix = {"t": int(time.time()), "la": round(lat, 5), "lo": round(lon, 5),
"s": 42, "h": int(heading), "q": 3, "n": 9}
client.publish(f"{BASE}/position", json.dumps(fix), qos=0)
time.sleep(5)
except KeyboardInterrupt:
client.publish(f"{BASE}/status", "offline", qos=1, retain=True).wait_for_publish()
client.loop_stop()
client.disconnect()
Note the printed prefix; you need it for the map. The public broker allows 10 messages per second per client and 8 topic levels, so the full topic connectmq/demo/xxxxxx/fleet/north/vehicle/v0142/position (8 levels) just fits. Shorten the hierarchy if you add more levels. Remember that the public area is readable by everyone: use simulated coordinates only.
A Live Fleet Map from Subscriptions
The map is just a browser MQTT client subscribing to positions and moving markers. This page uses MQTT.js over Secure WebSocket and Leaflet.
// map.js - load Leaflet and MQTT.js in your page and add a container element with id "map"
const PREFIX = "connectmq/demo/REPLACE_WITH_PREFIX";
const map = L.map("map").setView([41.01, 28.97], 13);
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "OpenStreetMap contributors",
}).addTo(map);
const markers = new Map();
const client = mqtt.connect("wss://broker.connectmq.com:8084/mqtt", {
clientId: "fleet-map-" + Math.random().toString(16).slice(2, 10),
protocolVersion: 5,
});
client.on("connect", () => {
client.subscribe(`${PREFIX}/fleet/+/vehicle/+/position`);
client.subscribe(`${PREFIX}/fleet/+/vehicle/+/status`);
});
client.on("message", (topic, payload) => {
const parts = topic.split("/");
const vehicleId = parts[parts.length - 2];
const kind = parts[parts.length - 1];
if (kind === "position") {
const fix = JSON.parse(payload.toString());
let m = markers.get(vehicleId);
if (!m) {
m = L.marker([fix.la, fix.lo]).addTo(map).bindTooltip(vehicleId);
markers.set(vehicleId, m);
}
m.setLatLng([fix.la, fix.lo]);
m.setTooltipContent(`${vehicleId} - ${fix.s} km/h`);
} else if (kind === "status") {
const m = markers.get(vehicleId);
if (m) m.setOpacity(payload.toString() === "online" ? 1 : 0.4);
}
});
For production, authenticate the map with credentials issued by your backend rather than embedding a shared password in the page, and if many map viewers are open at once, consider a small backend that subscribes once and serves positions to browsers.
Storing History and Geofencing
ConnectMQ is the broker, not a database, so history and geofencing run as your own MQTT clients. A Python or Node.js service subscribing to fleet/+/vehicle/+/position can write to PostgreSQL with PostGIS or a time-series database, and a second service can test each fix against geofence polygons and publish to the event topic. With Serverless (Paid) or above, shared subscriptions such as $share/geo/fleet/+/vehicle/+/position let several instances split the load. The portal's threshold alert rules are also handy for simple checks, such as a diagnostics battery voltage dropping below a limit.
Handling Coverage Gaps on the Device
Trackers spend real time out of coverage, so the firmware needs a plan for it. Keep a ring buffer of recent fixes in flash or RAM, sized for the longest gap you expect, for example an hour of fixes at a reduced rate. When the connection returns, publish the current position first so the live map updates immediately, then drain the buffer in batches with their original timestamps. Consumers that write history should upsert by vehicle and timestamp, so a batch delivered twice under QoS 1 does not create duplicate points.
Production Checklist
- Unique credentials and client ID per tracker, TLS on 8883.
- Device timestamps on every fix; batch after outages.
- Adaptive reporting interval.
- Retained status with Last Will.
- Persistent sessions and message expiry for commands.
- No personal data in topic names.
- Reconnect backoff with jitter for the whole fleet.
The ESP32 side of a tracker is covered in ESP32 GPS tracker with MQTT, and MQTT for mobile apps covers driver or dispatcher apps. For hosting choices, see the cloud MQTT broker guide.
Track Your Fleet with ConnectMQ
Create a free Serverless broker at app.connectmq.com with no credit card, point your trackers or the simulator at it over TLS, and watch positions arrive in the live topic explorer. ConnectMQ is a fully managed MQTT service, with Serverless (Paid) and Dedicated plans as your fleet grows.
Frequently Asked Questions
How often should a vehicle tracker publish its position over MQTT?
Every 5 to 15 seconds while moving and every few minutes while parked is a common starting point. Adjust based on your use case and data plan.
Which QoS should I use for GPS positions?
QoS 0 is often fine for frequent positions because the next fix replaces the last. Use QoS 1 for events and commands that must arrive.
How do I know when a vehicle goes offline?
Register a retained Last Will of offline on the vehicle's status topic and publish a retained online after connecting.
Can ConnectMQ store location history?
No, the broker routes messages. Run your own subscriber service to write positions into a database such as PostgreSQL with PostGIS.



