Skip to content

Troubleshoot

Fanout validates its whole configuration before opening any listener, so a configuration refusal names the setting. Other startup errors can report storage, listener, or identity-provider failures; read the error before changing settings.

The one that surprises people is an unrecognised variable. Fanout rejects unknown FANOUT_-prefixed setting names, except platform-injected service-discovery names, so a typo stops the process instead of being ignored. That is deliberate; see configuration.

One startup refusal is not about configuration. If a compaction was interrupted and Fanout can neither finish nor undo it, recovery fails closed and the error names telemetry/COMPACTION.json.

That is deliberate. The marker lists the batches the compaction retired, and recovery must not guess whether the original batches or their replacement are authoritative. A wrong guess could delete the only surviving copy of some rows, so Fanout stops instead and leaves things where they are: the replacement is either staged under telemetry/compaction/ or already published as telemetry/parquet/batches/<output id>.batch, and the retired inputs sit under telemetry/parquet/batches/, named <id>.retired-<output id>.

A publish that failed partway through may already have removed some of those retired inputs, so the rollback set is not guaranteed to be complete. That is what step 2 below checks, and why it has to be checked before anything is deleted.

Usually the interruption was transient — a full disk, or the process killed mid-swap. Clear the underlying cause and start Fanout again. Recovery re-runs on its own and needs no manual step.

If recovery keeps failing, roll the compaction back by hand. With the process stopped, use telemetry/parquet/batches/ as the batch directory:

  1. Read telemetry/COMPACTION.json. It is JSON, with an output object and an inputs list of batch ids.
  2. Back up telemetry/, then check every id in inputs. Each must exist in exactly one form: <id>.batch (never retired) or <id>.retired-<output id> (retired, restorable). If an id exists in both forms or neither form, stop here. Both forms make the authoritative copy ambiguous; neither means its rows may survive only inside the replacement. Reconcile the batch from the backup, or complete the compaction instead of rolling it back.
  3. Rename each <id>.retired-<output id> back to <id>.batch.
  4. Delete telemetry/compaction/<output id> if it exists.
  5. Delete telemetry/parquet/batches/<output id>.batch if it exists. Leaving a published replacement beside the restored inputs duplicates its rows; a corrupt replacement continues to block startup.
  6. Delete telemetry/COMPACTION.json.
  7. Start Fanout.

Deleting the whole telemetry/ directory also clears the refusal, and discards exactly the telemetry the refusal was protecting. Neither step above needs it.

An unreadable telemetry batch blocks startup

Section titled “An unreadable telemetry batch blocks startup”

Fanout also refuses to start when an authoritative .batch directory has invalid metadata, a corrupt Parquet file, a missing signal file, or a damaged trace index. First stop Fanout and back up telemetry/, then verify the full authoritative set:

Terminal window
# Use the same environment and data directory as the server.
fanout repair verify

The command is read-only and reports confirmed corrupt batches. Unsupported formats and operational errors stop verification; they do not authorize quarantine. Restore each reported corrupt batch from backup when possible. If one cannot be recovered and discarding only its telemetry is preferable to leaving the instance offline, set it aside explicitly:

Terminal window
fanout repair quarantine --batch <id>

Quarantine is an atomic rename beside the authoritative batch set, not a deletion. It refuses a batch that validates successfully and refuses any input or output protected by a live compaction marker. Preserve the reported quarantine directory for later recovery, run repair verify again, and only then restart Fanout.

Work down this list in order:

  1. Has setup finished? Before the first administrator exists there is no ingest token to check against, and ingest rejects everything. A collector started ahead of setup fails until you complete it.
  2. Is it the right credential, in the right header? The ingest token is fo_-prefixed and goes in Authorization: Bearer. Browser sessions and MCP tokens are not ingest credentials.
  3. Is the listener reachable? Both OTLP transports use FANOUT_ADDR (127.0.0.1:7520 by default; the image sets :7520). Check container port mappings, bind address and firewall rules.
  4. Right protocol? Both use port 7520. gRPC requires HTTP/2 through any proxy. Use grpc or http/protobuf consistently with the exporter configuration; HTTP/1.1 proxy forwarding cannot carry gRPC.

For OTLP/HTTP, set the exporter protocol to http/protobuf and give it the base endpoint — http://fanout:7520. Exporters derive /v1/traces, /v1/metrics and /v1/logs from it themselves.

Someone cannot sign in and SMTP is not configured

Section titled “Someone cannot sign in and SMTP is not configured”

SMTP is optional. Creating a local user without it succeeds and reports invite_delivery: "not_configured" with login_link_required: true. An operator with shell access to the same configuration and data directory then mints a link directly:

Terminal window
fanout login-link [email protected]

The link expires after 15 minutes, works once, and is recorded in the authentication audit history. It is available in local auth mode only.

A configured SMTP relay that fails delivery is reported as an error rather than silently claiming an invitation arrived — so “no error” and “not configured” are distinguishable.

Check the namespace. Fanout reads service.namespace from the OTLP resource and falls back to FANOUT_DEFAULT_NAMESPACE, so telemetry exported without one lands in default.

An unscoped query spans every namespace, so this is only a problem when something has scoped the query — a dashboard filter, or an MCP call passing namespace. Clear the scope before concluding the data is missing.

Rollups also lag ingest by design — the alert engine and the overview read aggregates, so the newest few seconds of data are not immediately reflected in them even though the raw telemetry is queryable.

  • /healthz — liveness.
  • /readyz — storage readiness and resolved runtime sizing.
  • /metrics — Prometheus metrics. Keep it private or set FANOUT_METRICS_TOKEN. Do not make it public merely to simplify scraping.

Alert on repeated restarts, readiness failures, ingest authentication failures, telemetry drops, sustained query latency, and free disk space.