Securing MQTT, Auth, ACLs, and Certificate Rotation
TL;DR — TLS on every connection (8883 not 1883). Per-device credentials with rotation plan. ACL per device, scoped to its own topics. mTLS for high-security. Rotate certs annually. Audit logs to a SIEM.
Final how-to of August. After alerting, the security review of the whole IIoT stack. Factory networks have a reputation for being open by default. They shouldn’t be.
The threat model
Real threats for an IIoT MQTT deployment:
- Eavesdropping — someone on the network reads sensor data
- Data injection — attacker publishes fake sensor data or commands
- Device compromise — one device is hacked; attacker uses its creds to access others
- Lateral movement — through the MQTT broker to other factory systems
- Denial of service — flooding the broker with connections / messages
Each has specific controls.
TLS everywhere
Plain MQTT on port 1883 is plaintext. Anyone with packet capture can read everything. Always use 8883 (MQTT over TLS).
# mosquitto.conf
listener 8883
cafile /mosquitto/certs/ca.crt
certfile /mosquitto/certs/server.crt
keyfile /mosquitto/certs/server.key
require_certificate false # set true for mTLS
Even for “trusted internal networks” — assume the network has more devices than you know. TLS adds <1ms latency; the protection is essentially free.
Per-device credentials
One username/password per device. Not “all sensors share the credential sensor:sensor.”
Reasons:
- Compromise of one device = revoke its credential, not all
- Audit log identifies which device did what
- ACL can be scoped per device
mosquitto_passwd -b /etc/mosquitto/passwords device1 <random-strong-password>
mosquitto_passwd -b /etc/mosquitto/passwords device2 <random-strong-password>
Generate passwords with openssl rand -base64 24. Store in your config management (Ansible, Terraform, k8s secrets).
ACL per device
# /etc/mosquitto/acl.conf
# Pattern: each device writes only its own subtree
pattern readwrite factory/%u/#
# Specific devices with broader access
user gateway-01
topic readwrite factory/+/#
user dashboard-readonly
topic read factory/#
user admin
topic readwrite #
%u substitutes the connecting username. Device “press-42” can only publish to/subscribe to factory/press-42/#. Even if compromised, blast radius is contained.
mTLS for high-security
For higher security, require client certificates:
listener 8883
cafile /mosquitto/certs/ca.crt
certfile /mosquitto/certs/server.crt
keyfile /mosquitto/certs/server.key
require_certificate true
use_identity_as_username true
Now clients must present a cert signed by ca.crt. The cert’s CN becomes the username; ACL applies based on CN.
Pros:
- Username/password replaced by cert (can’t be brute-forced)
- Per-device unique cert; revocation via CRL
- Hardware-stored private keys possible (TPM, Atmel)
Cons:
- More setup; cert distribution
- Renewal at scale takes work
- Some embedded MQTT libraries struggle with TLS 1.2/1.3 + client certs
For 100s of devices, password works. For 1000s with strict requirements, mTLS.
Certificate rotation
Even self-signed CAs need renewal. Server cert at 1-2 year intervals; CA cert at 5-10.
Plan from day one:
- CA cert — long-lived (10 years). Renewing it is painful; do it once and keep secure.
- Server cert — 1-2 years. Renew before expiry; restart broker.
- Client certs — match device fleet refresh cycle. 2-3 years typical.
Automation:
- Internal CA: small Go service that issues certs based on CSR
- Public CA: cert-manager + Let’s Encrypt for the broker’s public-facing cert
- Hardware-anchored: device’s TPM holds the private key; cert reissued via CSR
Half a day to set up; saves a panic when certs expire.
DoS protection
A misbehaving (or malicious) client can flood the broker. Mosquitto limits:
max_connections 5000
max_queued_messages 1000
max_inflight_messages 20
message_size_limit 1048576 # 1 MB
For aggressive defense, fronting with a TCP-aware proxy (HAProxy, Nginx Stream) that rate-limits per-IP is the next step. The proxy enforces connection-rate limits before they reach the broker.
For broker-internal limits, EMQX has more granular per-client limits (max_topic_alias, max_subscriptions) than Mosquitto. Trade off as needed.
Audit logging
log_dest file /var/log/mosquitto/mosquitto.log
log_type notice
log_type warning
log_type error
connection_messages true
Connection/disconnection events logged. Ship logs to your SIEM (Splunk, ELK, Loki) for retention and querying.
Patterns to alert on:
- Failed auth attempts (brute force)
- Client connecting from unexpected IP
- ACL denial logs (someone trying to access topics they shouldn’t)
- Sudden spike in connection count
For the factory project I’m on, these go to Loki + Grafana with alerts on >10 failed auths/min from one IP.
Network segmentation
Even with all the broker-side security, the network should be segmented:
- Operational technology (OT) network: PLCs, edge gateways
- IT/cloud bridge network: MQTT broker, OPC UA bridges
- Enterprise network: dashboards, business apps
Firewalls between. The MQTT broker is in the bridge segment, reachable from both OT and IT. OT devices can’t directly reach IT.
This is OT 101 but worth restating: even a perfectly-secured MQTT broker can’t protect against a flat network where a compromised laptop reaches the PLC.
Summary checklist
- TLS on every listener (8883 or higher)
- Per-device credentials, randomly generated, in config management
- ACL per device, principle of least privilege
-
allow_anonymous falsealways - Connection logs shipped to SIEM
- Failed auth alerting set up
- Cert rotation plan documented
- Connection / message size limits set
- Network segmented between OT and IT
- Backup of CA private key in secure storage
- Disaster recovery: re-issue creds for all devices in <day
Common Pitfalls
Trusting “the internal network.” Internal networks have unknown devices. Always TLS.
Same creds across devices. One compromise = all compromised.
No ACL. Compromised device pollutes all topics.
Letting expired certs alert at the wrong time. Renew 30 days before expiry, not at expiry.
Storing CA private key in version control. Use proper secrets management.
No DoS protection. A misbehaving client floods the broker. Set limits.
Wrapping Up
Six controls, layered. None bulletproof; together they push attacker cost way up. Monday: August retro closing out the IIoT month.