IoT Tutorials

Python MQTT TLS: Connecting Securely on Port 8883 with paho-mqtt

Encrypt your Python MQTT traffic with paho-mqtt and TLS on port 8883: use the system CA store, keep hostname verification on, fix the common certificate errors, and connect over secure WebSocket when only 443-style ports are open.

Padlock over a Python script showing a python mqtt tls connection to a broker 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

Python MQTT TLS is the difference between sending your username, password and sensor data across the internet in plain text and sending it encrypted and authenticated. In this tutorial you will configure paho-mqtt to connect on port 8883 using your operating system's trusted CA certificates, keep hostname verification switched on, and fix the certificate errors that stop most people. Every example connects to broker.connectmq.com, which serves a publicly trusted Let's Encrypt certificate, so no custom CA files are needed.

What TLS Does for MQTT

MQTT itself has no encryption. On port 1883 everything, including the CONNECT packet with your credentials, travels as readable bytes. TLS wraps the TCP connection and gives you three guarantees:

  1. Confidentiality: nobody on the network path can read payloads or passwords.
  2. Integrity: nobody can modify messages in transit without detection.
  3. Server authentication: your client verifies it is really talking to the broker it asked for, not an impostor.

The third point is the one people accidentally disable. Encryption without verification protects you from passive eavesdroppers but not from an active attacker who presents their own certificate. The MQTT security best practices guide covers the wider picture; this article focuses on getting the client side right.

ConnectMQ exposes these ports on every broker:

Port Transport Encrypted Use from Python
1883 MQTT over TCP No Local tests only
8883 MQTT over TLS Yes Default for production
8083 MQTT over WebSocket No Rarely needed from Python
8084 MQTT over secure WebSocket, path /mqtt Yes When only web ports are allowed

Prerequisites

python -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install "paho-mqtt>=2.0"

Check that the broker's certificate validates from your machine before writing any Python. This command shows the chain and the verification result:

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

Look for Verification: OK. If OpenSSL already fails here, the problem is your system's CA store or network, not your code.

The Minimal Python MQTT TLS Client

Save this as tls_client.py and replace k7f3q2 with your own random string:

import ssl
import time
import uuid

import paho.mqtt.client as mqtt

BROKER = "broker.connectmq.com"
PORT = 8883
TOPIC = "connectmq/demo/k7f3q2/secure"


def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        print(f"Connect failed: {reason_code}")
        return
    sock = client.socket()
    print(f"Connected over {sock.version()} using {sock.cipher()[0]}")
    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=f"tls-{uuid.uuid4().hex[:8]}")
client.on_connect = on_connect
client.on_message = on_message

# Use the operating system's trusted CAs, require a valid certificate,
# and verify that it matches BROKER. This is the secure default.
client.tls_set(cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLS_CLIENT)

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

Run it and you should see something like Connected over TLSv1.3 using TLS_AES_256_GCM_SHA384, followed by your message coming back.

What tls_set() does

Called with no ca_certs argument, tls_set() builds an SSL context that loads the platform's default trusted certificates, requires the server to present a valid certificate, and checks that the certificate's name matches the host you passed to connect(). The explicit cert_reqs and tls_version arguments above are the defaults, written out so the intent is obvious to the next reader.

If you prefer full control, build the context yourself and hand it over:

context = ssl.create_default_context()
context.minimum_version = ssl.TLSVersion.TLSv1_2
client.tls_set_context(context)

ssl.create_default_context() enables hostname checking and certificate verification, and setting a minimum version rejects legacy protocols. Use either tls_set() or tls_set_context(), not both.

Adding Credentials for a Private Broker

TLS protects the credentials you send, so always combine the two. With a broker created in the ConnectMQ portal:

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="YOUR_CLIENT_ID")
client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
client.tls_set()
client.connect("broker.connectmq.com", 8883, keepalive=60)

Use the host name and client ID shown in the portal; the broker binds client IDs to the credentials it issues. Load secrets from environment variables (os.environ["MQTT_PASSWORD"]) rather than committing them to source control.

Secure WebSocket From Python

Some networks block everything except standard web ports. paho can tunnel MQTT through secure WebSocket on port 8084 with the same TLS verification:

client = mqtt.Client(
    mqtt.CallbackAPIVersion.VERSION2,
    client_id=f"wss-{uuid.uuid4().hex[:8]}",
    transport="websockets",
)
client.ws_set_options(path="/mqtt")
client.tls_set()
client.connect("broker.connectmq.com", 8084, keepalive=60)

