Mosquitto ignores bind_interface on a websockets listener

I put an MQTT web client in front of Mosquitto and tried to keep the WebSocket port cluster-only. bind_interface does nothing for websockets listeners in 2.0.22, an interface name as host hangs the broker, and a pod IP rendered by an init container is what works.

I wanted a browser MQTT client for the cluster’s Mosquitto. Browser clients can only speak MQTT over WebSocket, so the broker needed a second listener, and the decision was that this listener stays inside the cluster. The broker also sits on the LAN through a macvlan interface, and I did not want a WebSocket port open there, on a broker that allows anonymous access.

What I expected

bind_interface exists in Mosquitto’s config and restricts a listener to one network interface. I set it to the pod’s own interface, deployed, and called it done.

What happened

The UI worked, and so did the secure WebSocket through the ingress. Then I checked the LAN address and port 9001 answered there too. Reading /proc/net/tcp inside the pod showed why: the socket was bound to 0.0.0.0. In Mosquitto 2.0.22, bind_interface has no effect on a protocol websockets listener, though it works for the plain MQTT one.

Two things that don’t work

Putting the interface name where the address goes, listener 9001 eth0, looks plausible and is far worse. The broker logs “Opening websockets listen socket”, never logs that it is running, and every handshake times out. I would not deploy that.

What does work

listener 9001 <IP address> does restrict a websockets listener. I tested it with two Docker networks: the handshake succeeds on the bound address, gets no answer on the other, and port 1883 is unaffected. The problem is that a pod does not know its address when you write the ConfigMap.

The fix is a template in the ConfigMap, with a placeholder such as listener 9001 POD_IP, and an init container that renders it into a config in an emptyDir using the downward API value status.podIP. Mosquitto reads the rendered file. After the change 9001 was bound to the pod address only, the secure WebSocket returned 101, the LAN address no longer answered, and 1883 was untouched.

Smaller facts from the same job

  • Without per_listener_settings, allow_anonymous true applies to the new listener as well. Convenient, and a reason to keep the port private.
  • Mosquitto ignores the URL path on a websockets listener, so the ingress rule decides it. /mqtt is the default in MQTTX.
  • The MQTTX Web image is a Node image running http-server as root on port 80. Override the command to use port 8080 and it runs as an unprivileged user with a read-only root filesystem.
  • MQTTX Web keeps connections in the browser. There is no way to pre-seed a default one from the deployment.