ESP32 yang memindahkan koneksi MQTT dari port 1883 ke 8883 tidak sekadar berpindah port. Stream TCP sekarang diharapkan membawa MQTT di dalam TLS. Data dalam perjalanan terlindungi hanya jika client TLS juga memvalidasi sertifikat broker. Autentikasi username/password MQTT merupakan mekanisme terpisah: kredensial mengidentifikasi client kepada broker, sedangkan validasi sertifikat mengidentifikasi broker kepada ESP32.

Menyebut konfigurasi ini “MQTT dengan HTTPS” mencampur dua protokol aplikasi. MQTT tidak berubah menjadi HTTP ketika TLS ditambahkan. MQTT dapat berjalan melalui TCP biasa atau melalui koneksi TCP yang dilindungi TLS.

TLS berada di bawah MQTT

Koneksi PubSubClient biasa memakai stack berikut:

PubSubClient
    |
paket MQTT
    |
WiFiClient
    |
TCP

Untuk MQTT over TLS pada ESP32, WiFiClientSecure menggantikan transport biasa:

PubSubClient
    |
paket MQTT
    |
WiFiClientSecure
    |
TLS
    |
TCP

PubSubClient tetap melakukan serialisasi control packet MQTT seperti CONNECT, SUBSCRIBE, dan PUBLISH. WiFiClientSecure menangani sesi TLS di bawah paket-paket tersebut.

Public test broker EMQX mendokumentasikan port 1883 untuk MQTT biasa dan 8883 untuk MQTT over TLS. Broker privat dan managed dapat menggunakan port listener berbeda, sehingga firmware harus mengikuti konfigurasi broker yang benar-benar dipakai.

Username dan password berada di MQTT CONNECT

PubSubClient menyediakan overload koneksi yang menerima kredensial:

client.connect(mqtt_client_id, mqtt_user, mqtt_pass);

Nilai tersebut dibawa oleh pertukaran koneksi MQTT. Username dan password bukan parameter TLS handshake.

TLS handshake
    |
    +-- ESP32 memverifikasi sertifikat broker
    |
koneksi terenkripsi
    |
MQTT CONNECT
    |
    +-- client ID
    +-- username
    +-- password
    |
autentikasi / otorisasi broker

Broker harus memiliki mekanisme autentikasi yang dikonfigurasi agar kredensial tersebut mempunyai arti. Public test broker dapat mengizinkan client anonim, sedangkan deployment EMQX privat dapat mewajibkan username/password, mutual TLS, token, atau mekanisme lain yang dikonfigurasi. Karena itu, kredensial berasal dari konfigurasi broker, bukan dari nilai contoh yang diambil dari deployment lain.

setInsecure() menghilangkan verifikasi sertifikat broker

Konfigurasi pengujian ESP32 yang umum adalah:

WiFiClientSecure espClient;

void setup() {
    espClient.setInsecure();
}

Kode tersebut dapat membentuk sesi TLS terenkripsi, tetapi setInsecure() menonaktifkan verifikasi sertifikat. ESP32 tidak lagi membuktikan bahwa sertifikat yang diberikan peer berasal dari broker tepercaya.

TLS + setInsecure()
    -> channel terenkripsi
    -> identitas broker tidak diverifikasi

TLS + CA tepercaya
    -> channel terenkripsi
    -> certificate chain diverifikasi
    -> identitas broker dapat diperiksa melalui sertifikat

setInsecure() berguna selama diagnosis terkontrol ketika konfigurasi sertifikat memang sengaja dilewati. Opsi tersebut bukan pengganti yang setara untuk validasi sertifikat pada perangkat yang mengirim kredensial atau telemetry melalui jaringan yang tidak tepercaya.

CA tepercaya menjadi anchor koneksi produksi

WiFiClientSecure dapat diberi material CA untuk memvalidasi certificate chain milik broker:

#include <WiFiClientSecure.h>

static const char root_ca[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
... trusted CA certificate ...
-----END CERTIFICATE-----
)EOF";

WiFiClientSecure espClient;

void setup() {
    espClient.setCACert(root_ca);
}

Placeholder tersebut harus diganti dengan material CA yang sesuai dengan certificate chain broker. Menyalin sertifikat yang tidak terkait ke firmware dapat membuat validasi gagal atau membentuk trust boundary yang keliru.

Masa berlaku sertifikat juga menjadi batas operasional. Perangkat yang menyimpan trust anchor di firmware memerlukan strategi rotasi ketika infrastruktur sertifikat berubah, bukan fallback darurat ke setInsecure().

Sketch sensor berubah pada batas transport

Logika DHT22 dan LED tidak perlu berubah hanya karena transport MQTT beralih ke TLS. Perubahan yang relevan adalah WiFiClientSecure, port listener TLS, certificate trust, dan pemanggilan connect() yang membawa kredensial:

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include "DHT.h"

const char* ssid = "YOUR_WIFI_SSID";
const char* wifi_password = "YOUR_WIFI_PASSWORD";