Everything else, callbacks, subscriptions and publishing, stays the same. The MQTT over WebSocket article explains when this transport makes sense.

Fixing Common TLS Errors

Most TLS failures come down to a handful of causes. Match your error message to this table:

Error Likely cause Fix
CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate Python cannot find trusted CAs See the CA store fixes below
Hostname mismatch, certificate is not valid for '...' You connected by IP address or an alias Connect with the exact broker host name
certificate is not yet valid or certificate has expired Device clock is wrong Sync time with NTP before connecting
WRONG_VERSION_NUMBER TLS client talking to plain port 1883 Use port 8883
Connection hangs, then times out Plain client talking to TLS port 8883, or port blocked Call tls_set(), or try port 8084

Fixing a missing CA store

  • macOS with Python from python.org: that Python ships without access to the system keychain. Run the "Install Certificates.command" script in the Python application folder, or point paho at certifi: pip install certifi, then client.tls_set(ca_certs=certifi.where()).
  • Slim Docker images: install the CA bundle, for example apt-get install -y ca-certificates on Debian-based images.
  • Corporate networks with TLS inspection: a proxy may replace certificates with its own. Ask IT for the corporate root CA and pass it with ca_certs= rather than disabling verification.
  • Embedded Linux: update the CA bundle; very old images may not include the ISRG Root X1 certificate that Let's Encrypt chains to.

Never "fix" TLS by turning verification off

You will find advice to call client.tls_insecure_set(True) or pass cert_reqs=ssl.CERT_NONE. Both make the error disappear by accepting any certificate from anyone, which silently removes server authentication. Use them only for a quick diagnostic on your own lab network, never in deployed code.

Also avoid pinning the broker's leaf certificate in your client. Let's Encrypt certificates are renewed regularly, so a pinned certificate will break on the next renewal. Trust the CA instead, which is what the default store does.

Production Checklist

Before deploying a Python client that uses TLS, confirm each item:

  1. Port 8883 (or 8084 with WebSocket) is used everywhere; nothing falls back to 1883.
  2. tls_set() or tls_set_context() is called without tls_insecure_set(True).
  3. The client connects by host name, never by IP address.
  4. The device keeps accurate time, synced by NTP at boot.
  5. The CA bundle on the device is maintained and updated with the OS.
  6. Credentials come from environment variables or a secrets store.
  7. Reconnect logic is in place; the paho-mqtt reconnect guide shows a robust pattern.

Where to Go Next

If you are new to paho, the paho mqtt python tutorial covers publish and subscribe basics, and the aiomqtt and asyncio guide shows how to pass a tls_context to an async client. For clients in other languages, the MQTT client libraries overview compares TLS support across ecosystems. The Python ssl module documentation is the reference for every context option used here.

Secure Your MQTT Traffic with ConnectMQ

ConnectMQ is a fully managed MQTT service with TLS on 8883 and secure WebSocket on 8084 enabled out of the box, backed by an automatically managed Let's Encrypt certificate, so your Python clients verify the broker with the CA store they already have. Test on the public MQTT test broker, then move to a private cloud MQTT broker with generated credentials. Create your free broker on the Serverless plan, no credit card required.

Frequently Asked Questions

Do I need to download a CA certificate to connect with TLS?

No. The broker uses a Let's Encrypt certificate that chains to a root trusted by current operating systems, so tls_set() with the default CA store works.

What port does MQTT over TLS use?

The standard port is 8883. ConnectMQ also offers MQTT over secure WebSocket on port 8084 with the path /mqtt.

Why does python mqtt tls fail when I connect by IP address?

Hostname verification compares the certificate's name with the host you connected to. An IP address does not match the broker's name, so the check fails by design; use the host name.

Is TLS enough to secure my MQTT client?

TLS protects the connection, but you still need strong, per-device credentials and careful topic design. Combine TLS with unique credentials from the broker portal.

Related articles

Best MQTT CLI Tools for Testing, Scripting and Debugging

A hands-on comparison of mosquitto_pub/sub, MQTTX CLI, HiveMQ MQTT CLI, the MQTT.js CLI, curl and mqttui, with the same test run against broker.connectmq.com so you can choose the right tool for scripting and debugging.

8 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.