Pendahuluan
Di artikel Mosquitto pribadi (#16), broker kamu sudah pakai username/password — tapi koneksi masih plain MQTT di port 1883. Di jaringan rumah (LAN) itu sering cukup; begitu broker diakses dari internet (VPS, port forwarding, atau subscriber cloud), password bisa disadap tanpa enkripsi.
Artikel ini melanjutkan Jalur B (infrastruktur & data) Seri 2: aktifkan MQTT over TLS (port 8883), lalu pelajari tiga fitur production MQTT yang sering terlewat — QoS, LWT (Last Will Testament), dan retained messages. Sketch ESP32 diperbarui memakai WiFiClientSecure + sertifikat CA.
Prasyarat: Broker Mosquitto + auth (#16) sudah jalan, paham dasar MQTT publish/subscribe (#7). Familiar NVS (#12) membantu menyimpan host broker.
Yang Kamu Butuhkan
- Server broker yang sama seperti #16 (Raspberry Pi atau VPS Ubuntu/Debian)
- Akses
sudo+ OpenSSL (biasanya sudah terpasang) - ESP32 DevKit + koneksi WiFi ke broker
- Arduino IDE + library PubSubClient (Nick O'Leary)
- Opsional: laptop dengan
mosquitto-clientsuntuk uji TLS
Estimasi biaya: Rp 0 (self-signed CA untuk lab) — sertifikat Let's Encrypt opsional untuk domain publik (dijelaskan di Keamanan).
Plain MQTT vs MQTT + TLS
| Aspek | Port 1883 (plain) | Port 8883 (TLS) |
|---|---|---|
| Enkripsi | Tidak ada | TLS — password & payload terenkripsi |
| Cocok untuk | LAN tepercaya, lab cepat | Internet, VPS, akses remote |
| ESP32 client | WiFiClient | WiFiClientSecure + CA |
| Setup broker | Listener 1883 + auth (#16) | Sertifikat + listener 8883 |
QoS dan LWT sudah diperkenalkan singkat di artikel MQTT (#7) — di sini kita terapkan ke Mosquitto dan firmware ESP32.
Arsitektur: TLS + Fitur Production MQTT
[ ESP32 ]
| WiFiClientSecure + CA root
| MQTT TLS :8883 · user/pass · LWT + retained (opsional)
v
[ Mosquitto #16 + TLS ]
|
+-- Subscriber CLI (mosquitto_sub --cafile)
+-- Home Assistant / ESPHome / Node-RED (#21/#22/#23) — set TLS di broker config
+-- Python subscriber (#18) — paho-mqtt tls_set()
Topic contoh (konsisten Seri 1):
- Data:
kodingindonesia/esp32/dht22/data - Status online:
kodingindonesia/esp32/status— ideal untuk LWT - Relay:
kodingindonesia/esp32/lampu/kontrol— sama #8/#24
Langkah 1: Buat CA & Sertifikat Server (OpenSSL)
Di broker (SSH), buat folder sertifikat:
sudo mkdir -p /etc/mosquitto/certs
cd /etc/mosquitto/certs
1. Certificate Authority (CA) sendiri — untuk lab & LAN. Produksi publik pertimbangkan Let's Encrypt (lihat Keamanan).
sudo openssl genrsa -out ca.key 2048
sudo openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
-subj "/CN=KindoMQTT-CA"
2. Sertifikat server — CN harus cocok dengan host yang dipakai ESP32/sketch (mqttHost). Jika pakai IP, set CN ke IP; jika pakai hostname, set CN ke hostname yang sama:
# Opsi A — ESP32 pakai IP (contoh sketch di bawah)
sudo openssl genrsa -out server.key 2048
sudo openssl req -new -key server.key -out server.csr \
-subj "/CN=192.168.1.50"
sudo openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt -days 825
# Opsional — jika verifikasi TLS gagal di client tertentu, sign ulang dengan SAN IP:
# echo "subjectAltName=IP:192.168.1.50" | sudo tee san.ext
# sudo openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
# -out server.crt -days 825 -extfile san.ext
# Opsi B — ESP32 pakai hostname broker.lan (semua klien harus konsisten)
# sudo openssl req -new -key server.key -out server.csr -subj "/CN=broker.lan"
sudo chown mosquitto:mosquitto /etc/mosquitto/certs/*
sudo chmod 640 /etc/mosquitto/certs/server.key
Pro tip: Simpan
ca.crt— file ini yang di-embed ke ESP32 sebagaisetCACert(). Jangan commitca.keyatauserver.keyke Git. Jika handshake TLS gagal padahal CN sudah benar, coba sign ulang dengan SubjectAltName (subjectAltName=IP:...) seperti contoh komentar di atas.
Langkah 2: Konfigurasi Mosquitto — Listener TLS 8883
Tambahkan file /etc/mosquitto/conf.d/tls.conf (jangan hapus config auth dari #16):
per_listener_settings true
listener 8883
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key
cafile /etc/mosquitto/certs/ca.crt
require_certificate false
allow_anonymous false
password_file /etc/mosquitto/passwd
Dengan per_listener_settings true, listener 1883 dari #16 tetap bisa aktif untuk debugging LAN — sementara 8883 dipakai untuk koneksi TLS (ESP32 production / akses remote).
Restart & cek log:
sudo systemctl restart mosquitto
sudo systemctl status mosquitto
sudo tail -20 /var/log/mosquitto/mosquitto.log
Firewall — buka 8883/tcp (bukan hanya 1883) jika akses dari luar LAN:
sudo ufw allow 8883/tcp
sudo ufw status
Langkah 3: Uji TLS dari Laptop
Ganti host agar sama dengan CN sertifikat (IP atau hostname):
mosquitto_sub -h 192.168.1.50 -p 8883 \
--cafile /path/ke/ca.crt \
-u kindo_esp32 -P 'GANTI_PASSWORD_MQTT' \
-t "kodingindonesia/esp32/dht22/data" -v
mosquitto_pub -h 192.168.1.50 -p 8883 \
--cafile /path/ke/ca.crt \
-u kindo_esp32 -P 'GANTI_PASSWORD_MQTT' \
-t "kodingindonesia/esp32/dht22/data" \
-m '{"suhu":28.5,"kelembaban":62}'
QoS, LWT & Retained — Kapan Pakai?
| Fitur | Fungsi | Contoh IoT |
|---|---|---|
| QoS 0 | Kirim sekali, tanpa ACK | Data suhu tiap 5 detik — hilang 1 paket tidak masalah |
| QoS 1 | Minimal sekali sampai (ACK) | Perintah relay penting, alarm |
| QoS 2 | Tepat sekali sampai (4-way handshake) | Jarang dipakai di ESP32 — berat; CLI/broker tetap mendukung |
| LWT | Pesan otomatis saat client disconnect mendadak | {"online":false} di topic status |
| Retained | Broker simpan pesan terakhir untuk subscriber baru | Status relay terakhir — hati-hati data basi |
CLI contoh QoS 1 + retained:
mosquitto_pub -h 192.168.1.50 -p 8883 --cafile ca.crt \
-u kindo_esp32 -P 'GANTI_PASSWORD_MQTT' \
-q 1 -r \
-t "kodingindonesia/esp32/status" -m '{"online":true}'
Catatan PubSubClient: Library default publish QoS 0. Untuk mayoritas sensor periodik itu cukup (sama rekomendasi #7). QoS 1 di ESP32 butuh buffer lebih besar — prioritaskan TLS + LWT dulu.
Kode ESP32: WiFiClientSecure + LWT
Ganti SSID, host broker, user/pass, dan paste isi ca.crt ke root_ca:
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
const char* ssid = "NamaWiFiKamu";
const char* password = "PasswordWiFiKamu";
const char* mqttHost = "192.168.1.50"; // IP/hostname broker — harus cocok sertifikat
const int mqttPort = 8883;
const char* mqttUser = "kindo_esp32";
const char* mqttPass = "GANTI_PASSWORD_MQTT";
const char* topicData = "kodingindonesia/esp32/dht22/data";
const char* topicStatus = "kodingindonesia/esp32/status";
// Paste isi ca.crt (hanya bagian PEM, termasuk BEGIN/END)
static const char root_ca[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
PASTE_ISI_CA_CRT_DI_SINI
-----END CERTIFICATE-----
)EOF";
const char* lwtPayload = "{\"online\":false}";
WiFiClientSecure espClient;
PubSubClient mqttClient(espClient);
unsigned long lastPublishMs = 0;
char clientId[24];
void buatClientIdUnik() {
snprintf(clientId, sizeof(clientId), "ESP32-TLS-%06llX", ESP.getEfuseMac() & 0xFFFFFF);
}
void publishStatusOnline() {
const char* online = "{\"online\":true}";
mqttClient.publish(topicStatus, online, true); // retained status
}
bool koneksiMQTT() {
espClient.setCACert(root_ca);
mqttClient.setServer(mqttHost, mqttPort);
mqttClient.setBufferSize(512);
buatClientIdUnik();
uint8_t percobaan = 0;
while (!mqttClient.connected() && percobaan < 5) {
Serial.print("MQTT TLS connect...");
if (mqttClient.connect(
clientId,
mqttUser,
mqttPass,
topicStatus, // willTopic
1, // willQoS
true, // willRetain
lwtPayload // willMessage — offline jika disconnect mendadak
)) {
Serial.println(" OK");
publishStatusOnline();
return true;
}
Serial.print(" gagal, rc=");
Serial.println(mqttClient.state());
percobaan++;
delay(5000);
}
return mqttClient.connected();
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi OK");
koneksiMQTT();
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
WiFi.reconnect();
}
if (!mqttClient.connected()) {
koneksiMQTT();
}
mqttClient.loop();
if (millis() - lastPublishMs > 5000) {
lastPublishMs = millis();
StaticJsonDocument<96> doc;
doc["suhu"] = 28.0;
doc["kelembaban"] = 60;
char buffer[96];
serializeJson(doc, buffer);
if (mqttClient.publish(topicData, buffer)) {
Serial.print("Publish ");
Serial.println(buffer);
}
}
}
Penjelasan Bagian Kritis
WiFiClientSecure+setCACert()— ESP32 memverifikasi identitas server. Jangan pakaisetInsecure()di produksi.- Port 8883 — standar MQTT over TLS (bukan 1883).
- LWT — parameter
connect()ke-4 s/d ke-7: broker publish{"online":false}retained jika ESP32 mati/listrik putus tanpa disconnect bersih. - Retained status —
publish(..., true)pada topic status; subscriber baru langsung tahu ESP32 online/offline. - CN sertifikat — hostname di sketch harus cocok dengan CN/SAN sertifikat server, atau verifikasi TLS gagal.
- Client ID unik —
ESP.getEfuseMac()agar beberapa ESP32 tidak saling kick di broker yang sama. - Max 5 percobaan connect —
koneksiMQTT()tidak blockloop()selamanya jika broker down. mqttClient.loop()— wajib diloop()agar TLS session & LWT handshake stabil.
Integrasi Home Assistant, ESPHome & Node-RED
Setelah broker TLS aktif, update konfigurasi MQTT integration:
- Home Assistant (#21) — broker port
8883, centang SSL/TLS, uploadca.crtatau setcertificate: autountuk Let's Encrypt. - ESPHome (#22) — di
mqtt:YAML setport: 8883+ paste isica.crtke fieldcertificate(self-signed) ataucertificate_authority. - Node-RED (#23) — di node mqtt-broker config → port 8883, TLS on, CA file sama seperti laptop.
Topic relay & sensor dari artikel sebelumnya tidak berubah — hanya transport yang dienkripsi.
Uji Coba (Checklist)
- Broker #16 masih jalan + auth OK di port 1883 (LAN)
- Generate CA + server cert → restart Mosquitto tanpa error log
mosquitto_subdengan--cafiledi port 8883 menerima pesan- Upload sketch ESP32 → Serial:
MQTT TLS connect... OK - Cabut USB ESP32 mendadak → subscriber harus terima LWT
{"online":false}di topic status - Colokkan lagi → status retained kembali
{"online":true} - Verifikasi CN sertifikat = mqttHost di sketch (IP atau hostname sama persis)
- Coba publish QoS 1 dari CLI (
-q 1) — bandingkan dengan QoS 0 - Pastikan tidak expose port 1883 ke internet jika sudah pakai 8883
Tips & Troubleshooting
- TLS handshake failed / rc=-2: CN/SAN sertifikat tidak cocok host — regenerate CSR atau sign ulang dengan
subjectAltName=IP:... - Certificate verify failed:
root_cadi sketch bukan CA yang sama dengan yang sign server cert - Connection refused 8883: Listener belum aktif atau firewall block — cek
ss -tlnp | grep 8883 - rc=5 Not authorized: Sama seperti #16 — user/password salah
- Config error setelah tambah TLS: Cek konflik
/etc/mosquitto/conf.d/— sama seperti #16 - LWT tidak muncul: Pastikan disconnect mendadak (bukan
disconnect()bersih); keepalive PubSubClient default 15 detik — tunggu ~1,5× keepalive - Retained data basi: Hapus dengan publish payload kosong + retain:
mosquitto_pub ... -t topic -n -r - ESP32 connect lalu langsung disconnect: Client ID bentrok — pastikan tiap board punya ID unik (lihat
buatClientIdUnik()di sketch) - WiFi 2.4 GHz: ESP32 tidak support jaringan 5 GHz saja
Keamanan & Produksi
- Self-signed CA cocok lab/LAN — untuk domain publik pertimbangkan Let's Encrypt + reverse proxy atau certbot
- Tutup port
1883dari internet setelah TLS aktif — hanya8883atau VPN - Jangan commit
*.keyatau password ke Git — simpan di NVS (#12) - Pertimbangkan ACL Mosquitto agar user ESP32 hanya publish/subscribe topic tertentu
- HTTPS client di ESP32 (bukan MQTT) → lihat artikel pelengkap #38
Langkah Selanjutnya (Seri 2)
- Artikel #18: Subscriber Python +
tls_set()→ simpan data MQTT ke MySQL - Artikel #34: NTP — timestamp akurat sebelum histori database
- Home Assistant (#21) — aktifkan TLS di integration broker
- PIR + lampu MQTT (#24) — upgrade sketch ke TLS untuk deploy remote
- Artikel #19: InfluxDB + Grafana untuk histori sensor
Dengan TLS, QoS, LWT, dan retained, stack MQTT kamu siap naik level dari lab LAN ke deploy yang lebih aman. Lanjutkan Seri 2 di halaman artikel Koding Indonesia.