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-clients untuk 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

AspekPort 1883 (plain)Port 8883 (TLS)
EnkripsiTidak adaTLS — password & payload terenkripsi
Cocok untukLAN tepercaya, lab cepatInternet, VPS, akses remote
ESP32 clientWiFiClientWiFiClientSecure + CA
Setup brokerListener 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 serverCN 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 sebagai setCACert(). Jangan commit ca.key atau server.key ke 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?

FiturFungsiContoh IoT
QoS 0Kirim sekali, tanpa ACKData suhu tiap 5 detik — hilang 1 paket tidak masalah
QoS 1Minimal sekali sampai (ACK)Perintah relay penting, alarm
QoS 2Tepat sekali sampai (4-way handshake)Jarang dipakai di ESP32 — berat; CLI/broker tetap mendukung
LWTPesan otomatis saat client disconnect mendadak{"online":false} di topic status
RetainedBroker simpan pesan terakhir untuk subscriber baruStatus 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

  1. WiFiClientSecure + setCACert() — ESP32 memverifikasi identitas server. Jangan pakai setInsecure() di produksi.
  2. Port 8883 — standar MQTT over TLS (bukan 1883).
  3. LWT — parameter connect() ke-4 s/d ke-7: broker publish {"online":false} retained jika ESP32 mati/listrik putus tanpa disconnect bersih.
  4. Retained statuspublish(..., true) pada topic status; subscriber baru langsung tahu ESP32 online/offline.
  5. CN sertifikat — hostname di sketch harus cocok dengan CN/SAN sertifikat server, atau verifikasi TLS gagal.
  6. Client ID unikESP.getEfuseMac() agar beberapa ESP32 tidak saling kick di broker yang sama.
  7. Max 5 percobaan connectkoneksiMQTT() tidak block loop() selamanya jika broker down.
  8. mqttClient.loop() — wajib di loop() 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, upload ca.crt atau set certificate: auto untuk Let's Encrypt.
  • ESPHome (#22) — di mqtt: YAML set port: 8883 + paste isi ca.crt ke field certificate (self-signed) atau certificate_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)

  1. Broker #16 masih jalan + auth OK di port 1883 (LAN)
  2. Generate CA + server cert → restart Mosquitto tanpa error log
  3. mosquitto_sub dengan --cafile di port 8883 menerima pesan
  4. Upload sketch ESP32 → Serial: MQTT TLS connect... OK
  5. Cabut USB ESP32 mendadak → subscriber harus terima LWT {"online":false} di topic status
  6. Colokkan lagi → status retained kembali {"online":true}
  7. Verifikasi CN sertifikat = mqttHost di sketch (IP atau hostname sama persis)
  8. Coba publish QoS 1 dari CLI (-q 1) — bandingkan dengan QoS 0
  9. 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_ca di 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 1883 dari internet setelah TLS aktif — hanya 8883 atau VPN
  • Jangan commit *.key atau 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.