Skip to content

EMQX 6 Certificate-Based Device Authentication Setup

Overview

This guide provides step-by-step instructions to configure EMQX 6 with certificate-based authentication for your Raspberry Pi fleet. Devices authenticate using X.509 client certificates instead of usernames and passwords, providing:

  • Strong Authentication: Cryptographically secure device identity
  • No Password Management: Eliminates password rotation burden
  • Device-Specific Authorization: ACLs based on certificate identity
  • Mutual TLS (mTLS): Both broker and client verify each other

Prerequisites

  • EMQX 6 installed and running
  • OpenSSL installed on your provisioning machine
  • MQTTX CLI installed for testing
  • Access to this repository's scripts

Step 1: Generate Certificates

1.1 Initialize the CA (Certificate Authority)

Initialize the root Certificate Authority once:

./scripts/provision-mqtt-certs.sh --init

This creates: - config/mqtt-ca/ca.key - CA private key (4096-bit RSA) - config/mqtt-ca/ca.crt - CA public certificate (10-year validity)

Output: You'll see the CA certificate details with fingerprint.

1.2 Generate Device Certificates

Generate certificates for all devices:

./scripts/provision-mqtt-certs.sh --all

Or for a specific device:

./scripts/provision-mqtt-certs.sh rpi-prod-1883c404a12f

This creates for each device: - config/{device}/mqtt/client.key - Device private key - config/{device}/mqtt/client.crt - Device certificate (2-year validity) - config/{device}/mqtt/ca.crt - Copy of CA certificate

Verification: Check certificate details:

openssl x509 -in config/rpi-prod-1883c404a12f/mqtt/client.crt -noout -text | head -20

Expected CN value: CN=rpi-prod-1883c404a12f

Step 2: Configure EMQX Server

2.1 Prepare Server Certificates

You need server-side certificates for the broker. Generate or obtain:

# Generate self-signed server certificate (for testing)
# Or use a valid certificate from Let's Encrypt/Cloudflare

Place on EMQX broker: - /etc/emqx/certs/server.crt - Server certificate - /etc/emqx/certs/server.key - Server private key - /etc/emqx/certs/ca.crt - CA certificate (for validating client certs)

2.2 EMQX Configuration (emqx.conf)

Add or modify the TLS listener configuration:

# ==============================================================================
# Listener - TLS/SSL (MQTT over TLS)
# ==============================================================================

listeners.ssl.default {
  # Binding address and port
  bind = "0.0.0.0:8883"
  acceptors = 32

  # ============================================================================
  # Server Certificates
  # ============================================================================
  certfile = "/etc/emqx/certs/server.crt"
  keyfile = "/etc/emqx/certs/server.key"

  # ============================================================================
  # Client Certificate Validation (for mTLS)
  # ============================================================================
  # CA certificate for validating client certificates
  cacertfile = "/etc/emqx/certs/ca.crt"

  # Require clients to present valid certificate
  # Options:
  #   - verify_none   : Don't request client certificate
  #   - verify_peer   : Request but don't require
  #   - verify_fail_if_no_peer_cert : Require certificate
  verify = verify_peer
  fail_if_no_peer_cert = true

  # ============================================================================
  # TLS Version and Security
  # ============================================================================
  # Only allow TLS 1.2+
  tls_versions = ["tlsv1.2", "tlsv1.3"]

  # Strong cipher suites (modern browsers/clients)
  ciphers = "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305"

  # Prefer server's cipher order
  honor_cipher_order = "on"

  # ============================================================================
  # Optional: Certificate Revocation
  # ============================================================================
  # Enable if you need to revoke compromised device certificates
  crl_check = false
  # crl_check_all = false
  # crl_files = ["/etc/emqx/certs/ca.crl.pem"]
}

2.3 Certificate Authentication

Configure EMQX to extract device identity from certificate CN:

# ==============================================================================
# Authentication
# ==============================================================================

authentication = [
  {
    enable = true
    type = cert

    # Extract device ID from certificate Common Name (CN)
    # The CN will be used as the MQTT username
    # Example: Certificate with CN=rpi-prod-1883c404a12f → username=rpi-prod-1883c404a12f
    user_id_from = "cn"
  }
]

Configure per-device topic access using ACLs:

# ==============================================================================
# Authorization (ACL)
# ==============================================================================

authorization {
  enable = true
  type = built_in_database
  cache {
    enable = true
  }
}

