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://)
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:
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:
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:
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_PROXYundHTTP_PROXY. Die einzige Umgebungsvariable, die es liest, istmqtt_proxy, zum Beispielexport mqtt_proxy=http://proxy.corp.local:3128odersocks://host:1080– und das nur mit installiertem PySocks. Hosts inno_proxywerden direkt angesprochen. - Viele Firmen-Proxys erlauben
CONNECTnur 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().
| Fehler | Wahrscheinliche Ursache | Lösung |
|---|---|---|
WebSocket handshake error, connection not upgraded | HTTP-Antwort ohne Upgrade: falscher Pfad (404), normaler Webserver, Fehlerseite des Proxys | Pfad prüfen; sicherstellen, dass Host und Port auf den WebSocket-Listener zeigen |
WebSocket handshake error | Verbindung 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 key | Ein Proxy oder Server hat ein falsches Sec-WebSocket-Accept geliefert | Den abfangenden Proxy umgehen oder reparieren |
[SSL: WRONG_VERSION_NUMBER] wrong version number | tls_set() gegen einen unverschlüsselten ws-Port wie 8083 | 8084 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 ab | Port prüfen; aus einem anderen Netz testen |
CONNACK Not authorized / Bad user name or password | WebSocket funktioniert, MQTT-Authentifizierung schlägt fehl | Zugangsdaten 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:
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/mqttEin 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:
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.


