Files
opencode/skill/mqtts-developer/setup-mqtts-acme.md

10 KiB

Setup MQTTS with Auto-Renewal Certificate

Description

Complete workflow to configure MQTTS (MQTT over TLS) for EMQX using acme.sh with automatic certificate renewal. Supports DNS-based certificate validation (Alibaba Cloud DNS API).

Parameters

  • domain: MQTT domain name (e.g., mq.example.com)
  • ca: Certificate Authority (default: zerossl, options: letsencrypt, zerossl)
  • dns_provider: DNS provider (default: dns_ali, supports acme.sh DNS APIs)
  • emqx_container: EMQX Docker container name (default: emqx)
  • cert_dir: Certificate storage directory (default: /root/certs)

Prerequisites Check

  1. Verify DNS resolution for the domain
  2. Check if acme.sh is installed
  3. Verify DNS API credentials are configured
  4. Check if EMQX container is running
  5. Verify current container configuration

Execution Steps

Phase 1: DNS and Environment Verification

# Check DNS resolution
dig ${domain} +short

# Get server public IP
curl -s ifconfig.me

# Verify acme.sh installation
~/.acme.sh/acme.sh --version

# Check DNS API configuration
cat ~/.acme.sh/account.conf | grep -E "Ali_Key|Ali_Secret"

# Check EMQX container status
docker ps | grep ${emqx_container}

Phase 2: Certificate Directory Setup

# Create certificate directory structure
mkdir -p ${cert_dir}/${domain}
chmod 755 ${cert_dir}
chmod 700 ${cert_dir}/${domain}

Phase 3: Certificate Issuance

# Issue certificate using DNS validation
~/.acme.sh/acme.sh --issue \
  --dns ${dns_provider} \
  -d ${domain} \
  --keylength 2048 \
  --server ${ca}

Phase 4: Certificate Installation

# Install certificate with auto-reload
~/.acme.sh/acme.sh --install-cert \
  -d ${domain} \
  --cert-file ${cert_dir}/${domain}/cert.pem \
  --key-file ${cert_dir}/${domain}/key.pem \
  --fullchain-file ${cert_dir}/${domain}/fullchain.pem \
  --reloadcmd "docker restart ${emqx_container}"

# Set proper permissions
chmod 755 ${cert_dir}/${domain}
chmod 644 ${cert_dir}/${domain}/cert.pem
chmod 644 ${cert_dir}/${domain}/key.pem
chmod 644 ${cert_dir}/${domain}/fullchain.pem

# Create CA cert symlink
ln -sf ${cert_dir}/${domain}/fullchain.pem ${cert_dir}/${domain}/cacert.pem

Phase 5: Extract Public Key

# Extract public key from private key
openssl rsa -in ${cert_dir}/${domain}/key.pem -pubout -out ${cert_dir}/${domain}/public.pem 2>/dev/null
chmod 644 ${cert_dir}/${domain}/public.pem

Phase 6: EMQX Container Reconfiguration

# Backup current container configuration
docker inspect ${emqx_container} > /root/emqx-backup-$(date +%Y%m%d-%H%M%S).json

# Get current container ports and volumes
PORTS=$(docker inspect ${emqx_container} --format '{{range $p, $conf := .NetworkSettings.Ports}}{{if $conf}}-p {{(index $conf 0).HostPort}}:{{$p}} {{end}}{{end}}')

# Stop and remove current container
docker stop ${emqx_container}
docker rm ${emqx_container}

# Recreate container with certificate mount
docker run -d \
  --name ${emqx_container} \
  --restart unless-stopped \
  -p 1883:1883 \
  -p 8083:8083 \
  -p 8084:8084 \
  -p 8883:8883 \
  -p 18083:18083 \
  -v /root/emqx/data:/opt/emqx/data \
  -v /root/emqx/log:/opt/emqx/log \
  -v ${cert_dir}/${domain}:/opt/emqx/etc/certs:ro \
  emqx/emqx:5.8.8

Phase 7: Verification

# Wait for container to start
sleep 8

