10 KiB
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
- Verify DNS resolution for the domain
- Check if acme.sh is installed
- Verify DNS API credentials are configured
- Check if EMQX container is running
- 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:
- Certificate details (domain, validity, CA)
- EMQX container status and ports
- MQTTS listener status (port 8883)
- Public key and certificate fingerprints
- Client configuration guide (system CA vs fullchain.pem)
- Backup file location and checksums
- 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:
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
});
Option 2: fullchain.pem (Recommended for Embedded Devices)
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
- Private Key (key.pem): Keep confidential, never share
- Certificate (cert.pem): Safe to distribute to clients
- Full Chain (fullchain.pem): Safe to distribute to clients
- Public Key (public.pem): Safe to distribute for pinning
- Backup Files: Store in encrypted storage
- 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