Tutorials

MQTT über WebSockets in Python

paho-mqtt über WebSockets verbinden: transport="websockets", wss mit TLS, Pfad und Header per ws_set_options(), HTTP-Proxys und Lösungen für Handshake-Fehler.

Aktualisiert · 8 Min. Lesezeit

MQTT über WebSockets in Python

Für MQTT über WebSockets in Python erzeugst du den paho-Client mit transport="websockets", verbindest dich mit dem WebSocket-Port des Brokers und setzt bei Bedarf den URL-Pfad mit ws_set_options(). Für verschlüsseltes wss kommen tls_set() und der Secure-WebSocket-Port dazu. Ansonsten ist die MQTT-API identisch: dieselben Callbacks, dasselbe publish() und subscribe().

Dieser Guide zeigt ws- und wss-Verbindungen, eigene Pfade und Header, Verbindungen über HTTP- und SOCKS-Proxys und wie du die Handshake-Fehler von paho debuggst.

Wann WebSockets aus Python sinnvoll sind

Browser können MQTT nur über WebSockets sprechen, Python dagegen kann reines TCP nutzen – einfacher und etwas schlanker. Nimm WebSockets in Python, wenn:

  • dein Netzwerk nur ausgehendes HTTP(S) erlaubt, etwa eine Firmen-Firewall oder ein Hotel-WLAN, das 1883 und 8883 blockiert (siehe MQTT hinter einer Firmen-Firewall).
  • der Broker hinter einem Reverse Proxy oder Load Balancer steht, der nur HTTP routet, etwa Nginx oder ein Cloud-Ingress.
  • ein Backend-Skript exakt denselben Endpunkt wie dein Web-Dashboard nutzen soll, um ein Browserproblem nachzustellen.

Trifft nichts davon zu, ist TCP auf 1883/8883 die Standardwahl. Den Overhead-Unterschied misst TCP vs. WebSocket Performance.

Unverschlüsselte WebSockets (ws://)

ws_client.py
import time

import paho.mqtt.client as mqtt


def on_connect(client, userdata, flags, reason_code, properties):
    print("CONNACK:", reason_code)
    client.subscribe("testmqtt/py-ws/#")


def on_message(client, userdata, msg):
    print(msg.topic, msg.payload.decode())


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, transport="websockets")
client.ws_set_options(path="/mqtt")  # /mqtt ist auch der Standard von paho
client.on_connect = on_connect
client.on_message = on_message

client.connect("broker.connectmq.com", 8083, keepalive=60)
client.loop_start()
client.publish("testmqtt/py-ws/hello", "hello over ws", qos=1)
time.sleep(2)
client.loop_stop()
client.disconnect()

transport muss im Konstruktor gesetzt werden; einen bestehenden Client kannst du nicht von TCP auf WebSockets umstellen. Gültige Werte sind "tcp", "websockets" und "unix"; alles andere wirft ValueError: transport must be "websockets", "tcp" or "unix", not ....

