MQTT Security

MQTT Security Best Practices: A 12-Point Checklist for IoT Teams

A hands-on MQTT security checklist covering TLS, credentials, client IDs, topic design, payload validation, rate limits and certificate checks, plus how ConnectMQ handles each point.

Padlock over an MQTT broker diagram illustrating an MQTT security best practices checklist
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 security comes down to a handful of habits that most teams know about and still skip under deadline pressure. This guide turns them into a 12-point checklist you can apply to any deployment, with copy-paste code for verifying TLS in Python and on the command line. For each point we also say what a managed broker like ConnectMQ already does for you, and what is still your job.

Why MQTT Security Needs Its Own Checklist

MQTT was designed to be lightweight. The protocol itself defines a username and password field in CONNECT and leaves everything else — encryption, authorization, abuse protection — to the broker and the network layer. That flexibility is great for constrained devices, but it also means an MQTT deployment is exactly as secure as its weakest configuration choice.

The typical failure modes are boring and avoidable:

  • Devices talking plain TCP on port 1883 over the open internet.
  • One shared password burned into every device's firmware.
  • Topics like customers/jane.doe@example.com/location that leak data through the topic name alone.
  • Subscribers that trust every payload and crash (or worse) on malformed input.
  • Clients that "fix" a TLS error by turning certificate verification off.

If you are new to how brokers route messages, start with What Is an MQTT Broker? and come back here.

The MQTT Security Checklist at a Glance

# Practice Your responsibility What ConnectMQ provides
1 Always use TLS Connect on 8883 / 8084 TLS on 8883, WSS on 8084, Let's Encrypt certificate
2 Keep real data out of the public area Use portal credentials in production Tenant topics isolated from the public area
3 Unique credentials per broker/device Do not reuse one password everywhere Username, password and client ID generated per broker
4 Restrict client IDs Use the issued ID and suffixes Only the issued client ID and its _suffix variants are accepted
5 Store secrets safely on devices Encrypted storage, no secrets in Git —
6 Design topics that leak nothing No personal data in topic names Multi-tenant isolation
7 Validate every payload Schema checks in subscribers Message size limits per plan
8 Apply rate limits and quotas Throttle publishers Per-plan quotas, reason code 0x97 Quota exceeded
9 Defend against brute force and floods Back off on failures IP blocking after repeated bad credentials, connection-flood protection
10 Monitor connection state Alert on unexpected disconnects $SYS/broker/connection/{clientId}/state
11 Validate certificates in clients Never disable verification Publicly trusted certificate chain
12 Authorize at the topic level Separate brokers or apps by trust boundary Tenant-level isolation

The rest of this article walks through each point.

1. Always Use TLS (Ports 8883 and 8084)

Plain MQTT on port 1883 sends usernames, passwords and payloads in clear text. Anyone on the path — a coffee-shop Wi-Fi, a compromised router, a mirrored switch port — can read and replay them. TLS fixes that by encrypting the connection and proving the broker's identity.

On ConnectMQ every broker accepts:

  • MQTT over TLS on mqtts://broker.connectmq.com:8883
  • MQTT over secure WebSocket on wss://broker.connectmq.com:8084/mqtt

Both use a Let's Encrypt certificate, so standard operating-system trust stores already trust it. Treat ports 1883 and 8083 as development-only. Browser apps must use WSS anyway; see MQTT over WebSocket for a full example.

2. Never Put Real Data in the Public Area

broker.connectmq.com offers an anonymous public area for experiments: connect with no username, a unique client ID, and you can publish and subscribe immediately. It is fantastic for learning, and it is completely public. Anyone who subscribes to the right topic can read your messages.

Rules for the public area:

  • Use a random prefix such as connectmq/demo/<your-random-id>/... so you do not collide with others.
  • Never publish personal data, credentials, locations or anything you would not post on a forum.
  • Move to portal credentials before real users or real devices are involved.

Authenticated tenants on ConnectMQ are isolated: their topics are not visible to other tenants or to the public area, and public clients only see other public clients. More details are in our guide to the free public MQTT broker.

3. Use Unique Credentials per Broker and Device

A single password shared across a fleet means one extracted firmware image compromises every device. Aim for credentials that can be revoked without touching the rest of the fleet.

