zenOSmosis blog

By jzombie

Zero-Trust IoT Messaging: Dockerized Mosquitto and Cloudflare Tunnels

Dockerized Eclipse Mosquitto MQTT broker with outbound-only Cloudflare Tunnel and no inbound ports, for secure IoT messaging.

On this page

A Dockerized Eclipse Mosquitto (opens in new tab) MQTT (opens in new tab) broker integrated with cloudflared to establish an outbound-only, Zero-Trust tunnel for IoT messaging. For edge deployments that cannot expose inbound ports. Source: jzombie/docker-mqtt-mosquitto-cloudflare-tunnel (opens in new tab) .

Exposing port 1883 on a public server to run an MQTT broker is a liability. It invites automated scanners, demands constant firewall vigilance, and bets everything on the authentication layer holding.

The docker-mqtt-mosquitto-cloudflare-tunnel (opens in new tab) repository provides a structural fix: eliminate the inbound attack surface entirely.

The Outbound-Only Architecture

The deployment runs two containers in one Docker network: mosquitto (eclipse-mosquitto:latest) and cloudflared (cloudflare/cloudflared:latest).

The defining feature of the docker-compose.yml (opens in new tab) is what is missing: no published inbound host ports.

Instead, the cloudflared container runs tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN} to establish a persistent outbound connection to the Cloudflare (opens in new tab) edge via Cloudflare Tunnel (opens in new tab) . The origin stays closed and invisible to the public internet. Full setup, including Zero Trust dashboard steps with screenshots and .env configuration, is in the repo README (opens in new tab) .

Note the dashboard screenshots have not been re-verified recently and may have drifted, so treat the official docs (opens in new tab) as source of truth.

Edge TLS and Internal Resolution

Public routing and cryptography are offloaded to Cloudflare:

  • Edge TLS termination: Clients such as sensors, smart-home devices, and dashboards connect to https://<subdomain>.<hostname> on port 443. TLS terminates at Cloudflare’s edge.
  • Internal Docker DNS: Cloudflare forwards through the tunnel to the internal service mosquitto:9001. That hostname resolves via Docker DNS; it is not a public URL or public port.
  • Local WebSocket handshake: mosquitto.conf (opens in new tab) listens on 1883 (native MQTT, container-internal only) and 9001 with protocol websockets. The tunnel targets the WebSocket listener.

Result: high-throughput MQTT-over-WebSockets without opening anything inbound.

Is it Free?

Yes for this use case. Cloudflare Tunnel (opens in new tab) is included in the Cloudflare Zero Trust free tier (opens in new tab) at $0 for up to 50 users. That covers the tunnel connector, Zero Trust Network Access, and device client.

Note: the default mosquitto.conf on main uses allow_anonymous true. Treat it as a starting primitive, not a production policy. For hardening patterns such as per-user ACLs and retained-message handling, see the protected-no-wildcard branch (opens in new tab) and compare view (opens in new tab) .