EMQX v6 TLS Configuration for mTLS Client Authentication¶
Overview¶
This document describes how to configure EMQX v6 for mutual TLS (mTLS) authentication with Node-RED clients. Devices authenticate using X.509 client certificates instead of passwords, providing strong, passwordless security at scale.
EMQX Configuration¶
1. TLS Listener Setup¶
Add the following to your EMQX configuration (typically in emqx.conf or via Helm values):
listeners.ssl.default {
bind = "0.0.0.0:8883"
acceptors = 32
# Server certificate and private key
certfile = "/etc/emqx/certs/server.crt"
keyfile = "/etc/emqx/certs/server.key"
# CA certificate for validating client certs
cacertfile = "/etc/emqx/certs/ca.crt"
# Require client certificate
verify = verify_peer
fail_if_no_peer_cert = true
# TLS version and cipher configuration
tls_versions = ["tlsv1.2", "tlsv1.3"]
ciphers = "ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256"
# Optional: CRL checking for revocation
crl_check = false
# crl_check_all = false
# Optional: OCSP stapling (if supported)
# ocsp_status_request = true
}
2. Certificate-Based Authentication¶
Enable the certificate authenticator to extract device identity from the client certificate:
authentication = [
{
enable = true
type = cert
# Extract identity from certificate's Common Name (CN)
# Common options: cn, dn, san
# Format: ${cn} extracts the CN field
user_id_from = "cn"
}
]
Important: The certificate CN (Common Name) will be used as the MQTT username/clientid. Ensure device certificates have CN set to the device ID (e.g., CN=rpi-prod-1883c404a12f).
3. Authorization and ACLs¶
Configure ACL rules to restrict device access by client identity:
authorization {
enable = true
type = built_in_database
}
# Define per-device ACL rules
# Example for rpi-prod-1883c404a12f:
# User: rpi-prod-1883c404a12f (extracted from certificate CN)
# - Publish: devices/rpi-prod-1883c404a12f/telemetry
# - Subscribe: devices/rpi-prod-1883c404a12f/command
# - Subscribe: $SYS/brokers/+/clients/rpi-prod-1883c404a12f/connected
acl = [
{
action = "allow"
subject {type = "username", value = "${username}"}
object {type = "topic", value = "devices/${username}/telemetry"}
access = "publish"
},
{
action = "allow"
subject {type = "username", value = "${username}"}
object {type = "topic", value = "devices/${username}/command"}
access = "subscribe"
},
{
action = "allow"
subject {type = "username", value = "${username}"}
object {type = "topic", value = "$SYS/brokers/+/clients/${username}/connected"}
access = "subscribe"
},
{
action = "deny"
subject {type = "all"}
object {type = "topic", value = "#"}
access = "all"
}
]
4. Certificate Installation on EMQX Broker¶
Place the following files in the EMQX container or volumes:
- Server Certificate:
/etc/emqx/certs/server.crt(public cert for the broker) - Server Private Key:
/etc/emqx/certs/server.key(broker's private key) - CA Certificate:
/etc/emqx/certs/ca.crt(CA bundle for validating client certs)
5. Certificate Validity and Rotation¶
Initial Setup¶
- Device Certs: Valid for 2 years (730 days)
- Server Cert: Valid for 1+ year; rotate annually
- CA Cert: Valid for 10 years; rarely rotated
Planned Reissuance (Year 2, 2028)¶
- Before expiry: Issue new 2-year certificates for all 100 devices
- Update device certificates via provisioning sync
- Restart Node-RED MQTT connections
- No server-side changes needed if same CA is used
6. Revocation (Optional CRL)¶
To revoke a compromised device certificate:
-
Revoke the certificate on your CA:
openssl ca -revoke /path/to/device.crt -keyfile ca.key -cert ca.crt -
Generate an updated CRL:
openssl ca -gencrl -keyfile ca.key -cert ca.crt -out ca.crl.pem -
Publish the CRL to a location accessible to EMQX (e.g., HTTP server or mounted volume)
-
Update EMQX config to enable CRL checking:
listeners.ssl.default { crl_check = true crl_files = ["/etc/emqx/certs/ca.crl.pem"] } -
Reload EMQX or restart the listener for changes to take effect
Node-RED Client Configuration¶
TLS Configuration Node¶
Node-RED uses the mqtt-broker node to connect to EMQX. Configure it with:
- Broker:
mqtts.tagai.xyz - Port:
8883 - TLS: Enabled
- Server Certificate Verification: Enabled
- Client Certificate:
/data/mqtt-certs/client.crt - Client Private Key:
/data/mqtt-certs/client.key - CA Certificate:
/data/mqtt-certs/ca.crt - Client ID: Device ID (e.g.,
rpi-prod-1883c404a12f); must match certificate CN - Protocol Version: MQTT v5
- Clean Session: false (retain session state)
- Keep Alive: 60 seconds
- Last Will: Publish
offlinetodevices/<device-id>/status - Birth Message: Publish
onlinetodevices/<device-id>/status
MQTT Nodes¶
Subscribe to Commands:
- Topic: devices/${DEVICE_ID}/command
- QoS: 1
Publish Telemetry:
- Topic: devices/${DEVICE_ID}/telemetry
- QoS: 1
- Retain: false
Monitoring and Debugging¶
Check TLS Listener Status¶
emqx_ctl listeners
View Connection Details¶
emqx_ctl clients list
emqx_ctl clients info <clientid>
Test Client Certificate¶
mosquitto_sub -h mqtts.tagai.xyz -p 8883 \
-t "devices/rpi-prod-1883c404a12f/#" \
--cert config/rpi-prod-1883c404a12f/mqtt/client.crt \
--key config/rpi-prod-1883c404a12f/mqtt/client.key \
--cafile config/rpi-prod-1883c404a12f/mqtt/ca.crt
Check Certificate Expiration¶
On device:
openssl x509 -in /data/mqtt-certs/client.crt -noout -enddate
On broker:
openssl x509 -in /etc/emqx/certs/server.crt -noout -enddate
Troubleshooting¶
Connection Refused (TLS Handshake Failed)¶
- Verify client cert is signed by CA specified in
cacertfile - Check
verify_peerandfail_if_no_peer_certsettings - Ensure TLS version matches (tlsv1.2 or tlsv1.3)
Authentication Failure¶
- Verify certificate CN matches the expected device ID
- Check authentication configuration; ensure
user_id_from = "cn"is set - Review EMQX logs:
docker logs <container> | grep -i auth
Certificate Expired¶
- Verify cert validity:
openssl x509 -in client.crt -noout -dates - Regenerate using provisioning script and sync to devices
- Restart Node-RED to reload certs
ACL Denial¶
- Verify device username (from cert CN) matches ACL rules
- Check logs:
emqx_ctl acl listor review authorization logs - Ensure topic patterns use
${username}placeholder correctly