const char* mqtt_server = "broker.emqx.io";
const uint16_t mqtt_port = 8883;
const char* mqtt_client_id = "YOUR_UNIQUE_CLIENT_ID";

const char* mqtt_user = "YOUR_MQTT_USERNAME";
const char* mqtt_pass = "YOUR_MQTT_PASSWORD";

const char* topic_sensor = "device/sensor";
const char* topic_led = "device/lamp";

#define DHTPIN 4
#define DHTTYPE DHT22
#define LED_PIN 5

static const char root_ca[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
... CA certificate for the broker ...
-----END CERTIFICATE-----
)EOF";

WiFiClientSecure espClient;
PubSubClient client(espClient);
DHT dht(DHTPIN, DHTTYPE);

unsigned long lastPublish = 0;
const unsigned long interval = 5000;

void callback(char* topic, byte* payload, unsigned int length) {
    char message[length + 1];
    memcpy(message, payload, length);
    message[length] = '\0';

    if (strcmp(topic, topic_led) == 0) {
        if (strcmp(message, "ON") == 0) {
            digitalWrite(LED_PIN, HIGH);
        } else if (strcmp(message, "OFF") == 0) {
            digitalWrite(LED_PIN, LOW);
        }
    }
}

void setup_wifi() {
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, wifi_password);

    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
    }
}

void connect_mqtt() {
    if (client.connected()) {
        return;
    }

    if (client.connect(mqtt_client_id, mqtt_user, mqtt_pass)) {
        client.subscribe(topic_led, 1);
    }
}

void setup() {
    Serial.begin(115200);
    pinMode(LED_PIN, OUTPUT);
    dht.begin();
    setup_wifi();

    espClient.setCACert(root_ca);
    client.setServer(mqtt_server, mqtt_port);
    client.setCallback(callback);
}

void loop() {
    if (WiFi.status() != WL_CONNECTED) {
        setup_wifi();
    }

    connect_mqtt();
    client.loop();

    if (millis() - lastPublish >= interval) {
        lastPublish = millis();

        float temperature = dht.readTemperature();
        float humidity = dht.readHumidity();
        if (isnan(temperature) || isnan(humidity)) {
            return;
        }

        char payload[64];
        snprintf(
            payload,
            sizeof(payload),
            "{\"temperature\":%.2f,\"humidity\":%.2f}",
            temperature,
            humidity
        );

        client.publish(topic_sensor, payload, true);
        Serial.println(payload);
    }
}

Kredensial Wi-Fi, kredensial MQTT, client ID, topic, dan material CA merupakan nilai deployment. Public broker EMQX ditujukan untuk pengujian dan demonstrasi; secret perangkat dan telemetry privat tidak seharusnya bergantung pada public test broker bersama.

TLS tidak menggantikan otorisasi topic

Autentikasi username/password yang berhasil tidak seharusnya memberikan akses ke setiap topic. Otorisasi broker dapat membatasi secara terpisah topic yang boleh menjadi tujuan publish dan topic filter yang boleh di-subscribe oleh suatu identitas.

publish:   device/123/sensor
subscribe: device/123/lamp
deny:      semua yang lain

Policy yang sempit mengurangi dampak jika kredensial bocor. Tanpa otorisasi tingkat topic, account yang valid dapat mempunyai akses broker lebih luas daripada yang dibutuhkan firmware.

Reconnection memerlukan visibilitas kegagalan

Fungsi reconnect yang diam-diam mengembalikan kontrol saat gagal membuat masalah TLS dan autentikasi sulit dipisahkan. PubSubClient menyediakan client.state() untuk kegagalan koneksi tingkat MQTT, sedangkan TLS dapat gagal sebelum MQTT menerima respons apa pun dari broker.

Firmware produksi juga sebaiknya menghindari reconnect loop yang terlalu rapat. Delay atau backoff mengurangi handshake berulang ketika broker down, sertifikat gagal diverifikasi, kredensial salah, atau Wi-Fi tidak stabil.

Wi-Fi tidak tersedia
        |
koneksi TCP/TLS gagal
        |
validasi sertifikat gagal
        |
MQTT CONNECT ditolak
        |
otorisasi subscribe/publish gagal

Menganggap semua kondisi tersebut hanya sebagai “MQTT disconnected” menyembunyikan lapisan yang sebenarnya menolak koneksi.

Secure MQTT merupakan komposisi beberapa kontrol

Setiap lapisan mempunyai tugas yang berbeda. TLS mengenkripsi stream TCP. Validasi CA mengautentikasi sertifikat broker. Kredensial MQTT mengautentikasi client sesuai konfigurasi broker. Otorisasi topic membatasi tindakan yang boleh dilakukan identitas yang sudah terautentikasi.

Mengganti 1883 menjadi 8883 hanya merupakan pintu masuk transport. Security boundary menjadi lengkap ketika verifikasi sertifikat, pengelolaan kredensial, autentikasi broker, dan otorisasi topic dikonfigurasi bersama tanpa menganggap satu mekanisme dapat menggantikan mekanisme lainnya.