Chapters

15 Lab 4: Secure MQTT Broker and Reliability

mqtt
implementation
labs

This is the fourth and final bounded MQTT lab. It starts where the public-broker exercises stop: use a broker you control, establish its trust boundary, and retain evidence for every security and reliability claim.

15.1 Lab 4: Secure MQTT with TLS and Authentication

Objective: Implement MQTT security using TLS encryption and username/password authentication.

Materials:

  • ESP32 or Python client
  • Private MQTT broker (Mosquitto installed locally or on Raspberry Pi)
  • SSL/TLS certificates

Step 1: Install and Configure Mosquitto Broker

Install Mosquitto on Linux/Raspberry Pi:

sudo apt update
sudo apt install mosquitto mosquitto-clients

Step 2: Generate SSL/TLS Certificates

# Create certificate directory
sudo mkdir -p /etc/mosquitto/certs
cd /etc/mosquitto/certs

# Generate CA certificate
sudo openssl req -new -x509 -days 365 -extensions v3_ca \
  -keyout ca.key -out ca.crt \
  -subj "/C=US/ST=State/L=City/O=IoTClass/CN=CA"

# Generate server key and certificate
sudo openssl genrsa -out server.key 2048
sudo openssl req -new -out server.csr -key server.key \
  -subj "/C=US/ST=State/L=City/O=IoTClass/CN=mqtt.local"
sudo openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
  -CAcreateserial -out server.crt -days 365

