MQTT Security

MQTT TLS Configuration: A Practical Guide to Port 8883

Learn how MQTT TLS really works, what clients must verify, and how to configure Python, Node.js, ESP32 and mosquitto clients for an encrypted connection on port 8883.

MQTT TLS configuration diagram showing an IoT device verifying a broker certificate chain before sending encrypted messages on port 8883
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

A correct MQTT TLS configuration is what turns MQTT from a plaintext protocol into one you can safely run over the internet. In this guide you will learn what the client actually verifies during the handshake, which settings matter, and how to configure Python, Node.js, ESP32 and command-line clients to connect over port 8883. Every example connects to a real broker, so you can test as you read.

Why MQTT Needs TLS

MQTT itself has no encryption. On port 1883 the CONNECT packet, including the username and password, travels as readable bytes, and so does every PUBLISH payload. Anyone on the same Wi-Fi, a compromised router, or a curious ISP can read and even modify that traffic. If you want the wider context of threats beyond eavesdropping, read our MQTT security best practices overview.

TLS (Transport Layer Security) wraps the TCP connection in an encrypted, authenticated tunnel before any MQTT bytes are exchanged. MQTT does not change at all; the broker simply listens on a different port, conventionally 8883, and the client performs a TLS handshake first. That gives you three properties:

  • Confidentiality: traffic is encrypted, so credentials and payloads cannot be read in transit.
  • Integrity: tampered packets are detected and the connection is dropped.
  • Server authentication: the client proves it is talking to the real broker, not an impostor.

The third property is the one most often broken by sloppy configuration, and it is the focus of the rest of this article.

What the Client Verifies During the Handshake

When your client opens a TLS connection, the broker sends its certificate plus any intermediate certificates. The client then runs these checks:

  1. Chain of trust: the server certificate must be signed by an intermediate, which must be signed by a root CA that the client already trusts (its CA store or a CA file you provide).
  2. Validity period: the current time must fall between the certificate's "not before" and "not after" dates. This is why devices without a real-time clock need NTP before connecting.
  3. Hostname match: the hostname you connected to must appear in the certificate's Subject Alternative Name list. Connecting by IP address to a certificate issued for a hostname will fail, and it should.
  4. Key possession: the handshake proves the broker holds the private key matching the certificate.

Two related details trip people up. First, SNI (Server Name Indication): the client sends the hostname inside the handshake so a server hosting several names can present the right certificate. Modern libraries send SNI automatically when you pass a hostname. Second, hostname verification is separate from chain verification in some libraries. A client can accept a perfectly valid certificate for the wrong domain if hostname checking is disabled, which defeats the point of TLS.

MQTT TLS Configuration Settings at a Glance

The table below maps the common transports to ports and URL schemes. The ports shown are the ones used by ConnectMQ and by most brokers that follow convention.

Transport Port URL scheme Encrypted Typical client
MQTT over TCP 1883 mqtt:// No Lab tests only
MQTT over TLS 8883 mqtts:// Yes Devices, backends, CLI
MQTT over WebSocket 8083 ws:// No Local browser tests
MQTT over secure WebSocket 8084 wss:// (path /mqtt) Yes Browsers, restrictive networks

For the trust anchor, you have three options:

  • System CA store: the default for Python, Node.js, Go and most desktop or server clients. Works when the broker uses a publicly trusted certificate.
  • Explicit CA file: you pass a PEM file containing the root CA. Required for private CAs and for most microcontrollers.
  • Pinning: you trust only one specific CA or key. Stronger but more fragile; see our guide to ESP32 certificate pinning.

The ConnectMQ broker uses a Let's Encrypt certificate, which chains to the ISRG Root X1 root that ships in every mainstream operating system and in the CA bundles of Python (via certifi) and Node.js. On desktops and servers you normally do not need to supply any CA file at all.

Testing the Connection Before Writing Code

Before touching application code, confirm the network path and the certificate with OpenSSL:

