Mosquitto Persistence: Define What Survives a Broker Restart After an Acknowledgement
In Mosquitto 2.0.20, enabling persistence does not make every MQTT acknowledgement a synchronous database checkpoint. The broker can acknowledge a retained QoS 1 publication while its newest state is still only in memory. Define restart expectations from persistence configuration and observed checkpoint behavior, then test orderly exit and abrupt process termination separately.
This guide pins Eclipse Mosquitto 2.0.20, its configuration manual and persistence implementation. Three real loopback broker experiments were executed with synthetic retained values. They are process-restart tests, not power-loss, storage-endurance or downstream database tests.
Inventory the effective persistence configuration
| Setting | Meaning in the pinned release | Evidence to retain |
|---|---|---|
| persistence | Enables on-disk connection, subscription and message state; default false | Effective loaded configuration |
| persistence_location | Directory for the database; otherwise current directory | Resolved path, service ownership and mount |
| persistence_file | Database filename; default mosquitto.db | Actual file and restart selection |
| autosave_interval | Time interval, or change threshold under the corresponding mode | Value and autosave_on_changes together |
| autosave_on_changes | Selects change counting rather than elapsed seconds | Whether time-based reasoning is applicable |
Do not inspect one configuration fragment and assume it is the file used by the service. Record the process arguments, included configuration and broker version. Check that the service can create the database in the intended directory and that it restarts from the same location.

Interpret autosave settings together
The 2.0.20 manual gives a default autosave interval of 1,800 seconds. With time-based autosave and an interval of zero, saving occurs at broker exit or in response to SIGUSR1 rather than periodically.
With autosave_on_changes true, the broker counts relevant subscription changes, retained messages and queued messages; the interval becomes a change threshold. Treating that number as seconds produces a false loss-window estimate.
Even a configured periodic interval is not an application promise that every acknowledged message survives a host failure within a precise duration. Scheduling, failed writes and storage behavior still matter. Establish a product requirement and measure the relevant boundary instead of turning an option name into a durability guarantee.
What the source establishes
The persistence path serializes selected in-memory state into a temporary database file, attempts to flush and synchronize it on the non-Windows path, closes it and renames it into place. This is a checkpoint operation distinct from the per-publication acknowledgement observed by a client.
That source ordering helps explain the restart experiments, but it is not proof of every underlying filesystem or device’s power-loss behavior. File creation, a “saving” log line, signal delivery and durable hardware completion are different observations.
Keep checkpoint errors visible. A configured persistence path that is unwritable, full or backed by an unexpected volatile mount can invalidate the intended recovery contract even while ordinary MQTT traffic continues.
Executed three-case campaign
The broker and command-line clients were built from the official 2.0.20 tag using GCC 14.2. TLS, WebSockets, dynamic plugins and documentation builds were disabled. Each case used a fresh directory, a loopback-only listener, persistence enabled, autosave_interval zero and autosave_on_changes false. No production broker, credential or external endpoint was involved.
For each case, the client published one synthetic retained value at QoS 1 and its debug output confirmed PUBACK. The harness then applied the specified process action, restarted the broker with the same configuration and subscribed to the test topic.
| Process action after PUBACK | Database after stop | Restart observation |
|---|---|---|
| Orderly termination | Present | Expected retained value received |
| SIGKILL before any checkpoint | Absent | No retained value during one-second subscriber wait |
| SIGUSR1, wait for database creation, then SIGKILL | Present | Expected retained value received |
All three cases received PUBACK before their stop action. The difference was whether the new state reached a checkpoint before abrupt termination. The checkpoint case waited for the resulting database file, rather than assuming that sending SIGUSR1 meant saving had completed.
Do not overgeneralize the retained-message result
The campaign tested one retained topic in fresh broker instances. It did not establish recovery of persistent-client subscriptions, queued offline messages, every QoS handshake phase, expired messages or large databases.
Add those cases according to the actual product requirement. Session settings and message expiry can legitimately remove state independently of checkpoint timing. Receiving a retained value after restart also does not prove that a downstream consumer committed it.
SIGKILL removes the broker process while the operating system and storage remain running. A power-loss campaign must use the real filesystem, storage hardware and safe test equipment, with an independent record of published and recovered identities. Do not relabel the process test as a power-cut result.
Build a release acceptance matrix
- Record exact broker build, persistence settings, database path and service ownership.
- Confirm retained-state recovery after an orderly restart.
- Repeat abrupt process termination before and after an observed checkpoint.
- Exercise the persistent Session and queued-message states the product actually depends on.
- Inject controlled storage failures and verify error visibility without claiming protected acceptance.
- Qualify power loss separately if the product promises survival of that event.
Keep compatible database backups when changing broker versions; the manual notes that persistence format can change and that newer writes use the current format. Test rollback rather than assuming an older binary can read a newly written database.
The useful deliverable is a precise recovery claim: which state, under which settings, survives which tested interruption. The executed evidence shows why a PUBACK and a persistence checkpoint must remain separate entries in that claim.