# Set permissions
sudo chmod 644 /etc/mosquitto/certs/*.crt
sudo chmod 600 /etc/mosquitto/certs/*.key

Step 3: Configure Mosquitto with Security

Edit /etc/mosquitto/mosquitto.conf:

# Default listener (disabled)
listener 1883
allow_anonymous false

# TLS listener
listener 8883
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key
require_certificate false

# Password file
password_file /etc/mosquitto/passwd

# Logging
log_dest file /var/log/mosquitto/mosquitto.log
log_type all

Step 4: Create User Accounts

# Create password file with first user
sudo mosquitto_passwd -c /etc/mosquitto/passwd iotuser

# Add more users (without -c flag)
sudo mosquitto_passwd /etc/mosquitto/passwd admin

Step 5: Restart Mosquitto

sudo systemctl restart mosquitto
sudo systemctl status mosquitto

Before testing credentials, inspect Figure 15.1 to place each control at the boundary where the broker can enforce it. The connection is acceptable only when transport, identity, and topic permissions all hold for the same client.

MQTT publish/subscribe diagram with an IoT sensor publisher, secured broker, dashboard and mobile subscribers, topic paths, and security layers for TLS transport, authentication, and authorization.
Figure 15.1: MQTT security architecture layers TLS transport, authentication, and topic-level authorization around the broker that routes messages from publishers to subscribers.

Read Figure 15.1 from the publisher’s protected connection to the broker and then outward to subscribers. TLS protects each client-to-broker path and validates the server endpoint; authentication binds that connection to iotuser or another distinct identity; authorization then limits which topics that identity may publish or subscribe to. The broker remains a trusted plaintext and routing boundary, so the positive and negative commands below must prove all three layers rather than treating a successful TLS handshake as the whole security result.

Testing Connection Security:

# Test with mosquitto_pub (with authentication)
mosquitto_pub -h mqtt.local -p 8883 \
  -u iotuser -P your_password \
  --cafile /etc/mosquitto/certs/ca.crt \
  -t "secure/test" -m "Hello from terminal" -q 1

# Test without authentication (should fail)
mosquitto_pub -h mqtt.local -p 8883 \
  --cafile /etc/mosquitto/certs/ca.crt \
  -t "secure/test" -m "This will fail"

Learning Outcomes:

  • Generate SSL/TLS certificates for MQTT
  • Configure Mosquitto broker with security
  • Implement username/password authentication
  • Use encrypted MQTT connections
  • Understand certificate-based security
  • Test and troubleshoot secure MQTT

Security Best Practices:

  1. Always use TLS in production (port 8883)
  2. Disable anonymous access
  3. Use strong passwords (12+ characters)
  4. Implement topic-level ACLs (Access Control Lists)
  5. Keep certificates updated (renew before expiration)
  6. Monitor broker logs for suspicious activity
  7. Use client certificates for enhanced security (mutual TLS)
Broker BexCheckpoint: Secure Broker Baseline

You now know:

Read the checkpoint as one evidence chain. Begin with The secure lab moves MQTT traffic to TLS port 8883 with CA, server certificate, and server key files. Then connect The minimum broker baseline disables anonymous access and adds a password file before production testing. Finish with The negative test matters: an unauthenticated mosquitto_pub command should fail before you trust the broker configuration.

Try It: MQTT Security Configuration Advisor

Select your deployment scenario and security requirements to get a tailored Mosquitto configuration with an overall security score.

15.2 Interactive Simulator: MQTT QoS Levels and Last Will Testament

What This Simulates: ESP32 demonstrating MQTT Quality of Service levels and Last Will and Testament for reliable communication

QoS Levels Explained:

QoS 0 (At Most Once):          QoS 1 (At Least Once):      QoS 2 (Exactly Once):
Publisher -> Message ->         Publisher -> Message ->      Publisher -> Message ->
Broker -> Subscriber            Broker -> Subscriber         Broker -> PUBREC <-
                                Broker <- PUBACK             Publisher -> PUBREL ->
Fast, no guarantee              May duplicate                Broker -> PUBCOMP <-
                                Acknowledged                 Guaranteed once

Use for: sensor readings        Use for: commands            Use for: billing data
Power: Lowest                   Power: Medium                Power: Highest
Latency: ~5ms                   Latency: ~15ms               Latency: ~30ms

Run it: Rather than tracing the handshake diagrams above, operate the QoS Delivery Workbench to see delivery guarantees play out. Set the Publish QoS to 0, 1, and 2 in turn, then change the Network event to drop the first PUBLISH, the acknowledgement, or the QoS 2 PUBREL and press Play to watch which messages are lost, retried, or duplicated. Lower the Subscriber maximum QoS to see a downgrade, adjust Messages per minute and Round-trip time, and toggle the idempotent option, then use those observations to answer the QoS choice in the check below.

15.3 Quick Check: QoS Levels

Last Will and Testament (LWT):

Device connects with LWT set:
{
  topic: "devices/sensor01/status",
  message: "offline",
  qos: 1,
  retain: true
}

Normal flow:                    Unexpected disconnect:
1. Device connects              1. Device crashes
2. Publishes "online"           2. Network timeout (60s)
3. Sends data periodically      3. Broker detects disconnect
4. Publishes "offline"          4. Broker publishes LWT:
5. Disconnects gracefully          "devices/sensor01/status" -> "offline"
                                5. Monitoring system alerted

Result: Device status always known, even after crash

How to Use:

Build the decision in sequence. Begin with Click the Play button to start simulation. Then consider Watch LWT being set during connection. Then consider Observe messages sent with different QoS levels. Then consider See PUBACK responses for QoS 1. Close by considering Monitor retained status messages.

Learning Points

QoS Level Comparison:

Compare the delivery exchanges in increasing protocol cost. QoS 0 sends without an acknowledgement and suits replaceable sensor samples. QoS 1 adds PUBACK, providing at-least-once delivery while requiring the application to tolerate duplicates; commands can use it when their handlers are idempotent. QoS 2 completes the PUBLISH/PUBREC/PUBREL/PUBCOMP exchange so one delivery is presented to the receiver, and should be reserved for the uncommon message whose loss and duplication are both unacceptable. The packet sequence, not a fixed byte count, is the dependable comparison because topic length and Remaining Length encoding also affect overhead.

Last Will and Testament Flow:

Trace Last Will behavior from connection setup to failure evidence. In its CONNECT, client sensor01 asks the broker to hold the payload "offline" on devices/sensor01/lwt, here at QoS 1 with the retain flag set, and negotiates a 60-second keep-alive. The broker stores that will without publishing it. A graceful disconnect discards it; an ungraceful connection loss causes the broker to publish it. Monitoring clients subscribed to devices/+/lwt can then alert an operator or trigger remediation. This ordering distinguishes a broker-observed loss of connection from an application choosing to report itself offline.

Real-World Applications:

Build the decision in sequence. Begin with Healthcare - Patient monitor disconnection alerts (QoS 2). Then consider Industrial - Equipment status tracking with LWT. Then consider Smart Home - Door/window sensor states (QoS 1, retained). Then consider Fleet Management - Vehicle tracking with offline detection. Close by considering Agriculture - Irrigation system failure notification.

Experiments to Try:

Build the decision in sequence. Begin with Compare QoS - Send 100 messages with each QoS, measure time. Then consider Network Interruption - Disconnect Wi-Fi, observe LWT trigger. Then consider Retained Status - New subscriber receives last status immediately. Then consider Duplicate Detection - QoS 1 may deliver twice, handle with message IDs. Close by considering Battery Impact - Calculate battery life with different QoS levels.

Choosing QoS Level (Rule of Thumb):

Build the decision in sequence. Begin with Telemetry (temperature, heartbeat): QoS 0 — frequent, replaceable. Then consider Commands (door sensor, firmware): QoS 1 — important, idempotent. Close by considering Transactions (payment, config change): QoS 2 — critical, non-idempotent.

LWT Best Practices:

DO:
- Set LWT on critical devices
- Use retain=true for status topics
- Set QoS 1 for LWT
- Include timestamp in LWT message
- Subscribe to +/status for monitoring

DON'T:
- Use large LWT messages (keep <100 bytes)
- Set very short keep-alive (<30s)
- Forget to publish "online" after connect
- Use QoS 0 for LWT (unreliable)

Example LWT message:
{
  "status": "offline",
  "reason": "unexpected",
  "timestamp": 1698765432,
  "last_seen": "2024-10-28T10:23:52Z"
}
Try It: MQTT Keep-Alive and LWT Timeout Planner

Configure keep-alive and LWT settings to balance between fast disconnect detection and network overhead. The broker detects a dead client after 1.5x the keep-alive interval (per MQTT spec).

Run it: Before you fix keep-alive and LWT values in the planner below, watch how the broker actually detects a lost client in the Last Will Workbench. Set the Keep Alive interval and a Will Delay, choose the Keep-alive timeout scenario, and press Play or Trigger event to see the broker publish the Will after 1.5x keep-alive; then compare the Graceful disconnect and Reconnect before Will Delay scenarios, which suppress the Will. Carry the detection-time and false-trigger behavior you observe into the keep-alive and QoS choices you record in the planner.

15.3.1 Pitfall: Stale Retained Messages After Device Removal

The Mistake: Developers use retained messages for device status (e.g., devices/sensor42/status with retain=true), but when devices are decommissioned or replaced, the old retained messages remain on the broker indefinitely. New subscribers receive stale “online” status for devices that no longer exist.

Why It Happens: Retained messages persist until explicitly cleared with an empty payload. Most developers focus on the “publish” side and forget that retained messages require lifecycle management. Brokers like Mosquitto will keep retained messages forever unless configured otherwise.

The Fix: Implement device decommissioning that clears retained messages. Publish an empty payload (zero-length) with retain=true to delete the retained message. For MQTT 5.0, use Message Expiry Interval to auto-expire stale status.

import paho.mqtt.client as mqtt

# MQTT 3.1.1: Clear retained message on device removal
def decommission_device(client, device_id):
    # Publish empty payload with retain=true to clear
    for suffix in ("status", "config", "lwt"):
        result = client.publish(
            f"devices/{device_id}/{suffix}", payload="", qos=1, retain=True
        )
        result.wait_for_publish()
        if result.rc != mqtt.MQTT_ERR_SUCCESS:
            raise RuntimeError(
                f"Failed to clear retained {suffix} message: MQTT code {result.rc}"
            )
    print(f"Cleared all retained messages for {device_id}")

# MQTT 5.0: Auto-expire status messages
from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes

props = Properties(PacketTypes.PUBLISH)
props.MessageExpiryInterval = 300  # Expire after 5 minutes of no update

result = client.publish(
    "devices/sensor01/status",
    payload='{"status": "online", "timestamp": 1699123456}',
    qos=1,
    retain=True,
    properties=props
)
result.wait_for_publish()
if result.rc != mqtt.MQTT_ERR_SUCCESS:
    raise RuntimeError(f"Publish failed with MQTT code {result.rc}")
# If device stops publishing, status auto-clears after 5 minutes

Broker Configuration (Mosquitto): Set retain_available false to disable retained messages entirely if your application doesn’t need them, or use \$SYS/broker/retained messages/count to monitor accumulation.

15.3.2 Pitfall: Last Will Triggered on Graceful Disconnect

The Mistake: Developers configure Last Will and Testament (LWT) to publish “offline” status, expecting it only fires on crashes or network failures. But they observe LWT messages being published even during normal shutdown sequences, flooding monitoring systems with false “offline” alerts.

Why It Happens: The MQTT spec states LWT is published when the broker closes the connection “without receiving a DISCONNECT packet.” Many developers call client.disconnect() but don’t wait for completion, or the TCP connection closes before the DISCONNECT packet is sent. Network issues during graceful shutdown can also prevent DISCONNECT delivery.

The Fix: Always explicitly publish “offline” status before disconnecting, then send DISCONNECT. Use blocking disconnect or wait for confirmation. For Python paho-mqtt, use disconnect() followed by loop_stop() in the correct order.

// ESP32: Graceful shutdown with explicit status
void gracefulShutdown() {
    // Step 1: Publish explicit offline status
    mqttClient.publish("devices/sensor01/status", "offline", true);  // retain=true
    delay(100);  // Allow time for publish to complete

    // Step 2: Send DISCONNECT packet (prevents LWT)
    mqttClient.disconnect();
    delay(100);  // Allow TCP to close cleanly

    // Step 3: Now safe to power down
    WiFi.disconnect(true);
    esp_deep_sleep_start();
}

// If LWT fires anyway, it's redundant (already sent "offline")
// Better than missing offline status if crash occurs before explicit publish
# Python paho-mqtt 2.0+: Proper disconnect sequence
import time

def graceful_disconnect(client):
    # Publish explicit status first
    result = client.publish("devices/sensor01/status", "offline", qos=1, retain=True)
    result.wait_for_publish(timeout=5.0)  # Block until published

    # Now disconnect - this prevents LWT from firing
    client.disconnect()
    client.loop_stop()  # Stop network thread AFTER disconnect

# Common mistake: loop_stop() before disconnect()
# This kills the network thread before DISCONNECT packet is sent
# Result: Broker never receives DISCONNECT, triggers LWT

MQTT 5.0 Enhancement: Use Reason Code in DISCONNECT to tell broker why you’re disconnecting. Reason Code 0x00 (Normal disconnection) explicitly signals “don’t publish LWT.”

Battery Life Calculation:

Scenario: Send temperature every 60 seconds

QoS 0:
- Assumptions: ~120 mA radio current, ~50 ms radio-on time per publish
- Per message: 120 mA x 50 ms / 3,600,000 = 0.0017 mAh
- Per day: 1440 x 0.0017 = 2.4 mAh/day
- Battery (2000 mAh): 2000 / 2.4 = ~833 days (~2.3 years)

QoS 1:
- Assumptions: ~120 mA radio current, ~150 ms radio-on time (PUBLISH + PUBACK)
- Per message: 120 mA x 150 ms / 3,600,000 = 0.0050 mAh
- Per day: 1440 x 0.0050 = 7.2 mAh/day
- Battery (2000 mAh): 2000 / 7.2 = ~278 days (~9 months)

QoS 2:
- Assumptions: ~120 mA radio current, ~300 ms radio-on time (4-way handshake)
- Per message: 120 mA x 300 ms / 3,600,000 = 0.0100 mAh
- Per day: 1440 x 0.0100 = 14.4 mAh/day
- Battery (2000 mAh): 2000 / 14.4 = ~139 days (~4.6 months)

Conclusion: QoS 0 uses ~67% less TX energy than QoS 1; QoS 2 has the highest overhead.

Note: These estimates isolate MQTT exchange time. If the device reconnects Wi-Fi/TLS each minute (common with deep sleep), connection overhead will dominate and battery life will be much lower.

15.3.3 Putting Numbers to It

The QoS battery calculations above are a simplified “always-on” radio model. With ESP32 deep sleep, connection setup often dominates:

Deep sleep scenario (ESP32 wakes every 60 s, sends 1 reading, sleeps):

Wi-Fi connection overhead:

  • Scan channels: 200 ms @ 80 mA = 4.44 µAh
  • Associate + DHCP: 1,500 ms @ 100 mA = 41.67 µAh
  • Total connection: 46.11 µAh per wake

MQTT connection (clean session):

  • TCP handshake: 150 ms @ 80 mA = 3.33 µAh
  • CONNECT + CONNACK: 100 ms @ 80 mA = 2.22 µAh
  • Total MQTT setup: 5.55 µAh

Message transmission (QoS 0):

  • PUBLISH: 50 ms @ 120 mA = 1.67 µAh
  • Total message: 1.67 µAh

Per-wake cycle: Etotal=46.11+5.55+1.67=53.33 µAhE_{total} = 46.11 + 5.55 + 1.67 = 53.33 \text{ µAh}

Daily energy (1,440 wakes): Eday=1,440×53.33=76,795 µAh=76.8 mAhE_{day} = 1{,}440 \times 53.33 = 76{,}795 \text{ µAh} = 76.8 \text{ mAh}

Battery life (2000 mAh): 2,00076.8=26 days\frac{2{,}000}{76.8} = 26 \text{ days}

Key insight: In this example, Wi-Fi setup is much larger than the MQTT publish. Treat persistent sessions, sleep interval, retained status, and TLS setup as scenario variables instead of assuming a fixed battery-life multiplier.

Broker BexCheckpoint: Reliability and Energy Evidence

You now know:

Read the checkpoint as one evidence chain. Begin with QoS choices change both delivery guarantees and packet exchanges: QoS 0 has no acknowledgement, QoS 1 adds PUBACK, and QoS 2 adds PUBREC, PUBREL, and PUBCOMP. Then connect Last Will evidence depends on the broker not receiving DISCONNECT; keep-alive detection uses 1.5x the configured interval. Finish with In the deep sleep example, Wi-Fi setup, MQTT setup, and one publish add to 53.33 uAh per wake, which is the number to challenge before promising battery life.

15.4 Interactive Design Tools

Use these calculators as scenario estimators for MQTT deployments. Confirm final sizing with measurements from your broker, radio, TLS settings, and cloud pricing.

15.4.1 MQTT QoS Battery Life Calculator

Estimate how MQTT publish exchange time changes battery demand at each QoS level.

15.4.2 MQTT Broker Capacity Planner

Plan your MQTT broker resources based on device count and connection patterns.

15.4.3 MQTT Message Cost Estimator

Estimate monthly MQTT message charges and potential savings from change-based publishing. Update the unit price to match the provider and region you are using.

15.4.4 MQTT Deep Sleep Energy Calculator

Model the full wake cycle energy for ESP32 with deep sleep, including Wi-Fi and MQTT connection overhead.

Broker BexCheckpoint: Calculator Results to Review Evidence

You now know:

Read the checkpoint as one evidence chain. Begin with The battery calculator isolates MQTT publish exchange time, so it should not be treated as a full product battery-life estimate by itself. Then connect The broker capacity planner uses the same connection formula as the pitfall section: devices x 1.2, backend services x 2, and monitoring x 3. Finish with The cost and deep sleep calculators turn the lab trade-offs into reviewable claims about message volume, heartbeat interval, Wi-Fi setup, and MQTT setup overhead.

15.5 Knowledge Check

Test your understanding of MQTT implementations with these questions:

15.6 Matching Quiz: MQTT Concepts and Definitions

15.7 Ordering Quiz: Secure MQTT Broker Setup

15.8 Auto-Gradable Quick Check

15.9 Label the Diagram

15.10 Code Challenge

15.11 Deep-Dive Note: Presence Evidence Boundaries

Last Will and Testament is strongest when the lab treats presence as evidence, not as a dashboard badge. A useful transcript shows the subscriber already listening, the will-enabled client connecting, the exact ungraceful stop, and the broker publication on the status topic. Record the broker, TLS mode, username or client-certificate method, will topic, retain flag, QoS, keep-alive, and termination method so another engineer can reproduce why the will did or did not fire.

Run this test on a private or local broker when the topic carries device status. Public brokers are useful for syntax checks, but they do not provide stable authorization boundaries, broker log access, or protection from another user publishing to the same topic. In production, scope the status topic by site and device id, protect it with ACLs so only the device or trusted service can write it, and include timestamps so operators can distinguish a fresh offline event from stale retained state.

The under-the-hood failure to rehearse is split-brain presence. A device can publish retained online before its sensors are ready, a long Will Delay Interval can make alarms late, and an aggressive keep-alive can make mobile or cellular devices flap offline during normal roaming. MQTT 5 Will Delay helps short network blips by waiting before publishing the will; retained-message cleanup prevents a decommissioned device or reprovisioned replacement from inheriting another device’s stale marker.

Keep one presence acceptance record: clean DISCONNECT suppresses the will, killed process or network drop fires it after keep-alive detection, late subscribers see the retained state, and decommissioning clears retained status topics.

15.12 Summary

This lab moved the earlier publisher, dashboard, and automation evidence behind a broker boundary you control. You configured TLS, credentials, and ACLs; exercised QoS and Last Will behaviour; and used capacity, cost, and battery calculations to state where the design remains safe and where it needs another test.

15.13 Concept Relationships

MQTT Hands-On Labs connect to:

Carry the chapter forward as one connected chain. First, MQTT Publisher-Subscriber Setup - Foundation skills (pub/sub, topics, brokers) applied in these labs. Then, MQTT Python Patterns - Production patterns (callbacks, reconnection, TLS) demonstrated in labs. Then, MQTT Security - Lab 4 implements TLS, authentication, and ACLs from security chapter. Finally, Sensor Integration - DHT22 sensor interfacing techniques used in Lab 1.

Lab progression: Lab 1 (basic publisher) → Lab 2 (Python dashboard) → Lab 3 (multi-device automation) → Lab 4 (secure deployment). Each builds on previous concepts while adding complexity.

15.14 See Also

Continue through these resources in a deliberate order. Begin with MQTT QoS - Deep dive into QoS selection implemented in these labs. Then use MQTT Sessions - Persistent session and reconnection behavior used by reliable clients. Then use Sensor Lab Implementation - Hardware setup and sensor-readout practices for ESP32 labs. Finish with MQTT Production Review - Advanced patterns and production deployment.

15.15 What’s Next

Move from lab evidence to production decisions in a deliberate order. Start with MQTT QoS and MQTT Sessions when acknowledgement, persistence, or queue behavior is still unclear. Continue to MQTT Security Fundamentals for mutual authentication, certificate lifecycle, and threat boundaries, then MQTT Python Patterns for reconnection, callbacks, error handling, and batching. Use Sensor Lab Implementation when the hardware and measurement path need proof. Finish with MQTT Production Review, where protocol comparison, load testing, failure simulation, clustering, and capacity evidence turn the individual lab results into an operational release decision.

15.16 Key Takeaway

Hands-on MQTT labs should verify observable broker behavior, not only code execution. Check subscription filters, retained state, Last Will messages, QoS acknowledgements, and reconnection behavior in the broker logs or dashboard.