Skip to content
WSLDeep Dive Published Updated 7 min readViews unavailable

Mosquitto in WSL: MQTT Sessions, QoS, and Persistence

Use Mosquitto in WSL to test MQTT topics, QoS, retained messages, sessions, and persistence without treating a workstation as an always-on IoT service.

Eclipse Mosquitto is a compact MQTT broker that works well as a local integration dependency for device simulators and application tests. WSL is useful when the client tools and application are Linux-based, but it introduces a service-lifecycle boundary: the distribution can stop, and Windows-to-WSL networking depends on the current WSL configuration. Keep the test broker local and do not treat its process state as an always-on IoT endpoint.

MQTT terms that sound similar represent different guarantees. QoS describes protocol delivery between a sender and receiver. Retained messages provide a latest value for a topic to future subscribers. Persistent sessions preserve subscription and eligible queued-delivery state across client disconnects. Mosquitto’s broker persistence setting controls whether broker state is written to its own persistence file. None of these by itself makes an application-side database update and MQTT acknowledgement atomic.

Install one broker and prove the process boundary

Use the current Ubuntu or Debian package for the selected WSL distro, or follow the official Mosquitto installation instructions. Record the broker and client versions. Before changing configuration, inspect the package’s active unit and config includes; a package can load one main configuration file plus a directory of fragments.

mosquitto -h
mosquitto_sub --help
systemctl status mosquitto --no-pager
sudo systemctl start mosquitto
systemctl is-active mosquitto

The -h option prints usage rather than starting a server. If the unit is absent, verify how the broker was installed. Do not start an unmanaged broker on the same port while the package service is running.

Run one subscriber and one publisher in separate WSL terminals using a topic reserved for the local test:

mosquitto_sub -h 127.0.0.1 -p 1883 -t 'lab/events' -q 1 -v
mosquitto_pub -h 127.0.0.1 -p 1883 -t 'lab/events' -q 1 -m '{"id":"local-1"}'

These commands assume the broker is configured to accept that local test client under the project’s chosen access policy. Package defaults and Mosquitto major-version behavior can differ. If the broker rejects a connection, inspect the loaded listener and authorization configuration rather than weakening the policy blindly. For a Windows-native client, test the specific Windows-to-WSL path separately from the in-distro loopback test.

Interpret QoS without overstating delivery

MQTT QoS 0 is at-most-once delivery, QoS 1 is at-least-once, and QoS 2 provides exactly-once protocol delivery for the relevant MQTT exchange. QoS 1 can produce duplicates, so consumers should tolerate duplicate application events. QoS 2 does not make a remote database write and the protocol exchange one atomic transaction. Test the actual side-effect boundary in the application.

QoS should be selected per message path. A telemetry stream that can be sampled or reconstructed may use different delivery expectations from a command that must be observed. Raising all messages to QoS 2 can add protocol work without solving application-level loss, duplication, or persistence requirements. Measure client behavior and broker load with the real payload rate.

Use a bounded subscriber test that records topic, payload identifier, and QoS. Disconnect the client at controlled points and observe whether the broker redelivers as expected. Do not infer end-to-end processing from the broker’s protocol acknowledgement alone.

Retained messages are snapshots, not a queue

A retained publication stores the latest retained value for its topic, which the broker can send to a later matching subscriber. Publishing a zero-length retained message clears the retained value for that topic. Retained state is useful for current device state or configuration snapshots, but it is not an append-only event log and does not preserve every prior value.

This distinction matters for tests. If a new subscriber immediately receives an old value, inspect retained state before concluding that the publisher sent a duplicate. Use a dedicated topic namespace for each test and deliberately clear retained values during cleanup. Avoid clearing a shared broker’s entire topic space as a convenience.