Secure WebSockets (wss://)

Für alles jenseits eines schnellen Tests nutzt du wss. Derselbe Code plus tls_set(), der sichere Port und echte Zugangsdaten:

wss_client.py
import os

import paho.mqtt.client as mqtt

client = mqtt.Client(
    mqtt.CallbackAPIVersion.VERSION2,
    client_id="py-wss-demo",
    transport="websockets",
    protocol=mqtt.MQTTv5,
)
client.username_pw_set(os.environ["MQTT_USER"], os.environ["MQTT_PASS"])
client.tls_set()                     # prüft Zertifikat und Hostname
client.ws_set_options(path="/mqtt")

client.on_connect = lambda c, u, f, rc, p: print("CONNACK:", rc)
client.connect("broker.connectmq.com", 8084, keepalive=60)
client.loop_forever()

paho führt zuerst den TLS-Handshake aus, dann das HTTP-Upgrade im verschlüsselten Kanal und danach das MQTT-CONNECT. Alle TLS-Optionen und -Fehler aus MQTT über TLS in Python gelten unverändert.

Pfade und eigene Header

Der WebSocket-Pfad muss zu dem passen, was Broker oder Proxy erwarten, und einen universellen Standard gibt es nicht. /mqtt ist am verbreitetsten (Standard bei EMQX, HiveMQ und vielen Managed Brokern), das Web-MQTT-Plugin von RabbitMQ nutzt /ws, und Reverse Proxys können jeden beliebigen Pfad abbilden. Ein falscher Pfad ist die häufigste Ursache für WebSocket-Verbindungsfehler; siehe WebSocket-Pfad 404.

ws_set_options() akzeptiert außerdem Header – entweder als Dict, das mit den Standards zusammengeführt wird, oder als Funktion, die die Standard-Header erhält und den finalen Satz zurückgibt:

ws_headers.py
import os

TOKEN = os.environ["GATEWAY_TOKEN"]

def add_headers(headers):
    headers["Authorization"] = f"Bearer {TOKEN}"   # für einen Auth-Proxy vor dem Broker
    headers["Origin"] = "https://dashboard.example.com"
    return headers

client.ws_set_options(path="/mqtt", headers=add_headers)

Standardmäßig sendet paho Origin: http://host:port (bzw. https:// mit TLS) und Sec-WebSocket-Protocol: mqtt. Origin zu überschreiben ist wichtig, wenn ein Proxy eine Allow-List erzwingt – Thema von CORS und WebSocket-Origin-Prüfung. Denk daran: MQTT-Zugangsdaten gehören weiterhin per username_pw_set() ins CONNECT-Paket; Header sind nur für HTTP-Komponenten vor dem Broker gedacht.

Verbindung über einen HTTP- oder SOCKS-Proxy

paho tunnelt über Proxys mit der Bibliothek PySocks. Installiere sie zuerst mit pip install pysocks und konfiguriere den Proxy dann explizit:

ws_via_proxy.py
import socks  # aus dem Paket PySocks

import paho.mqtt.client as mqtt

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, transport="websockets")
client.tls_set()
client.proxy_set(proxy_type=socks.HTTP, proxy_addr="proxy.corp.local", proxy_port=3128)
client.connect("broker.connectmq.com", 8084)
client.loop_forever()

Mit socks.HTTP öffnet PySocks einen HTTP-CONNECT-Tunnel zu broker.connectmq.com:8084, und paho fährt TLS, das WebSocket-Upgrade und MQTT hindurch. Für einen SOCKS-Proxy nimmst du socks.SOCKS5 (optional mit proxy_username / proxy_password).

Zwei Details sparen viel Zeit:

  • paho ignoriert HTTPS_PROXY und HTTP_PROXY. Die einzige Umgebungsvariable, die es liest, ist mqtt_proxy, zum Beispiel export mqtt_proxy=http://proxy.corp.local:3128 oder socks://host:1080 – und das nur mit installiertem PySocks. Hosts in no_proxy werden direkt angesprochen.
  • Viele Firmen-Proxys erlauben CONNECT nur auf Port 443. Wird der Tunnel abgelehnt, brauchst du einen Broker-Endpunkt auf 443 oder eine Proxy-Ausnahme.

Handshake-Fehler und ihre Lösung

WebSocket-Probleme erscheinen als WebsocketConnectionError, eine Unterklasse von ConnectionError. Mit connect() wird sie sofort geworfen; mit connect_async() plus loop_start() versucht paho es still erneut – aktiviere beim Debuggen also client.enable_logger().

FehlerWahrscheinliche UrsacheLösung
WebSocket handshake error, connection not upgradedHTTP-Antwort ohne Upgrade: falscher Pfad (404), normaler Webserver, Fehlerseite des ProxysPfad prüfen; sicherstellen, dass Host und Port auf den WebSocket-Listener zeigen
WebSocket handshake errorVerbindung während des Upgrades geschlossen oder Nicht-HTTP-Antwort (oft unverschlüsseltes ws gegen einen TLS-Port)tls_set() mit wss-Ports nutzen; prüfen, ob der Port ein WebSocket-Listener ist
WebSocket handshake error, invalid secret keyEin Proxy oder Server hat ein falsches Sec-WebSocket-Accept geliefertDen abfangenden Proxy umgehen oder reparieren
[SSL: WRONG_VERSION_NUMBER] wrong version numbertls_set() gegen einen unverschlüsselten ws-Port wie 80838084 für wss nutzen oder tls_set() weglassen
ConnectionRefusedError: [Errno 61] Connection refused (111 unter Linux)Auf dem Port lauscht nichts, oder eine Firewall weist abPort prüfen; aus einem anderen Netz testen
CONNACK Not authorized / Bad user name or passwordWebSocket funktioniert, MQTT-Authentifizierung schlägt fehlZugangsdaten und ACLs prüfen

