MQTT Security Best Practices (ESP32 + Home Networks)¶
MQTT is the backbone of most home IoT systems β lightweight, fast, and almost always misconfigured. A cleartext broker with anonymous access on your home LAN is one compromised laptop away from a full network pivot. These practices are proportionate for home use and scale to small commercial deployments.
1. Threat Model¶
| Threat | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Guest WiFi device sniffs MQTT | Medium | High (credential + data) | TLS, isolate MQTT to trusted VLAN |
| Compromised IoT device publishes to wrong topics | Medium | Medium (spoof sensor data) | Per-device ACLs |
| Default broker credentials | High | Critical | Disable anonymous, per-device passwords |
| Replay attack via retained command | Medium | Medium | No retained commands; QoS 0 for cmds |
| Internet-exposed broker | Low (if misconfigured) | Critical | Firewall; broker internal-only |
| Compromised device pivoting LAN | Medium | High | IoT VLAN isolation |
Realistic adversary at home: a compromised device on the same WiFi network. TLS + ACL + per-device credentials defeats most passive and active attacks in this context.
2. Broker Hardening¶
Mosquitto (recommended for home use)¶
# /etc/mosquitto/mosquitto.conf
# Disable anonymous access β this is the single most important setting
allow_anonymous false
# Auth via password file
password_file /etc/mosquitto/passwd
# Access control list
acl_file /etc/mosquitto/acl
# Bind to internal interface only β do NOT expose to 0.0.0.0 unless behind reverse proxy
listener 8883
bind_address 192.168.1.10 # your broker's LAN IP
# TLS (see Section 3)
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/broker.crt
keyfile /etc/mosquitto/certs/broker.key
tls_version tlsv1.2
Create a user:
sudo mosquitto_passwd -c /etc/mosquitto/passwd esp32_living_room
sudo mosquitto_passwd /etc/mosquitto/passwd esp32_bedroom
sudo mosquitto_passwd /etc/mosquitto/passwd homeassistant
One user per device. Never share credentials between devices β if one is compromised, you revoke that one user only.
ACL file structure¶
# /etc/mosquitto/acl
# Home Assistant: read everything, write nothing except cmd topics it controls
user homeassistant
topic read home/#
topic write home/+/+/cmd
# ESP32 living room: read its own cmd, write its own state and telemetry
user esp32_living_room
topic read home/livingroom/+/cmd
topic write home/livingroom/+/state
topic write home/livingroom/+/telemetry
topic write home/livingroom/+/availability
# Deny all else implicitly β ACL is deny-by-default for listed users
Key rules: - No # wildcard write access for any device - Devices can only write to their own namespace - HA reads broadly but writes narrowly - Presence of topic read # for any user is a red flag β review immediately
3. TLS Configuration¶
Why not port 1883 (plaintext)?¶
Cleartext MQTT on port 1883 means: - Any device on the LAN can sniff all sensor data and commands - Credentials are transmitted in plaintext - Replay attacks are trivial
Use port 8883 (MQTT over TLS) always.
Self-signed CA (home use)¶
# Generate CA key and certificate
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
-subj "/CN=HomeIoT-CA"
# Generate broker key and CSR
openssl genrsa -out broker.key 2048
openssl req -new -key broker.key -out broker.csr \
-subj "/CN=broker.home.local"
# Sign broker cert with CA
openssl x509 -req -days 3650 -in broker.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial -out broker.crt
Copy ca.crt to all ESP32 devices. The CA cert is ~1400 bytes β small enough for NVS or #define in firmware.
Let's Encrypt (if broker has a public domain)¶
Use Certbot with DNS challenge. Works even for internal IP, provided you own a domain:
certbot certonly --dns-cloudflare --dns-cloudflare-credentials ~/.secrets/cf.ini \
-d mqtt.yourdomain.com
Add a cron job for renewal. Update Mosquitto certfile/keyfile paths and reload.
Client certificate auth (advanced)¶
Generate per-device client certs for mutual TLS. Requires more key management but eliminates password-based auth:
openssl genrsa -out esp32_living.key 2048
openssl req -new -key esp32_living.key -out esp32_living.csr -subj "/CN=esp32-living-room"
openssl x509 -req -days 1825 -in esp32_living.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out esp32_living.crt
Enable in Mosquitto: require_certificate true. ESP32 memory constraint: client certs add ~3β4 KB to heap usage. Test carefully on low-memory builds.
ESP32 TLS memory considerations¶
TLS on ESP32 uses mbedTLS. Memory costs: - TLS handshake: ~35β50 KB heap spike - Sustained TLS session: ~15β20 KB - Total free heap needed: at least 80 KB before TLS connect
Check before connecting:
Serial.printf("Free heap before MQTT: %u\n", ESP.getFreeHeap());
// Should be > 80000 β if not, reduce buffer sizes or disable logging
Do not use WiFiClientSecure with setInsecure() in production β it disables certificate verification, making TLS useless against MITM.
4. Topic Design¶
Use a structured, predictable namespace:
home/{room}/{device_type}/{measurement_or_cmd}
Examples:
home/livingroom/light/state β current state (ON/OFF), retained
home/livingroom/light/cmd β command topic, NOT retained
home/livingroom/bme280/temperature β sensor reading
home/livingroom/bme280/humidity
home/bedroom/motion/state
home/office/esp32_desk/availability β LWT topic
Rules: - state topics: retained, published by device - cmd topics: NOT retained, published by controller (HA) - availability topic: Last Will and Testament (LWT) β set on connect - Never embed credentials, UUIDs, or passwords in topic names - Avoid personal or location data that could identify occupancy patterns
5. Credential Storage on ESP32¶
// β
DO: store credentials in NVS, loaded at boot
#include <Preferences.h>
Preferences prefs;
void loadMqttCredentials(char* user, char* pass, size_t len) {
prefs.begin("mqtt", true);
prefs.getString("user", user, len);
prefs.getString("pass", pass, len);
prefs.end();
}
// β
DO: provision via captive portal or BLE provisioning on first boot
// β
DO: store CA cert as a string constant (embedded, not secret)
static const char MQTT_CA_CERT[] = R"EOF(
-----BEGIN CERTIFICATE-----
... your ca.crt contents here ...
-----END CERTIFICATE-----
)EOF";
// β NEVER:
const char* mqttUser = "esp32_living_room"; // hardcoded in source = in git history
const char* mqttPass = "hunter2"; // visible in binary flash dump
CA certificates are not secrets β they are public trust anchors. Embedding the CA cert in firmware is correct. Passwords are secrets β store in NVS.
6. Rate Limiting and Retained Messages¶
Retained messages on command topics are dangerous: - If a device restarts, it will immediately receive the last retained command - This can cause unexpected actuator activation (lights on, relays triggered) - Use retain=false for all command topics; let HA resend state on reconnect
QoS guidance: | QoS | Use case | |---|---| | 0 (fire and forget) | Sensor telemetry β occasional loss is acceptable | | 1 (at least once) | Commands, availability β must arrive, duplicates tolerable | | 2 (exactly once) | Rarely needed at home scale; higher overhead |
Publish rate limits: don't publish sensor data more than once per 10 seconds under normal conditions. Burst publishing fills broker queues and wastes bandwidth.
static uint32_t lastPublishMs = 0;
if (millis() - lastPublishMs > 10000) {
mqttClient.publish(topic, payload);
lastPublishMs = millis();
}
7. Security Checklist¶
Must-do¶
-
allow_anonymous falsein broker config - Unique username and password per device
- TLS enabled (port 8883) β no plaintext 1883 in production
- ACL file: devices write only to their own namespace
- CA cert pinned in ESP32 firmware (not
setInsecure()) - MQTT broker bound to internal LAN interface only β no internet exposure
- No retained command topics
Nice-to-have (but meaningful)¶
- IoT VLAN isolating all ESP32 devices from main LAN
- Client certificate authentication (mutual TLS)
- Broker access logs reviewed periodically
- LWT (Last Will and Testament) for each device's availability topic
- Unique MQTT client ID per device (use MAC or UUID)
- Firmware OTA updates signed and verified
See Also¶
See also
- Home Assistant Security Best Practices β secure the controller that manages your MQTT devices
- ESP32 + MQTT + Home Assistant Integration β see these security practices applied in a working firmware example
- Embedded Security & OTA β NVS credential storage, OTA signing, secure boot
- ESP32 Programming Architecture β non-blocking reconnect patterns for MQTT