In the ConnectMQ portal (https://app.connectmq.com) each broker you create gets a generated username, password and client ID. Generated secrets avoid the classic weak choices (admin/admin, iot123) and keep environments separate: create one broker for staging and another for production rather than sharing credentials between them.

Regardless of the broker, rotate credentials when a device is decommissioned and never paste production secrets into tickets, chat or screenshots.

4. Restrict Client IDs

The MQTT client ID identifies a session. If anyone can connect with any client ID, an attacker who knows a device's ID can kick it offline (the broker disconnects the existing session when a duplicate ID connects) or hijack its persistent session.

ConnectMQ restricts authenticated connections to the client ID issued for the broker, plus _suffix variants of it. If your issued ID is YOUR_CLIENT_ID, you can connect YOUR_CLIENT_ID_sensor1, YOUR_CLIENT_ID_dashboard and so on, but not arbitrary IDs. That gives you predictable, auditable session names and stops strangers from impersonating your sessions.

Practical tip: derive the suffix from something stable on the device, such as its serial number or MAC address, so reconnects reuse the same session.

5. Store Secrets Safely on Devices

Credentials have to live somewhere on the device. Make that place as hard to reach as practical:

  • Do not commit credentials to source control. Use a separate, git-ignored config header or environment file.
  • Use the platform's protected storage where it exists (encrypted flash / NVS encryption on ESP32, a TPM or secure element on gateways, a secrets manager on servers).
  • Disable debug ports and serial consoles in production firmware so secrets cannot be dumped trivially.
  • Plan a provisioning step that writes per-device secrets at manufacturing time instead of compiling them into a shared image.

6. Design Topics That Do Not Leak Information

Topic names are metadata, and metadata leaks. Even with encrypted payloads, a topic such as hospital/ward3/patient-4471/heart-rate reveals a lot to anyone who can observe it.

Guidelines:

  • Use opaque identifiers (site-07/dev-3fa9/telemetry) rather than names, emails or addresses.
  • Keep a consistent hierarchy, for example site/area/line/device/kind, so permissions and wildcards stay manageable.
  • Avoid broad wildcard subscriptions like # in production consumers; subscribe to what you need.
  • Keep the number of levels modest. The public area, for instance, allows at most 8 topic levels.

7. Validate Every Payload

The broker moves bytes; it does not know whether your JSON is well formed or whether a temperature of 9,000 °C is plausible. Every subscriber should treat incoming payloads as untrusted input.

import json

ALLOWED_KEYS = {"temperature", "humidity", "ts"}

def parse_telemetry(raw: bytes):
    if len(raw) > 4096:
        raise ValueError("payload too large")
    data = json.loads(raw.decode("utf-8"))
    if not isinstance(data, dict) or not set(data) <= ALLOWED_KEYS:
        raise ValueError("unexpected fields")
    t = float(data["temperature"])
    if not -40.0 <= t <= 125.0:
        raise ValueError("temperature out of range")
    return data

Reject, log and move on. Never pass payload content directly into SQL, shell commands or HTML. ConnectMQ enforces maximum message sizes per plan (for example 256 KB on the free Serverless plan and 64 KB in the public area), which caps the worst case, but schema validation remains your job.

8. Apply Rate Limits and Quotas

A buggy firmware loop that publishes as fast as it can is a denial-of-service attack you wrote yourself. Throttle on the device (publish on change or on a fixed interval) and expect the broker to enforce limits too.

ConnectMQ applies quotas per plan and per area. The public area allows 10 messages per second and 10 subscriptions per client, for example. When a client exceeds a quota on MQTT 5, the broker answers with reason code 0x97 Quota exceeded; under heavy load it can respond with 0x89 Server busy. Handle these codes explicitly: back off, log, and alert instead of retrying in a tight loop. The MQTT 5 features guide explains reason codes in more depth.

9. Protect Against Brute Force and Connection Floods

Exposed brokers see constant password guessing and reconnect storms. On ConnectMQ:

  • IP addresses are blocked after repeated bad-credential attempts.
  • Connection floods trigger temporary bans.
  • Adaptive overload protection keeps the cluster responsive under spikes.

On the client side, implement exponential backoff with jitter. If a whole fleet reconnects at the same instant after a network blip, even legitimate devices look like a flood.

10. Monitor Connection State with $SYS

You cannot secure what you cannot see. ConnectMQ publishes connection state per client at $SYS/broker/connection/{clientId}/state: 1 when the client connects and 0 when it disconnects. Subscribe from a monitoring service and alert on unexpected drops or on a device that connects at odd hours.

mosquitto_sub -h broker.connectmq.com -p 1883 -i monitor-7c2e \
  -t '$SYS/broker/connection/my-device-7c2e/state' -v
# $SYS/broker/connection/my-device-7c2e/state 1
# $SYS/broker/connection/my-device-7c2e/state 0

Combine this with MQTT Last Will messages so the rest of your system learns about unclean disconnects.

11. Validate Certificates in Every Client

TLS without certificate validation is encryption to an unknown party. An attacker in the middle can present any certificate and the client will happily send credentials to it. Unfortunately, "disable verification" is a common copy-paste fix for TLS errors.

Python (paho-mqtt 2.x with the ssl module)

import ssl
import paho.mqtt.client as mqtt

def on_connect(client, userdata, flags, reason_code, properties):
    print("Connected:", reason_code)

ctx = ssl.create_default_context()          # system CAs, hostname check on
ctx.minimum_version = ssl.TLSVersion.TLSv1_2

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="YOUR_CLIENT_ID")
client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
client.tls_set_context(ctx)
client.on_connect = on_connect
client.connect("broker.connectmq.com", 8883)
client.loop_forever()

