Managed MQTT Cloud

MQTT Vehicle Tracking: Topics, Payloads and a Live Fleet Map

Build MQTT vehicle tracking that works on patchy cellular links: topic design, compact GPS payloads, keep alive tuning, offline detection and a live web map.

MQTT vehicle tracking illustration showing delivery vans sending GPS positions over cellular to a cloud broker and a live fleet map
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

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:

  1. Always include the device timestamp. Messages buffered during a coverage gap arrive late; the map and history must order by fix time.
  2. Batch when buffering. After a tunnel, publish one message containing an array of buffered fixes rather than hundreds of individual messages.
  3. Report adaptively. Every 5 to 15 seconds while moving, every few minutes while parked, and immediately on significant heading changes.
  4. Drop bad fixes on the device. If fix quality is 0, do not publish a position.
  • 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 offline will on the status topic and publish a retained online after 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

  1. Unique credentials and client ID per tracker, TLS on 8883.
  2. Device timestamps on every fix; batch after outages.
  3. Adaptive reporting interval.
  4. Retained status with Last Will.
  5. Persistent sessions and message expiry for commands.
  6. No personal data in topic names.
  7. 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.

Related articles

BYOC MQTT Broker: Bring Your Own Cloud Explained

A BYOC MQTT broker runs in your cloud account but is operated by a provider. Learn how the model works, the responsibility split, and how to prepare your account for it.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.