Persistent sessions serve a different problem: the client identity and session settings allow the broker to retain applicable subscription state and queue eligible messages while the client is disconnected, subject to protocol version and session-expiry policy. MQTT 5 makes session expiry explicit; MQTT 3.1.1 uses the clean-session flag model. A persistent session can consume broker storage and require explicit lifecycle cleanup. Verify the client’s protocol version, client identifier, clean-start/session-expiry settings, and the broker’s behavior as one test contract.

Configure broker persistence deliberately

Mosquitto’s built-in persistence option is false by default in the current configuration manual. When enabled, the broker writes connection, subscription, and message state to mosquitto.db at the configured persistence location; writes occur on close and at configured autosave intervals. If disabled, that state remains in memory. This broker file is distinct from a retained message or persistent MQTT client session: those are protocol/application concepts, while the option determines whether Mosquitto saves broker state across a process restart.

For a package-managed service, inspect the existing configuration before adding directives. A configuration fragment can express the intended behavior, but the final listener and authorization settings must match the installed package and project policy:

persistence true
persistence_location /var/lib/mosquitto/
autosave_interval 60

Confirm that the selected directory exists and is writable by the account that runs Mosquitto. Validate the broker configuration using the package’s documented test or startup method, then inspect logs after restart. The values above are an example for a local experiment, not a general durability objective. An autosave interval is not a guarantee that every acknowledged message has reached durable media.

Test a graceful stop and restart with disposable messages and sessions. Then separately test what a WSL shutdown or abrupt host interruption means for the local lab. Do not copy a live persistence file while Mosquitto is updating it and call the copy a verified backup. If state matters, use the broker’s supported shutdown path and keep a tested copy outside the distro VHDX.

Diagnose with protocol-level checks

Split failures into listener, route, authorization, topic filter, client identity, QoS, retained state, session state, and persistence. A broker that accepts TCP but receives no matching message may be healthy while the publisher is using a different topic or the subscriber has a mismatching wildcard. MQTT topic names are case-sensitive.

Use verbose client output and broker logs to determine what happened at the protocol boundary. A successful mosquitto_pub process exit does not prove that an application consumer processed or stored the payload. Add a consumer-side record or acknowledgement in the application test when that is the actual acceptance condition.

To make tests deterministic, assign each run a unique client ID and topic prefix, then clean only those resources. Reusing a persistent client identifier can resume an earlier session and make a clean test appear to contain unexpected queued messages. Reusing a retained topic can make an old state value look like newly generated traffic.

When testing reconnects, use a bounded outage and record the session settings before disconnecting. A clean-start client intentionally discards prior session state, while a resumed session asks the broker to continue an existing session according to the selected protocol version. Session expiry controls how long MQTT 5 session state may remain available; it does not guarantee that a local process or host stays online for that interval. Validate both the client flags and the broker’s saved state after a controlled restart.

For topic filters, remember that a wildcard subscription determines which publications match; it does not change the topic on the message. Test exact topics and wildcard filters independently, including a case where a similar-looking topic should not match. This catches common test mistakes where a broad filter receives stale retained state or where the publisher and subscriber use different prefixes.

Use client reconnect callbacks and broker logs to separate protocol resumption from application reconnection. The application should detect whether a session was present and decide whether it must resubscribe or reconstruct local state. A successful TCP reconnect alone says nothing about prior subscriptions, queued delivery, or retained values.

Respect the WSL runtime boundary

An enabled systemd service can start Mosquitto when its distro starts, but it cannot keep WSL running indefinitely. Host sleep, reboot, servicing, or manual wsl –shutdown terminates the local broker. Any local persistence file remains inside one distribution disk unless separately backed up. Do not use WSL as a substitute for an IoT broker that needs to remain continuously reachable.

Accept the local setup when a clean distro can start one known service, a same-distro client can publish and subscribe with the intended QoS, retained and session behavior match expectations, and a controlled restart produces the documented persistence outcome. If Windows applications consume the broker, repeat the acceptance test from Windows. Record broker version, config path, persistence directory, listener address, protocol version, and which data is disposable.

Related:

Sources:

Comments