# Check container status
docker ps | grep ${emqx_container}

# Verify certificate files in container
docker exec ${emqx_container} ls -l /opt/emqx/etc/certs/

# Check EMQX listeners
docker exec ${emqx_container} emqx ctl listeners

# Test SSL connection
timeout 10 openssl s_client -connect ${domain}:8883 -servername ${domain} -showcerts 2>/dev/null | openssl x509 -noout -text | grep -E "Subject:|Issuer:|Not Before|Not After|DNS:"

# Verify SSL handshake
timeout 10 openssl s_client -connect ${domain}:8883 -servername ${domain} 2>&1 <<< "Q" | grep -E "Verify return code:|SSL handshake|Protocol|Cipher"

Phase 8: Generate Documentation

# Extract public key fingerprint
PUB_FP_MD5=$(openssl rsa -in ${cert_dir}/${domain}/key.pem -pubout 2>/dev/null | openssl md5 | awk '{print $2}')
PUB_FP_SHA256=$(openssl rsa -in ${cert_dir}/${domain}/key.pem -pubout 2>/dev/null | openssl sha256 | awk '{print $2}')

# Extract certificate fingerprints
CERT_FP_MD5=$(openssl x509 -in ${cert_dir}/${domain}/cert.pem -noout -fingerprint -md5 | cut -d= -f2)
CERT_FP_SHA1=$(openssl x509 -in ${cert_dir}/${domain}/cert.pem -noout -fingerprint -sha1 | cut -d= -f2)
CERT_FP_SHA256=$(openssl x509 -in ${cert_dir}/${domain}/cert.pem -noout -fingerprint -sha256 | cut -d= -f2)

# Generate fingerprints file
cat > ${cert_dir}/${domain}/fingerprints.txt << EOF
Certificate and Key Fingerprints
Generated: $(date)

=== Private Key Fingerprint ===
MD5: ${PUB_FP_MD5}
SHA256: ${PUB_FP_SHA256}

=== Certificate Fingerprint ===
MD5: ${CERT_FP_MD5}
SHA1: ${CERT_FP_SHA1}
SHA256: ${CERT_FP_SHA256}

=== Certificate Details ===
$(openssl x509 -in ${cert_dir}/${domain}/cert.pem -noout -subject -issuer -dates)
EOF

# Generate README
cat > ${cert_dir}/${domain}/README.txt << 'EOF'
EMQX MQTTS Certificate Files

Domain: ${domain}
Created: $(date +%Y-%m-%d)
CA: ${ca}
Key Length: RSA 2048
Auto-Renewal: Enabled

Files:
- cert.pem: Server certificate
- key.pem: Private key (CONFIDENTIAL)
- public.pem: Public key
- fullchain.pem: Full certificate chain
- cacert.pem: CA certificate (symlink)
- fingerprints.txt: Certificate fingerprints
- README.txt: This file

Client Configuration:
- PC/Mobile: Use system CA (no files needed)
- Embedded: Use fullchain.pem
- Connection: mqtts://${domain}:8883

Security:
⚠️  Keep key.pem secure and confidential
✓  cert.pem and fullchain.pem are safe to distribute
EOF

Phase 9: Create Backup Package

# Create compressed backup
cd ${cert_dir}
tar czf ${domain}-complete-$(date +%Y%m%d-%H%M%S).tar.gz ${domain}/

# Generate checksums
md5sum ${domain}-complete-*.tar.gz | tail -1 > ${domain}-checksums.txt
sha256sum ${domain}-complete-*.tar.gz | tail -1 >> ${domain}-checksums.txt

Phase 10: Verify Auto-Renewal

# Check certificate renewal configuration
~/.acme.sh/acme.sh --info -d ${domain}

# List all managed certificates
~/.acme.sh/acme.sh --list

# Verify cron job
crontab -l | grep acme

Post-Installation Summary

Display the following information:

  1. Certificate details (domain, validity, CA)
  2. EMQX container status and ports
  3. MQTTS listener status (port 8883)
  4. Public key and certificate fingerprints
  5. Client configuration guide (system CA vs fullchain.pem)
  6. Backup file location and checksums
  7. Auto-renewal schedule