openssl s_client -connect broker.connectmq.com:8883 -servername broker.connectmq.com -brief </dev/null

Look for Verification: OK and the negotiated protocol version. If you see a verification error here, no client library will succeed either; our article on fixing MQTT TLS handshake failures walks through each error.

Then try a real MQTT round trip with the mosquitto clients. Open two terminals and pick a unique topic prefix:

# Terminal 1: subscribe over TLS using the system CA directory (Linux/macOS)
mosquitto_sub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
  -i tls-sub-7f3a -t "connectmq/demo/7f3a/#" -v

# Terminal 2: publish over TLS
mosquitto_pub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
  -i tls-pub-7f3a -t "connectmq/demo/7f3a/hello" -m "encrypted hello"

On Windows, mosquitto cannot read the system store, so download the ISRG Root X1 PEM from Let's Encrypt and pass it with --cafile isrgrootx1.pem. The free public MQTT broker accepts anonymous connections, so these commands work immediately. Remember that the public area is readable by anyone, so never send real data there.

Python Client Configuration (paho-mqtt 2.x)

With paho-mqtt 2.x, tls_set() with no arguments loads the system's default CA certificates, enables hostname checking and requires a valid certificate. That is exactly what you want.

import ssl
import time
import paho.mqtt.client as mqtt

BROKER = "broker.connectmq.com"
PORT = 8883
TOPIC = "connectmq/demo/7f3a/python"

def on_connect(client, userdata, flags, reason_code, properties):
    print("Connected:", reason_code)
    client.subscribe(TOPIC, qos=1)

def on_message(client, userdata, msg):
    print(f"{msg.topic}: {msg.payload.decode()}")

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="py-tls-7f3a")
client.on_connect = on_connect
client.on_message = on_message

# System CA store, certificate required, hostname verified,
# newest TLS version both sides support
client.tls_set(cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLS_CLIENT)

# For a private ConnectMQ broker, add the credentials from the portal:
# client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")

client.connect(BROKER, PORT, keepalive=60)
client.loop_start()
time.sleep(1)
client.publish(TOPIC, "hello over TLS", qos=1)
time.sleep(2)
client.loop_stop()
client.disconnect()

If your platform's CA store is broken or outdated, pass ca_certs=certifi.where() instead. Never "fix" an error with tls_insecure_set(True); that disables hostname checking and silently removes server authentication. For the common Python errors, see SSL CERTIFICATE_VERIFY_FAILED in MQTT.

Node.js Client Configuration (MQTT.js v5)

MQTT.js uses Node's built-in TLS stack, which validates against Node's bundled Mozilla CA list and verifies the hostname by default.

const mqtt = require("mqtt");

const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
  clientId: "node-tls-7f3a",
  protocolVersion: 5,
  // For a private broker:
  // username: "YOUR_USERNAME",
  // password: "YOUR_PASSWORD",
  rejectUnauthorized: true, // default; never set to false in production
});

const topic = "connectmq/demo/7f3a/node";

client.on("connect", () => {
  console.log("Connected over TLS");
  client.subscribe(topic, { qos: 1 }, () => {
    client.publish(topic, "hello from Node.js", { qos: 1 });
  });
});

client.on("message", (t, payload) => {
  console.log(`${t}: ${payload.toString()}`);
  client.end();
});

client.on("error", (err) => console.error("MQTT error:", err.message));

If you must trust a private CA, add ca: fs.readFileSync("ca.pem") to the options. In the browser, use wss://broker.connectmq.com:8084/mqtt; the browser handles certificate validation for you.

ESP32 Client Configuration

Microcontrollers do not have an operating system CA store, so you embed the root certificate. With the Arduino core, WiFiClientSecure plus PubSubClient looks like this:

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <time.h>

const char* WIFI_SSID = "your-ssid";
const char* WIFI_PASS = "your-password";
const char* BROKER = "broker.connectmq.com";