# Device-specific topic permissions using ${username} placeholder
acl = [
  # Allow each device to publish telemetry
  {
    action = "allow"
    subject {type = "username", value = "${username}"}
    object {type = "topic", value = "devices/${username}/telemetry"}
    access = "publish"
  },

  # Allow each device to subscribe to commands
  {
    action = "allow"
    subject {type = "username", value = "${username}"}
    object {type = "topic", value = "devices/${username}/command"}
    access = "subscribe"
  },

  # Allow device to check its own connection status
  {
    action = "allow"
    subject {type = "username", value = "${username}"}
    object {type = "topic", value = "$SYS/brokers/+/clients/${username}/connected"}
    access = "subscribe"
  },

  # Deny everything else by default
  {
    action = "deny"
    subject {type = "all"}
    object {type = "topic", value = "#"}
    access = "all"
  }
]

2.5 Apply Configuration

After updating emqx.conf:

# For Docker Compose
docker-compose restart emqx

# For standalone EMQX
emqx ctl conf reload

# Or restart EMQX
docker restart emqx

Step 3: Verify Setup

3.1 Check EMQX Configuration

Connect to EMQX Dashboard (http://localhost:18083 by default): 1. Login with default credentials (admin/public) 2. Go to Listeners → verify SSL listener on port 8883 3. Go to Authentication → verify Certificate authenticator enabled 4. Go to Authorization → verify ACL rules loaded

3.2 Test TLS Connection

Use OpenSSL to verify the TLS connection works:

openssl s_client -connect mqtts.tagai.xyz:8883 \
  -cert config/rpi-prod-1883c404a12f/mqtt/client.crt \
  -key config/rpi-prod-1883c404a12f/mqtt/client.key \
  -CAfile config/mqtt-ca/ca.crt \
  -showcerts

Expected output:

...
Verify return code: 0 (ok)

3.3 Test MQTT Connection with MQTTX CLI

See Step 4 below.

Step 4: Test with MQTTX CLI

4.1 Install MQTTX CLI

# macOS (Homebrew)
brew install mqtt-x

# Or download from: https://mqttx.app/downloads

4.2 Test Connection

Connect to the broker:

mqttx conn \
  -h mqtts.tagai.xyz \
  -p 8883 \
  -l mqtts \
  --cert config/rpi-prod-1883c404a12f/mqtt/client.crt \
  --key config/rpi-prod-1883c404a12f/mqtt/client.key \
  --ca config/mqtt-ca/ca.crt \
  -i rpi-prod-1883c404a12f

Expected output:

[10:45:12] » [CONNECT] Connecting to mqtts.tagai.xyz:8883...
[10:45:12] « [CONNECT] Connection successful (83ms)
[10:45:12] « [CONNACK] Code: 0 Success

4.3 Publish Test Message

In another terminal:

mqttx pub \
  -h mqtts.tagai.xyz \
  -p 8883 \
  -l mqtts \
  -t "devices/rpi-prod-1883c404a12f/telemetry" \
  -m '{"temperature": 25.5, "humidity": 65}' \
  --cert config/rpi-prod-1883c404a12f/mqtt/client.crt \
  --key config/rpi-prod-1883c404a12f/mqtt/client.key \
  --ca config/mqtt-ca/ca.crt \
  -i rpi-prod-1883c404a12f

Expected output:

[10:45:15] » [PUBLISH] devices/rpi-prod-1883c404a12f/telemetry
[10:45:15] « [PUBACK] Packet ID: 1

4.4 Subscribe to Topics

mqttx sub \
  -h mqtts.tagai.xyz \
  -p 8883 \
  -l mqtts \
  -t "devices/rpi-prod-1883c404a12f/#" \
  --cert config/rpi-prod-1883c404a12f/mqtt/client.crt \
  --key config/rpi-prod-1883c404a12f/mqtt/client.key \
  --ca config/mqtt-ca/ca.crt \
  -i rpi-prod-1883c404a12f

Step 5: Deploy to Production

5.1 Distribute Device Certificates

Copy device certificates to each Raspberry Pi:

./scripts/sync-device-configs.sh

# Or manually:
scp config/rpi-prod-1883c404a12f/mqtt/* pi@rpi-prod-1883c404a12f:/etc/mqtt/

5.2 Configure Node-RED

Update Node-RED MQTT client to use certificates. See nodered-mqtt-client.md for detailed instructions.

Example Node-RED configuration:

{
  "id": "mqtt-client",
  "type": "mqtt-broker",
  "name": "EMQX Broker",
  "broker": "mqtts.tagai.xyz",
  "port": 8883,
  "tls": {
    "ca": "/etc/mqtt/ca.crt",
    "cert": "/etc/mqtt/client.crt",
    "key": "/etc/mqtt/client.key"
  },
  "clientid": "rpi-prod-1883c404a12f"
}

5.3 Configure MQTT Client on Device

For any MQTT client (Node-RED, mosquitto, etc.):

# Example with mosquitto_pub
mosquitto_pub -h mqtts.tagai.xyz -p 8883 \
  -t "devices/rpi-prod-1883c404a12f/telemetry" \
  -m "test" \
  --cert /etc/mqtt/client.crt \
  --key /etc/mqtt/client.key \
  --cafile /etc/mqtt/ca.crt

Troubleshooting

Connection Refused

Problem: Cannot connect to broker

connect ECONNREFUSED mqtts.tagai.xyz:8883

Solutions: 1. Verify EMQX is running: docker ps | grep emqx 2. Check firewall: sudo ufw status (port 8883 should be open) 3. Verify listener is enabled in EMQX config: listeners.ssl.default

Certificate Verification Failed

Problem:

Error: certificate verify failed

Solutions: 1. Check certificate CN matches device ID:

openssl x509 -in config/rpi-prod-1883c404a12f/mqtt/client.crt -noout -subject
Expected: subject=CN=rpi-prod-1883c404a12f

  1. Verify CA certificate path is correct
  2. Regenerate certificate if CN is wrong:
    rm -rf config/rpi-prod-1883c404a12f/mqtt
    ./scripts/provision-mqtt-certs.sh rpi-prod-1883c404a12f
    

Authentication Failed

Problem:

[DISCONNECT] Code: 135 (NOT_AUTHORIZED)

Solutions: 1. Check EMQX logs: docker logs emqx 2. Verify authenticator is enabled: check authentication.type = cert 3. Verify device username (from CN) exists in EMQX 4. Check ACL rules are correct

Cannot Access Topic

Problem:

[PUBLISH FAILED] Error: User not authorized to access topic

Solutions: 1. Verify topic matches ACL rules 2. Check username in certificate CN 3. Review ACL configuration in EMQX 4. Device should only publish to: devices/{device-id}/telemetry 5. Device should only subscribe to: devices/{device-id}/command

Certificate Expired

Problem:

Error: certificate has expired

Solutions: 1. Check certificate validity:

openssl x509 -in config/rpi-prod-1883c404a12f/mqtt/client.crt -noout -dates

  1. Regenerate certificate:

    ./scripts/provision-mqtt-certs.sh rpi-prod-1883c404a12f
    

  2. Deploy to device:

    scp config/rpi-prod-1883c404a12f/mqtt/* pi@rpi-prod-1883c404a12f:/etc/mqtt/
    

Certificate Lifecycle

Phase Timeline Action
Initial Generation 2026-01 Generate 2-year certs for all devices
Valid Period 2026-2028 No renewal needed
Pre-Expiry Prep ~Jul 2027 Plan for reissuance
Reissuance ~Dec 2027 Generate new 2-year batch
Deployment ~Jan 2028 Sync new certs to devices
Expiry 2028-01 Old certificates expire

Security Best Practices

Do: - Keep private keys secure (600 permissions, private directories) - Use strong cipher suites (TLS 1.2+) - Enable mutual TLS (verify_peer + fail_if_no_peer_cert) - Monitor certificate expiration dates - Use separate certificates for each device - Enable ACL to restrict per-device topic access

Don't: - Share private keys between devices - Use self-signed certificates without validation - Disable certificate verification (verify = verify_none) - Commit certificate private keys to git - Use weak ciphers or old TLS versions

Quick Reference

Generate Certificates

./scripts/provision-mqtt-certs.sh --init      # Initialize CA
./scripts/provision-mqtt-certs.sh --all       # Generate all devices
./scripts/provision-mqtt-certs.sh rpi-prod-1883c404a12f     # Single device

Test Connection

# TLS handshake test
openssl s_client -connect mqtts.tagai.xyz:8883 \
  -cert config/rpi-prod-1883c404a12f/mqtt/client.crt \
  -key config/rpi-prod-1883c404a12f/mqtt/client.key \
  -CAfile config/mqtt-ca/ca.crt

# MQTT connection test
mqttx conn -h mqtts.tagai.xyz -p 8883 -l mqtts \
  --cert config/rpi-prod-1883c404a12f/mqtt/client.crt \
  --key config/rpi-prod-1883c404a12f/mqtt/client.key \
  --ca config/mqtt-ca/ca.crt -i rpi-prod-1883c404a12f

View Certificate Details

openssl x509 -in config/rpi-prod-1883c404a12f/mqtt/client.crt -noout -text

Check Certificate Validity

openssl x509 -in config/rpi-prod-1883c404a12f/mqtt/client.crt -noout -dates

Support

For issues or questions: 1. Check Troubleshooting section 2. Review EMQX logs: docker logs emqx 3. Consult EMQX documentation: https://docs.emqx.io/ 4. Check MQTTX CLI documentation: https://mqttx.app/docs