# 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 ```bash # 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 ```bash # Create certificate directory structure mkdir -p ${cert_dir}/${domain} chmod 755 ${cert_dir} chmod 700 ${cert_dir}/${domain} ``` ### Phase 3: Certificate Issuance ```bash # Issue certificate using DNS validation ~/.acme.sh/acme.sh --issue \ --dns ${dns_provider} \ -d ${domain} \ --keylength 2048 \ --server ${ca} ``` ### Phase 4: Certificate Installation ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ### Option 1: System CA (Recommended for PC/Mobile) No files needed. The operating system's built-in CA trust store will verify the certificate automatically. **Python Example:** ```python 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:** ```javascript const mqtt = require('mqtt'); const client = mqtt.connect('mqtts://${domain}:8883', { username: 'username', password: 'password', rejectUnauthorized: true }); ``` ### Option 2: fullchain.pem (Recommended for Embedded Devices) Distribute `fullchain.pem` to clients that cannot access system CA. **Python Example:** ```python client.tls_set(ca_certs="fullchain.pem", cert_reqs=ssl.CERT_REQUIRED) client.connect("${domain}", 8883, 60) ``` **ESP32/Arduino Example:** ```cpp #include #include 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: ```bash ~/.acme.sh/acme.sh --renew -d ${domain} --force ``` ### Check Certificate Expiry ```bash 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 ## Related Commands - 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`