Skip to main content

mqtt-proxy

The mqtt-proxy plugin validates the protocol name and level in an MQTT CONNECT packet before proxying the TCP connection to an MQTT broker. It supports MQTT 3.1.x and MQTT 5.0 and is configured on a stream route.

The plugin also exposes the MQTT client ID as mqtt_client_id. A consistent-hash upstream can use this variable to keep connections from the same client ID on the same broker.

Examples​

The examples configure a stream listener and two Eclipse Mosquitto brokers before proxying MQTT 3.1.1 and MQTT 5 traffic. The final example adds consistent hashing to keep connections with the same client ID on the same broker.

Configure the Stream Listener​

The gateway must listen for stream traffic on the port used by the stream route.

Add the stream listener to config.yaml:

config.yaml
apisix:
proxy_mode: http&stream
stream_proxy:
tcp:
- 9100

Reload the gateway after saving the configuration.

Start the Example MQTT Brokers​

The runnable examples use Docker or Kubernetes. Both workflows start two Eclipse Mosquitto brokers in the same environment as the gateway.

Create the broker configuration:

mosquitto.conf
listener 1883 0.0.0.0
allow_anonymous true
persistence false
log_dest stdout

Set GATEWAY_CONTAINER to the running APISIX or API7 Gateway container:

export GATEWAY_CONTAINER=replace-with-gateway-container-name

Create a dedicated network and connect the gateway:

docker network create gateway-mqtt-proxy-net
docker network connect gateway-mqtt-proxy-net "$GATEWAY_CONTAINER"

Start two brokers on the same network. The network aliases match the Kubernetes Service addresses used below, so the same route configuration works in both environments:

docker run -d \
--name mqtt-broker-1 \
--network gateway-mqtt-proxy-net \
--network-alias mqtt-broker-1.aic.svc.cluster.local \
-v "$PWD/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro" \
eclipse-mosquitto:2.1.2-alpine

docker run -d \
--name mqtt-broker-2 \
--network gateway-mqtt-proxy-net \
--network-alias mqtt-broker-2.aic.svc.cluster.local \
-v "$PWD/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro" \
eclipse-mosquitto:2.1.2-alpine

The fixture permits anonymous connections without encryption for evaluation. Production deployments should add authentication, encryption, and network controls appropriate to their topology. Because mqtt-proxy inspects the MQTT CONNECT packet, it cannot process connections that use TLS passthrough.

Proxy MQTT 3.1.1 Traffic​

The following example configures a stream route on port 9100 to accept MQTT 3.1.1 connections and proxy them to the configured broker backend.

For APISIX, create the stream route with the Admin API:

curl "http://127.0.0.1:9180/apisix/admin/stream_routes/mqtt-route" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
"server_port": 9100,
"plugins": {
"mqtt-proxy": {
"protocol_name": "MQTT",
"protocol_level": 4
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"mqtt-broker-1.aic.svc.cluster.local:1883": 1
}
}
}
EOF

Open a terminal and subscribe directly to the first broker:

docker exec mqtt-broker-1 \
mosquitto_sub -h 127.0.0.1 -p 1883 \
-V mqttv311 -t docs/mqtt

In another terminal, publish through the gateway listener:

docker exec mqtt-broker-1 \
mosquitto_pub -h "$GATEWAY_CONTAINER" -p 9100 \
-V mqttv311 -t docs/mqtt -m "Hello through the gateway"

The subscriber should print Hello through the gateway.

Proxy MQTT 5 Traffic​

The following example configures a stream route with the protocol name MQTT and protocol level 5 to accept MQTT 5 CONNECT packets and proxy their messages through the gateway. Connections using other protocol levels are rejected.

For APISIX, create the stream route with the Admin API:

curl "http://127.0.0.1:9180/apisix/admin/stream_routes/mqtt-route" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
"server_port": 9100,
"plugins": {
"mqtt-proxy": {
"protocol_name": "MQTT",
"protocol_level": 5
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"mqtt-broker-1.aic.svc.cluster.local:1883": 1
}
}
}
EOF

Subscribe with an MQTT 5 client:

docker exec mqtt-broker-1 \
mosquitto_sub -h 127.0.0.1 -p 1883 \
-V mqttv5 -t docs/mqtt-v5

Publish an MQTT 5 message through the gateway:

docker exec mqtt-broker-1 \
mosquitto_pub -h "$GATEWAY_CONTAINER" -p 9100 \
-V mqttv5 -t docs/mqtt-v5 -m "Hello MQTT 5"

The subscriber should print Hello MQTT 5.

Load Balance by MQTT Client ID​

The following example configures a consistent-hash upstream using mqtt_client_id so connections from the same client continue to reach the same broker. It uses MQTT 5 traffic to verify the affinity.

When a client sends an empty ID, the gateway falls back to the client IP for consistent hashing.

For APISIX, create the stream route with the Admin API:

curl "http://127.0.0.1:9180/apisix/admin/stream_routes/mqtt-route" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
"server_port": 9100,
"plugins": {
"mqtt-proxy": {
"protocol_name": "MQTT",
"protocol_level": 5
}
},
"upstream": {
"type": "chash",
"key": "mqtt_client_id",
"nodes": {
"mqtt-broker-1.aic.svc.cluster.local:1883": 1,
"mqtt-broker-2.aic.svc.cluster.local:1883": 1
}
}
}
EOF

Subscribe directly to each broker in separate terminals:

docker exec mqtt-broker-1 \
mosquitto_sub -h 127.0.0.1 -p 1883 \
-V mqttv5 -t docs/balance

In the second terminal, subscribe to the other broker:

docker exec mqtt-broker-2 \
mosquitto_sub -h 127.0.0.1 -p 1883 \
-V mqttv5 -t docs/balance

Publish through the gateway with two client IDs:

docker exec mqtt-broker-1 \
mosquitto_pub -h "$GATEWAY_CONTAINER" -p 9100 \
-V mqttv5 -i publisher-1 \
-t docs/balance -m "from publisher-1"

docker exec mqtt-broker-1 \
mosquitto_pub -h "$GATEWAY_CONTAINER" -p 9100 \
-V mqttv5 -i publisher-2 \
-t docs/balance -m "from publisher-2"

The messages can reach the same or different brokers. Repeat either publish command to verify that the same client ID continues to reach the same broker while the upstream set remains unchanged.