Die MQTT-Reason-Codes aus der letzten Zeile schlägst du im Reason-Code-Nachschlagewerk nach.

So testest du die Verbindung

Prüfe zuerst das HTTP-Upgrade ohne Python. Ein gesunder WebSocket-Endpunkt antwortet mit 101 Switching Protocols:

WebSocket-Upgrade mit curl testen
curl -i -N --max-time 5 \
  -H "Connection: Upgrade" -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  -H "Sec-WebSocket-Protocol: mqtt" \
  https://broker.connectmq.com:8084/mqtt

Ein 404 heißt falscher Pfad, ein 400 oder 426 meist ein entfernter Header, und ein TLS-Fehler bedeutet, dass du mit dem falschen Port sprichst. Danach der Ende-zu-Ende-Check: Öffne den Online-MQTT-Client, verbinde dich per wss mit demselben Broker, abonniere testmqtt/py-ws/# und starte ws_client.py. Kommt die Nachricht im Browser an, stimmen Python, Pfad und Broker. Weitere Checks, auch für Proxys und Load Balancer, findest du unter eine MQTT-WebSocket-Verbindung testen.

Im Browser abonnieren, aus Python publizieren

Alternative: aiomqtt

Ist dein Code asyncio-basiert, bietet aiomqtt (das paho kapselt) dieselben Optionen als Konstruktor-Argumente:

aiomqtt_ws.py
import asyncio
import ssl

import aiomqtt


async def main():
    async with aiomqtt.Client(
        "broker.connectmq.com",
        8084,
        transport="websockets",
        websocket_path="/mqtt",
        tls_context=ssl.create_default_context(),
    ) as client:
        await client.subscribe("testmqtt/py-ws/#")
        await client.publish("testmqtt/py-ws/hello", "hello from aiomqtt")
        async for message in client.messages:
            print(message.topic, message.payload)
            break


asyncio.run(main())

MQTT über WebSockets in Python ist ein Konstruktor-Argument plus der richtige Port und Pfad. Nutze wss mit aktiver Prüfung, setze den Pfad explizit, damit niemand raten muss, konfiguriere Proxys mit proxy_set() oder mqtt_proxy und teste das Upgrade mit curl, bevor du deinem Code die Schuld gibst.

Häufig gestellte Fragen

Welchen Port und Pfad nutzt paho-mqtt für WebSockets?

paho nutzt den Port, den du an connect() übergibst, und standardmäßig den Pfad /mqtt. Unverschlüsselte WebSockets liegen meist auf 8083 oder 80, Secure WebSockets auf 8084 oder 443. Den Pfad änderst du mit client.ws_set_options(path=...), falls dein Broker oder Proxy einen anderen nutzt.

Beachtet paho-mqtt HTTPS_PROXY oder HTTP_PROXY?

Nein. paho liest nur eine Umgebungsvariable namens mqtt_proxy (mit einer http://- oder socks://-URL) und auch nur, wenn PySocks installiert ist. Ansonsten konfigurierst du den Proxy explizit mit client.proxy_set(proxy_type=socks.HTTP, proxy_addr=..., proxy_port=...).

Was bedeutet WebSocket handshake error, connection not upgraded?

Der Server hat auf den HTTP-Upgrade-Request geantwortet, aber nicht auf WebSocket umgeschaltet. Meist ist der Pfad falsch (eine 404-Seite), du landest bei einem normalen Webserver oder Proxy statt beim WebSocket-Listener des Brokers, oder ein Proxy hat den Upgrade-Header entfernt.