I Turned a Meshtastic Node Into a Message Bus (MQTT in the Real World)

Mesh is fun when it's just radio nerds whispering into the void.

Mesh gets dangerous (in a good way) when you can capture, replay, route, alert, and integrate those messages with the rest of your tooling. That's where MQTT comes in.

This post is about taking Meshtastic's MQTT support and turning it into something closer to an actual message backbone — the kind you can plug dashboards into, feed into automation, and bridge across systems without duct-taping screenshots together like a caveman.

No personal network details here. Just the pattern.


The goal: from "mesh chat" to "message infrastructure"

Meshtastic gives you:

  • A low-power mesh radio network (LoRa)
  • Messaging + telemetry (depending on config)
  • Optional MQTT integration (publish/subscribe)

MQTT gives you:

  • A lightweight pub/sub bus
  • Persistence (if you want it), routing, bridging
  • A standard interface you can feed into dashboards, automations, and custom services

Put them together and you get:

  • Mesh traffic → MQTT topics → automations + analytics
  • A clean path to unify mesh, web services, monitoring, and "hacker stuff"

The mental model (don't skip this)

Think in layers:

  • Layer 1: The radios — radios talk LoRa to each other. That's the mesh.
  • Layer 2: A gateway node — one node (or your client app) is allowed to "bridge" what it sees to MQTT.
  • Layer 3: MQTT broker — Mosquitto (or other broker) receives publishes and handles subscriptions.
  • Layer 4: Consumers — anything subscribing to topics: dashboards, scripts, alerting, logging.

If something's broken, the fastest way to debug is to test each layer independently.


Topic strategy: don't paint yourself into a corner

The most common self-own I see is choosing an MQTT topic prefix that's too specific early on.

Example pattern:

  • Starting narrow: msh/US/reps4thor/#
  • Later wanting broad: msh/US/#

That shift is normal as your project grows. The problem is: MQTT security (ACLs) and consumers (subscribers) are usually built around your topic tree.

Practical advice

  • Pick a stable top-level prefix and treat it like an API contract.
  • Keep "project-specific" stuff one level down.

A clean hierarchy might look like:

  • msh/<region>/<channel>/...
  • msh/<region>/<device>/...
  • msh/<region>/telemetry/...

You want to be able to subscribe with:

  • Everything: msh/US/#
  • Only a channel: msh/US/reps4thor/#
  • Only telemetry: msh/US/telemetry/#

Design it like you're building the file system for your future self.


The broker: Mosquitto in Docker (the sane baseline)

Mosquitto is simple, fast, and doesn't need a ceremony to run.

The broker has two common listener modes:

  • 1883 = plaintext MQTT (easy for internal testing)
  • 8883 = MQTT over TLS (the grown-up version)

If you expose only TLS, your quick tests must use TLS too — otherwise you get the lovely "connection refused / handshake fail / nope" experience.

What you want in a production-ish mindset

  • TLS on 8883 for real clients
  • Optional 1883 only for internal debugging (or not at all)
  • Auth enabled (user/pass at minimum)
  • ACL rules so clients can't publish everywhere like a toddler with a marker

ACLs: the part everyone "means to do later" (and then regrets)

MQTT without ACLs is basically: "Come on in, yell whatever you want, wherever you want."

That's fine on your laptop. It's not fine on anything remotely reachable.

What you're protecting against

  • Random clients publishing garbage into your topics
  • Misconfigured clients clobbering your topic tree
  • Accidental publishes (the #1 cause of "why is my data nonsense?")

A solid minimum ACL posture

  • Give each client a username
  • Allow read/write only to the slice they need
  • Deny everything else by default

Example intent (not your actual file, just the idea):

  • A Meshtastic bridge client can publish to msh/US/#
  • Dashboards can read msh/US/# but not publish
  • A test client can publish only to msh/US/test/#

If you ever see errors like Denied PUBLISH, that's not Mosquitto being annoying — that's your broker doing its job.


How I test this end-to-end (without guessing)

Testing MQTT is about removing ambiguity. You want to answer:

  1. Is the broker reachable?
  2. Can I authenticate?
  3. Can I subscribe?
  4. Can I publish?
  5. Do ACLs allow what I think they allow?
  6. Is Meshtastic actually bridging?

Step 1 — verify the broker is listening where you think it is

Confirm which ports are exposed and whether you're supposed to use TLS or plaintext. If only 8883 is exposed, plaintext tests to 1883 will fail. If no ports are published, the broker is living happily in a container… alone.

Step 2 — run a subscriber first (always)

Start a listener before you publish. Example subscriptions:

  • msh/US/#
  • msh/US/test/#

Step 3 — publish a known-good test message

Publish a tiny payload to a safe test topic (example):

  • msh/US/test/pinghello world

Interpretation:

  • If the subscriber sees it: networking + auth + pub/sub works.
  • If you get Denied PUBLISH: ACL doesn't allow that client/topic.
  • If you get Connection refused: wrong port/host/protocol/firewall, or broker isn't running.

Step 4 — confirm Meshtastic messages appear on the broker

Once the broker is proven functional, validate the bridge. You're looking for consistent traffic under your expected prefix (for example msh/US/...).

Step 5 — watch the broker logs like it's a crime scene

Broker logs show connects/disconnects, auth failures, ACL denies, and TLS handshake issues. This is where you catch "it connects but can't publish" instantly.


Common failure modes (and what they usually mean)

  • "Connection refused" — wrong host/port/protocol, ports not published, firewall, or broker not running.
  • TLS handshake errors — TLS vs non-TLS mismatch, missing trust chain, cert/key problems.
  • "Denied PUBLISH" — ACL rules don't match your topic tree (often after changing prefixes).

Why this architecture is worth it

Once Meshtastic is feeding MQTT, everything becomes modular:

  • Want alerts? Subscribe and notify.
  • Want a dashboard? Subscribe and visualize.
  • Want storage? Subscribe and write to a DB.
  • Want bridging? Bridge MQTT to MQTT, MQTT to WebSockets, or whatever you want.

Treat mesh like "field layer telemetry + messaging" and MQTT like "transport + integration layer." That separation keeps the system flexible.


What I'm building next (and what you should build too)

  • A "health" topic — track nodes alive/last-seen/battery (if available).
  • A test harness — a dedicated subtree like msh/US/test/#.
  • A subscriber service — log + detect anomalies + alerting.
  • A bridge boundary — keep broker private when possible; if exposed, lock it down (TLS, auth, ACL, network restrictions).

Closing thought (Reps4Thor rule)

Mesh is cool. Mesh + MQTT is useful. And usefulness is where "hacker hobby" turns into "hacker infrastructure."

If you're running Meshtastic and you're not piping it into MQTT yet, you're basically sitting on a goldmine… and using it as a paperweight.