Client Configuration Guide

No files needed. The operating system's built-in CA trust store will verify the certificate automatically.

Python Example:

import paho.mqtt.client as mqtt
import ssl

client = mqtt.Client()
client.username_pw_set("username", "password")
client.tls_set(cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2)
client.connect("${domain}", 8883, 60)

Node.js Example:

const mqtt = require('mqtt');
const client = mqtt.connect('mqtts://${domain}:8883', {
  username: 'username',
  password: 'password',
  rejectUnauthorized: true
});

Distribute fullchain.pem to clients that cannot access system CA.

Python Example:

client.tls_set(ca_certs="fullchain.pem", cert_reqs=ssl.CERT_REQUIRED)
client.connect("${domain}", 8883, 60)

ESP32/Arduino Example:

#include <WiFiClientSecure.h>
#include <PubSubClient.h>

const char* root_ca = "..."; // Content from fullchain.pem

WiFiClientSecure espClient;
PubSubClient client(espClient);

void setup() {
  espClient.setCACert(root_ca);
  client.setServer("${domain}", 8883);
  client.connect("ESP32", "username", "password");
}

Troubleshooting

DNS Resolution Issues

  • Verify A record points to server IP
  • Wait 5-10 minutes for DNS propagation
  • Test with: dig ${domain} +short

Certificate Validation Failed

  • Check system time is synchronized
  • Update system CA certificates
  • Use fullchain.pem instead of system CA

Container Start Failed

  • Check certificate file permissions
  • Verify volume mount paths
  • Review container logs: docker logs ${emqx_container}

SSL Connection Refused

  • Verify port 8883 is open in firewall
  • Check EMQX listener status
  • Test with: openssl s_client -connect ${domain}:8883

Maintenance

Certificate Renewal

Auto-renewal is configured via cron (daily at 00:34). Manual renewal:

~/.acme.sh/acme.sh --renew -d ${domain} --force

Check Certificate Expiry

openssl x509 -in ${cert_dir}/${domain}/cert.pem -noout -dates

Update Client Certificates

When certificate is renewed, container automatically restarts. No client updates needed unless using public key pinning.

Security Notes

  1. Private Key (key.pem): Keep confidential, never share
  2. Certificate (cert.pem): Safe to distribute to clients
  3. Full Chain (fullchain.pem): Safe to distribute to clients
  4. Public Key (public.pem): Safe to distribute for pinning
  5. Backup Files: Store in encrypted storage
  6. Auto-Renewal: Enabled, checks daily, renews 60 days before expiry

Output Files

  • ${cert_dir}/${domain}/cert.pem: Server certificate
  • ${cert_dir}/${domain}/key.pem: Private key
  • ${cert_dir}/${domain}/public.pem: Public key
  • ${cert_dir}/${domain}/fullchain.pem: Full certificate chain
  • ${cert_dir}/${domain}/cacert.pem: CA certificate (symlink)
  • ${cert_dir}/${domain}/fingerprints.txt: Fingerprints
  • ${cert_dir}/${domain}/README.txt: Documentation
  • ${cert_dir}/${domain}-complete-*.tar.gz: Backup package
  • ${cert_dir}/${domain}-checksums.txt: Package checksums
  • /root/emqx-backup-*.json: Container configuration backup

Success Criteria

  • DNS resolves correctly
  • Certificate issued successfully
  • EMQX container running with certificate mount
  • SSL listener on port 8883 active
  • SSL handshake succeeds
  • Auto-renewal configured
  • Documentation generated
  • Backup package created
  • Check certificate info: openssl x509 -in cert.pem -text -noout
  • Test MQTTS connection: mosquitto_pub -h ${domain} -p 8883 --cafile fullchain.pem -t test -m hello
  • View EMQX logs: docker logs -f ${emqx_container}
  • List certificates: ~/.acme.sh/acme.sh --list
  • Force renewal: ~/.acme.sh/acme.sh --renew -d ${domain} --force