// Paste the full ISRG Root X1 PEM from letsencrypt.org here
static const char ROOT_CA[] = R"EOF(
-----BEGIN CERTIFICATE-----
...ISRG Root X1 certificate body...
-----END CERTIFICATE-----
)EOF";

WiFiClientSecure net;
PubSubClient mqtt(net);

void setup() {
  Serial.begin(115200);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) delay(250);

  // A valid clock is needed to check certificate dates
  configTime(0, 0, "pool.ntp.org");
  while (time(nullptr) < 1700000000) delay(250);

  net.setCACert(ROOT_CA);
  mqtt.setServer(BROKER, 8883);
}

void loop() {
  if (!mqtt.connected()) {
    if (mqtt.connect("esp32-tls-7f3a")) {
      mqtt.publish("connectmq/demo/7f3a/esp32", "hello over TLS");
    } else {
      Serial.printf("MQTT connect failed, state=%d\n", mqtt.state());
      delay(5000);
    }
  }
  mqtt.loop();
}

For a private broker use mqtt.connect("YOUR_CLIENT_ID", "YOUR_USERNAME", "YOUR_PASSWORD"). The ESP32 MQTT over SSL tutorial covers memory use and reconnect behaviour in more depth.

Production Checklist

  1. Use port 8883 (or 8084 for browsers) everywhere outside a lab; disable or firewall plain 1883 for production clients.
  2. Verify the certificate chain against a trusted root, not "accept anything".
  3. Keep hostname verification on and always connect by hostname, never by IP.
  4. Sync time (NTP) on embedded devices before the handshake.
  5. Trust the root CA, not the leaf certificate, because leaf certificates rotate often.
  6. Send credentials only over TLS and store them outside source code.
  7. Plan for CA changes: make the trusted root updatable over the air.
  8. Monitor failed connections; repeated TLS or auth failures usually signal misconfiguration or attack.

Let a Managed Broker Handle the Server Side

The broker side of TLS is the less fun half: obtaining certificates, renewing them before they expire, choosing protocol versions and ciphers, and restarting listeners without dropping clients. A managed MQTT broker removes that work. ConnectMQ terminates TLS on 8883 and secure WebSocket on 8084 with automatically renewed Let's Encrypt certificates, so your clients only need the standard configuration shown above. If you are comparing hosting options, our cloud MQTT broker guide explains the trade-offs.

Get Started with ConnectMQ

Create a free account at app.connectmq.com, choose the Serverless (Free) plan with no credit card, create a broker and copy the generated username, password and client ID. Point any of the clients above at port 8883 with those credentials and you have an encrypted, authenticated MQTT connection in minutes.

Frequently Asked Questions

Is MQTT over TLS slower than plain MQTT?

The handshake adds round trips and CPU work when a connection opens, but MQTT connections are long-lived, so the steady-state overhead is small. Keep connections open and reuse sessions instead of reconnecting for every message.

Do I need a client certificate to use MQTT over TLS?

No. Most deployments use server-side TLS plus a username and password sent inside the encrypted tunnel. Client certificates (mutual TLS) are an additional option some self-hosted brokers support.

Which CA certificate do I need for ConnectMQ?

The broker certificate is issued by Let's Encrypt and chains to ISRG Root X1. Desktop and server clients already trust it; embedded devices should embed that root PEM.

Why does my client fail when I connect by IP address?

The certificate lists hostnames, not IP addresses, so hostname verification fails. Always connect using broker.connectmq.com or your broker's hostname.

Related articles

How to Rotate MQTT Credentials Without Downtime

A step-by-step dual-credential strategy for rotating MQTT usernames and passwords across a fleet: stage the new credential, switch with fallback, verify and only then revoke the old one.

7 min read

Storing MQTT Credentials Securely on Embedded Devices

A practical comparison of ways to protect MQTT usernames and passwords on ESP32 and Linux devices, from encrypted NVS and flash encryption to secure elements and TPM-backed systemd credentials.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.