Never ship ctx.check_hostname = False, ssl.CERT_NONE or client.tls_insecure_set(True).

mosquitto_pub and mosquitto_sub

On Linux, point the CLI at the system CA directory. On any platform, you can instead download the ISRG Root X1 certificate (isrgrootx1.pem) from Let's Encrypt and pass it with --cafile:

# Linux: system CA store
mosquitto_sub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
  -u YOUR_USERNAME -P YOUR_PASSWORD -i YOUR_CLIENT_ID_cli \
  -t "site-07/#" -v

# Any OS: explicit root certificate
mosquitto_pub -h broker.connectmq.com -p 8883 --cafile isrgrootx1.pem \
  -u YOUR_USERNAME -P YOUR_PASSWORD -i YOUR_CLIENT_ID_pub \
  -t "site-07/dev-3fa9/telemetry" -m '{"temperature":22.5}'

If the hostname or chain does not match, the connection fails. That failure is the feature.

12. Authorize at the Topic Level Where You Need It

As a general MQTT practice, the principle of least privilege applies to topics: a temperature sensor should publish only to its own telemetry topic and subscribe only to its own command topic. Many architectures enforce this with topic-level authorization rules or by placing devices with different trust levels behind separate credentials and separate brokers.

With ConnectMQ, the isolation boundary is the tenant: one tenant's topics are never visible to another tenant or to the public area. If you need hard separation between groups of devices, create separate brokers for them in the portal and keep application-level checks (who may send which command) in your backend.

A Secure Connection, End to End

Putting it together, a production-ready ConnectMQ client connects on port 8883 (or WSS 8084) with certificate validation on, authenticates with portal-generated credentials and a YOUR_CLIENT_ID_<device> client ID, publishes to opaque topics at a sensible rate, backs off on 0x97 and 0x89, and is monitored via $SYS.

For a device-side walkthrough, see the ESP32 MQTT tutorial, which ends with a TLS-secured version of the sketch.

Get Started with ConnectMQ

ConnectMQ gives you TLS and WSS endpoints, generated credentials, client ID restrictions, tenant isolation and abuse protection out of the box, so your team can focus on the parts of MQTT security that only you can own. Create a broker on the free Serverless plan at https://app.connectmq.com — no credit card required — and connect your first secure client in minutes.

Frequently Asked Questions

Is MQTT secure by default?

No. The MQTT protocol defines authentication fields but leaves encryption and authorization to the broker and transport. You need TLS, strong per-device credentials and sensible broker limits to make it secure.

Which port should I use for secure MQTT?

Use 8883 for MQTT over TLS and 8084 for MQTT over secure WebSocket (WSS). On ConnectMQ both use a publicly trusted Let's Encrypt certificate.

Can I use the public area of broker.connectmq.com in production?

No. The public area is anonymous and readable by anyone who subscribes to your topics. Use credentials from the ConnectMQ portal for any real data; authenticated tenants are isolated from the public area and from each other.

What does reason code 0x97 mean?

0x97 is the MQTT 5 reason code for "Quota exceeded". ConnectMQ returns it when a client exceeds a plan or area limit; your client should back off rather than retry immediately.

Is it OK to disable certificate verification for testing?

Only on a throwaway development machine, and never in shipped firmware or production code. Disabling verification removes protection against man-in-the-middle attacks, which is the main reason to use TLS.

Related articles

How to Rotate MQTT Credentials Without Downtime

A step-by-step dual-credential strategy for rotating MQTT usernames and passwords across a fleet: stage the new credential, switch with fallback, verify and only then revoke the old one.

7 min read

Storing MQTT Credentials Securely on Embedded Devices

A practical comparison of ways to protect MQTT usernames and passwords on ESP32 and Linux devices, from encrypted NVS and flash encryption to secure elements and TPM-backed systemd credentials.

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.