To run MQTT over TLS on an ESP32 PLC, wrap the network client (Ethernet or Wi-Fi) in a TLS client, give it the CA certificate of the broker as a trust anchor, and connect PubSubClient to the TLS port of the broker. With mutual TLS (mTLS), the PLC also sends its own client certificate, so the broker knows which device is connecting. This guide uses test.mosquitto.org on port 8884, which requires a client certificate.
The guide covers the whole chain: the OpenSSL commands for the client certificate, the trust anchor file for the broker CA, a script that puts the certificate and the key into a header file, the Arduino code for the ESP32 PLC, and a troubleshooting table with the real error messages. The certificate steps are the same for any controller or gateway that talks MQTT over TLS, also if you do not program Arduino.
How does MQTT over TLS work on an ESP32 PLC?
MQTT itself does not encrypt anything. MQTT over TLS (also called MQTTS or secure MQTT) runs the normal MQTT packets inside a TLS session on top of TCP. The registered port for MQTT over TLS is 8883. Public test brokers often use other ports for other security options, like 8884 on test.mosquitto.org.
TLS gives three things: encryption of the data on the network, a check that the server is the real broker, and, with mTLS, a check that the client is a known device. Without the server check, the PLC would send its data to anyone who can redirect the traffic.
- 1 · TCPSocket to port 8884Ethernet or Wi-Fi, the same code path
- 2 · Server checkBroker certificateVerified against the trust anchor stored in the PLC
- 3 · Client checkPLC certificateThe PLC proves it has the private key
- 4 · MQTTCONNECT, PUBLISH, SUBSCRIBENormal MQTT packets inside the encrypted session
On the ESP32 PLC, the SSLClient library (OPEnS Lab, based on BearSSL) does steps 2 and 3. It works on top of any Arduino Client, so the same sketch runs over the Ethernet port or over Wi-Fi. PubSubClient does step 4 and does not know that TLS is there.
These are the files you prepare, and where each one ends up:
| File | What it is | Where it goes | Secret? |
|---|---|---|---|
mosquitto.org.crt | CA certificate of the broker | Converted to trust_anchors.h | No |
trust_anchors.h | The CA in BearSSL format | Firmware of the PLC | No |
client.key | Private key of the PLC | Firmware, through ssl_client.h | Yes |
client.csr | Certificate request | Sent to the CA once | No |
client.crt | Client certificate signed by the CA | Firmware, through ssl_client.h | No |
ssl_client.h | Certificate and key as C strings | Firmware of the PLC | Yes (contains the key) |
Hardware, libraries and network for MQTT over TLS
- Controller: any ESP32 PLC. The original example ran on an ESP32 PLC 21+, and the code does not use model-specific I/O.
- Boards package: the Industrial Shields boards for the Arduino IDE, which include the Ethernet library of the PLC (see how to install the boards).
- Libraries: SSLClient (version 1.6.11 in our test) and PubSubClient (version 2.8 in our test). Both install from the Library Manager of the Arduino IDE.
- PC tools: OpenSSL and Python 3, to create the certificate and the header files.
- Network: the PLC must reach the broker on TCP port 8884, with DNS. On a plant network, ask IT to open this outgoing port.
For Wi-Fi setup on the PLC, see how to connect your ESP32 based PLC to Wi-Fi. For sites without a cable, the same TLS client also works over the 4G connection of the ESP32 PLC, as long as the modem library gives an Arduino Client.
Create the client certificate with OpenSSL
The client certificate identifies one PLC. You create a private key and a certificate request (CSR) on your PC, and the CA of the broker signs the request. For test.mosquitto.org, the CA is the signing form at test.mosquitto.org/ssl.
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr -subj "/C=ES/O=Your company/CN=esp32plc-line1"
Write your own Country (C), Organisation (O) and Common Name (CN). The CSR must include these three fields, and the signer does not accept the default values of OpenSSL (see the Espressif mutual TLS example for the same broker). A good Common Name is the name of the device, for example the line and the PLC.
Open client.csr with a text editor, paste all of it in the form, including the BEGIN and END lines, and submit. The page returns client.crt. Check it against the CA before you continue:
openssl verify -CAfile mosquitto.org.crt client.crt
Why the key has no passphrase. The PLC needs the key in clear text to sign the TLS handshake, so a passphrase on client.key only protects the file on your PC. Keep client.key and ssl_client.h out of shared folders and code repositories, and create one key and one certificate for each PLC.
Convert the broker CA into SSLClient trust anchors
SSLClient does not read PEM certificates for the server check. It needs trust anchors: the name and the public key of the CA as C arrays. Download the CA certificate of the broker from test.mosquitto.org/ssl/mosquitto.org.crt and convert it with pycert_bearssl.py, the tool in the SSLClient repository (it needs the Python packages click, certifi and pyopenssl):
python pycert_bearssl.py convert --no-search mosquitto.org.crt -o trust_anchors.h
The online BearSSL certificate utility does not help here: it builds trust anchors from public root CAs, and the mosquitto.org CA is a self-signed CA that is not in the public root stores. With --no-search, the tool uses the file as it is.
This is the result for the current mosquitto.org CA (valid from 9 June 2020 to 7 June 2030). We generated it again on 22 October 2026, and the key bytes are the same as in the first version of this guide:
#ifndef _TRUST_ANCHORS_H_
#define _TRUST_ANCHORS_H_
#ifdef __cplusplus
extern "C"
{
#endif
/* This file is auto-generated by the pycert_bearssl tool. Do not change it manually.
* Certificates are BearSSL br_x509_trust_anchor format. Included certs:
*
* Index: 0
* Label: mosquitto.org
* Subject: [email protected],CN=mosquitto.org,OU=CA,O=Mosquitto,L=Derby,ST=United Kingdom,C=GB
* Type: Certificate Authority
*/
#define TAs_NUM 1
static const unsigned char TA_DN0[] = {
0x30, 0x81, 0x90, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
0x13, 0x02, 0x47, 0x42, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
0x08, 0x0c, 0x0e, 0x55, 0x6e, 0x69, 0x74, 0x65, 0x64, 0x20, 0x4b, 0x69,
0x6e, 0x67, 0x64, 0x6f, 0x6d, 0x31, 0x0e, 0x30, 0x0c, 0x06, 0x03, 0x55,
0x04, 0x07, 0x0c, 0x05, 0x44, 0x65, 0x72, 0x62, 0x79, 0x31, 0x12, 0x30,
0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x09, 0x4d, 0x6f, 0x73, 0x71,
0x75, 0x69, 0x74, 0x74, 0x6f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
0x04, 0x0b, 0x0c, 0x02, 0x43, 0x41, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03,
0x55, 0x04, 0x03, 0x0c, 0x0d, 0x6d, 0x6f, 0x73, 0x71, 0x75, 0x69, 0x74,
0x74, 0x6f, 0x2e, 0x6f, 0x72, 0x67, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x09,
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x10, 0x72,
0x6f, 0x67, 0x65, 0x72, 0x40, 0x61, 0x74, 0x63, 0x68, 0x6f, 0x6f, 0x2e,
0x6f, 0x72, 0x67,
};
static const unsigned char TA_RSA_N0[] = {
0xc1, 0x34, 0x1c, 0xa9, 0x88, 0xcd, 0xf4, 0xce, 0xc2, 0x42, 0x8b, 0x4f,
0x74, 0xc7, 0x1d, 0xef, 0x8e, 0x6d, 0xd8, 0xb3, 0x6a, 0x63, 0xe0, 0x51,
0x99, 0x83, 0xeb, 0x84, 0xdf, 0xdf, 0x32, 0x5d, 0x35, 0xe6, 0x06, 0x62,
0x7e, 0x02, 0x11, 0x76, 0xf2, 0x3f, 0xa7, 0xf2, 0xde, 0xd5, 0x9c, 0xf1,
0x2d, 0x9b, 0xa1, 0x6e, 0x9d, 0xce, 0xb1, 0xfc, 0x49, 0xd1, 0x5f, 0xf6,
0xea, 0x37, 0xdb, 0x41, 0x89, 0x03, 0xd0, 0x7b, 0x53, 0x51, 0x56, 0x4d,
0xed, 0xf1, 0x75, 0xaf, 0xcb, 0x9b, 0x72, 0x45, 0x7d, 0xa1, 0xe3, 0x91,
0x6c, 0x3b, 0x8c, 0x1c, 0x1c, 0x6a, 0xe4, 0x19, 0x8e, 0x91, 0x88, 0x34,
0x76, 0xa9, 0x1d, 0x19, 0x69, 0x88, 0x26, 0x6c, 0xaa, 0xe0, 0x2d, 0x84,
0xe8, 0x31, 0x5b, 0xd4, 0xa0, 0x0e, 0x06, 0x25, 0x1b, 0x31, 0x00, 0xb3,
0x4e, 0xa9, 0x90, 0x41, 0x62, 0x33, 0x0f, 0xaa, 0x0d, 0xf2, 0xe8, 0xfe,
0xcc, 0x45, 0x28, 0x1e, 0xaf, 0x42, 0x51, 0x5e, 0x90, 0xc7, 0x82, 0xca,
0x68, 0xcb, 0x09, 0xb3, 0x70, 0x3c, 0x9c, 0xaa, 0xca, 0x11, 0x66, 0x3d,
0x6c, 0x22, 0xa3, 0xf3, 0xc3, 0x32, 0xbb, 0x81, 0x4f, 0x33, 0xc7, 0xdd,
0xc8, 0xa8, 0x06, 0x7a, 0xc9, 0x58, 0xa5, 0xdc, 0xdc, 0xe8, 0xd7, 0x74,
0xb1, 0x85, 0x24, 0xe7, 0xe3, 0xee, 0x93, 0xf4, 0x8f, 0xf7, 0x6b, 0xd8,
0xb1, 0xfb, 0xd9, 0xe4, 0xaf, 0xbf, 0x73, 0xd0, 0x40, 0x59, 0x7d, 0xd0,
0x26, 0x4f, 0x16, 0x1a, 0xc2, 0x51, 0xc4, 0x47, 0x49, 0x2c, 0x68, 0x13,
0xac, 0xa3, 0x18, 0xe7, 0x67, 0xcf, 0xb7, 0xfa, 0x3e, 0xf7, 0x8b, 0x20,
0x1e, 0x7b, 0xe2, 0x44, 0x0e, 0x47, 0x0b, 0x7c, 0x78, 0xf9, 0xf4, 0xca,
0x27, 0x6b, 0x4c, 0x2d, 0x62, 0x72, 0xd8, 0xa4, 0x10, 0x3d, 0xe7, 0x1d,
0x88, 0x4c, 0x50, 0xe5,
};
static const unsigned char TA_RSA_E0[] = {
0x01, 0x00, 0x01,
};
static const br_x509_trust_anchor TAs[] = {
{
{ (unsigned char *)TA_DN0, sizeof TA_DN0 },
BR_X509_TA_CA,
{
BR_KEYTYPE_RSA,
{ .rsa = {
(unsigned char *)TA_RSA_N0, sizeof TA_RSA_N0,
(unsigned char *)TA_RSA_E0, sizeof TA_RSA_E0,
} }
}
},
};
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* ifndef _TRUST_ANCHORS_H_ */
If you use your own broker, generate the file from the CA that signed the certificate of your broker, not from the broker certificate itself.
Put the client certificate and key in ssl_client.h
The sketch reads the client certificate and the key from ssl_client.h, as two C strings in PEM format. This script writes the file and stops if the key still has a passphrase:
"""Write ssl_client.h with the client certificate and the private key as C strings.
Usage: python pem_to_header.py client.crt client.key
"""
import sys
def c_string(path):
with open(path, "r", encoding="ascii") as f:
lines = [line.strip() for line in f if line.strip()]
if not lines or not lines[0].startswith("-----BEGIN"):
sys.exit(f"{path} is not a PEM file")
if "ENCRYPTED" in lines[0] or any(l.startswith("Proc-Type") for l in lines):
sys.exit(f"{path} is encrypted. Remove the passphrase first: "
f"openssl pkey -in {path} -out plain.key")
return "\n".join(f' "{line}\\n"' for line in lines)
if __name__ == "__main__":
if len(sys.argv) != 3:
sys.exit("Usage: python pem_to_header.py client.crt client.key")
cert, key = c_string(sys.argv[1]), c_string(sys.argv[2])
with open("ssl_client.h", "w", encoding="ascii") as out:
out.write("#pragma once\n\n")
out.write(f"const char my_cert[] =\n{cert};\n\n")
out.write(f"const char my_key[] =\n{key};\n")
print("ssl_client.h written")
python pem_to_header.py client.crt client.key
Copy ssl_client.h and trust_anchors.h into the folder of the sketch. SSLClient accepts RSA and EC keys, in the PKCS#1 format (BEGIN RSA PRIVATE KEY) or in the PKCS#8 format (BEGIN PRIVATE KEY) that OpenSSL 3 writes by default.
ESP32 PLC code for MQTT over TLS with SSLClient and PubSubClient
The sketch connects to test.mosquitto.org on port 8884 with the client certificate, publishes one message to industrialshields/<client ID>/out and prints every message that arrives on industrialshields/<client ID>/in. It uses the Ethernet port by default. To use Wi-Fi, comment out the line #define USE_ETHERNET and write your network name and password.
// MQTT over TLS with client certificate (mutual TLS) on an ESP32 PLC
// Broker: test.mosquitto.org, port 8884 (TLS, client certificate required)
// Libraries: SSLClient (OPEnS Lab) and PubSubClient (Nick O'Leary)
#include <SSLClient.h>
#include <PubSubClient.h>
#include "trust_anchors.h" // CA certificate of the broker (BearSSL format)
#include "ssl_client.h" // client certificate and private key (PEM)
#define USE_ETHERNET // Comment out this line to use Wi-Fi
#ifdef USE_ETHERNET
#include <Ethernet.h>
byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED}; // use a unique MAC on your network
EthernetClient netClient;
#else
#include <WiFi.h>
const char* WIFI_SSID = "your-ssid";
const char* WIFI_PASS = "your-password";
WiFiClient netClient;
#endif
const char* BROKER_HOST = "test.mosquitto.org";
const uint16_t BROKER_PORT = 8884;
// SSLClient reads 16 values from an analog pin to seed its random generator.
// Use a pin that is not wired to the process.
SSLClient tlsClient(netClient, TAs, (size_t)TAs_NUM, Q0_7);
SSLClientParameters mtls = SSLClientParameters::fromPEM(my_cert, sizeof my_cert, my_key, sizeof my_key);
PubSubClient mqtt(tlsClient);
char clientId[24];
char topicOut[48];
char topicIn[48];
unsigned long lastAttempt = 0;
const unsigned long RETRY_MS = 5000;
void onMessage(char* topic, byte* payload, unsigned int length) {
Serial.print("Message on ");
Serial.print(topic);
Serial.print(": ");
for (unsigned int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}
// One connection attempt (TCP, TLS handshake, MQTT CONNECT).
// After a failure, loop() waits RETRY_MS before the next attempt.
bool connectMqtt() {
Serial.print("Connecting to the broker... ");
if (mqtt.connect(clientId)) {
Serial.println("connected");
mqtt.publish(topicOut, "hello industrial world");
mqtt.subscribe(topicIn);
return true;
}
Serial.print("failed, state=");
Serial.println(mqtt.state());
return false;
}
void setup() {
Serial.begin(115200);
// Unique client ID and topics: the broker closes an older session with the same ID
uint32_t chip = (uint32_t)((ESP.getEfuseMac() >> 24) & 0xFFFFFF); // last 3 bytes of the MAC
snprintf(clientId, sizeof clientId, "esp32plc-%06lX", (unsigned long)chip);
snprintf(topicOut, sizeof topicOut, "industrialshields/%s/out", clientId);
snprintf(topicIn, sizeof topicIn, "industrialshields/%s/in", clientId);
Serial.print("Client ID: ");
Serial.println(clientId);
tlsClient.setMutualAuthParams(mtls);
mqtt.setServer(BROKER_HOST, BROKER_PORT);
mqtt.setCallback(onMessage);
#ifdef USE_ETHERNET
Ethernet.begin(mac); // DHCP
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
Serial.println("Ethernet hardware not found");
}
if (Ethernet.linkStatus() == LinkOFF) {
Serial.println("Ethernet cable is not connected");
}
Serial.print("IP address: ");
Serial.println(Ethernet.localIP());
#else
WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
#endif
}
void loop() {
if (!mqtt.connected()) {
if (lastAttempt == 0 || millis() - lastAttempt >= RETRY_MS) {
lastAttempt = millis();
connectMqtt();
}
} else {
mqtt.loop(); // keep-alive and incoming messages
}
// The control logic of the PLC runs here, also while the broker is offline.
}
Four details matter in a PLC program:
- Unique client ID. The broker closes an older session when a new client connects with the same ID. Two PLCs with the same fixed ID disconnect each other in a loop. The sketch builds the ID from the chip MAC address of the ESP32, and uses it in the topics too, because anyone can read a public test broker.
- Connection retries that do not block the PLC.
loop()makes one attempt every 5 seconds and returns. The control logic keeps running while the broker or the network is down. Each attempt still takes the time of the TLS handshake. - Random seed. SSLClient seeds its random generator with 16 readings of the analog pin given in the constructor. The example keeps
Q0_7from the original version. Before you use it in a plant, print 16 readings of that pin on the serial monitor: if they always give the same value, the seed is predictable and the TLS session keys are weak, so use a free analog input that is not wired. - Validation time. To check the dates of the server certificate, SSLClient uses the date and time of compilation by default. A PLC that runs for years cannot see that a certificate has expired unless you call
setVerificationTime()with a real date, for example from the RTC synchronised with NTP.
PubSubClient publishes at QoS 0 only and subscribes at QoS 0 or 1. Its default buffer is 256 bytes, which limits the size of one message including the topic; use mqtt.setBufferSize() for longer JSON payloads. The default keep-alive is 15 seconds.
How we tested it (22 October 2026). We compiled the sketch without warnings (-Wall -Wextra) on a Linux PC together with the real SSLClient 1.6.11, BearSSL and PubSubClient 2.8 sources, with the Ethernet client replaced by a TCP socket. Against a local Mosquitto 2.0.18 broker with require_certificate true on port 8884, and with a certificate created with the commands above: the PLC code connected, published, received a message from mosquitto_pub, and reconnected by itself after a broker restart. A certificate signed by another CA and a wrong trust anchor were rejected, with the messages shown in the troubleshooting table. This test does not replace a test on the ESP32 PLC with your network.
MQTT over TLS errors on the ESP32 PLC and how to fix them
With the default log level, SSLClient prints its errors on the serial monitor. mqtt.state() gives the MQTT result: -2 means that the network or TLS connection failed, 5 means that the broker refused the client.
| Message or symptom | Cause | Fix |
|---|---|---|
Failed to connect using m_client, state -2 | No TCP connection: no IP address, no DNS, or port 8884 closed | Check the cable or Wi-Fi, DNS and the firewall. Test the port from a PC on the same network. |
Chain could not be linked to a trust anchor | trust_anchors.h does not match the CA of the broker | Generate the file again from the CA that signed the broker certificate. |
Unknown error code: 304 | The broker sent a fatal TLS alert (304 = 256 + alert 48, unknown CA): it does not trust the client certificate | Use a client certificate signed by the CA of the broker, and check that setMutualAuthParams() runs before the first connection. |
Broker log: peer did not send certificate chain | The client certificate or the key is not valid PEM, so SSLClient connects without mTLS | Generate ssl_client.h again with the script and check the BEGIN and END lines. |
| Handshake times out only over Ethernet | The Ethernet socket buffer is too small for TLS records | The SSLClient documentation describes larger socket buffers for the Arduino Ethernet library. Test the same code over Wi-Fi to confirm. |
| The PLC connects and disconnects every few seconds | Another client uses the same client ID | Use a unique ID for each device, as in the sketch. |
| Connected, but no message arrives | Topic typo, or a message larger than the PubSubClient buffer | Subscribe to industrialshields/# with mosquitto_sub and check the buffer size. |
For a TLS problem, first test the same files from a PC. If this command connects, the certificates are correct and the problem is on the PLC side:
mosquitto_sub -h test.mosquitto.org -p 8884 --cafile mosquitto.org.crt --cert client.crt --key client.key -t 'industrialshields/#' -v
More MQTT tests from a Windows PC: MQTT troubleshooting in Windows.
From the test broker to a plant broker with TLS
test.mosquitto.org is a public service for tests. Anyone can subscribe to your topics, and it can be offline. In a plant, the PLCs connect to your own broker: for example Mosquitto on a server or on a Raspberry PLC. The same files and the same sketch work: you only change the host, the port and the CA.
- Listener: port 8883 with
cafile,certfileandkeyfile, andrequire_certificate truefor mTLS (mosquitto.conf reference). - Identity and permissions:
use_identity_as_username truemakes the Common Name of the certificate the MQTT user name, so an ACL file can limit each PLC to its own topics. - Lost or replaced devices: one certificate per PLC lets you revoke only that device with a certificate revocation list (
crlfile). - The key in the firmware: it is stored in the flash memory as plain text. With physical access and a programmer, someone can read it, unless flash encryption is active on the ESP32. Lock the cabinet and keep the certificates short-lived where you can.
- Time: set the validation time from the RTC or NTP, so that expired certificates are detected.
The central Mosquitto broker for a solar tracker fleet in the PLC Solution Library shows a plant broker with authentication, and explains when to add the TLS listener. For the security concepts behind these choices, read MQTT security fundamentals in industrial automation.
Remote telemetry with ESP32 PLC and MQTT: examples
Encrypted MQTT is most useful when PLCs send data from remote sites over 4G, shared networks or the internet. These pages show the rest of the system:
- Remote site telemetry simulator: a town water network monitored with ESP32 PLC stations over 4G and LoRaWAN, with leak detection and alarms.
- Water treatment case study: one ESP32 PLC 42 doses pH and chlorine and sends the values by MQTT to a Panel PC over Ethernet.
- MQTT remote commands on an ESP32 PLC with a response topic and offline-first MQTT data logging with an SD card, two functions you can run on top of the TLS connection.
- Node-RED MQTT concentrator for ESP32 PLC fleets: the server side that receives the data.
Plan an MQTT over TLS project with ESP32 PLC. Try the telemetry simulator, or send us your architecture (broker, network, number of PLCs) and an engineer will review it with you.
FAQ about MQTT over TLS on ESP32
Which port does MQTT over TLS use?
The registered port is 8883. A broker can use any port. On test.mosquitto.org, port 8884 is the TLS port that requires a client certificate, and it uses the mosquitto.org CA.
Can I use a user name and password instead of a client certificate?
Yes. Keep TLS with the server check (trust anchors) and pass the user name and password in mqtt.connect(clientId, user, password); then you do not call setMutualAuthParams(). TLS protects the password on the network. A client certificate avoids shared passwords and lets you revoke one device.
How long does the TLS handshake take on an ESP32?
The SSLClient documentation gives 1 to 4 seconds for a full handshake on microcontrollers, and recommends allowing 4 to 10 seconds on a SAMD21; the ESP32 is faster. After that, the session stays open and each message only adds the encryption of a few bytes.
Can I use WiFiClientSecure instead of SSLClient?
Over Wi-Fi, yes: the ESP32 Arduino core includes a TLS client based on mbedTLS with functions for the CA, the client certificate and the key. SSLClient is used here because it works on top of any Arduino Client, so Ethernet and Wi-Fi share one code path.
Do I need a new trust_anchors.h when the broker certificate changes?
Not if the broker gets a new certificate from the same CA, because the trust anchor is the CA. You need a new file when the CA itself changes. The current mosquitto.org CA is valid until 7 June 2030.
Is MQTT over TLS enough to secure a PLC?
It protects the data on the network and controls which devices connect. You still need topic permissions on the broker, safe handling of the keys, firmware updates and network segmentation between the plant and the internet.