Configuration
topics is configured entirely from the environment — there is no config file. This
page is the complete reference: every TOPICS_* variable plus RUST_LOG, with its exact
default and what it controls. Unset variables fall back to the defaults shown here.
Every TOPICS_MAX_* limit accepts 0 to mean unlimited. Exceeding a configured cap
returns 429 throttled with the offending bound in error.detail.limit. See
Resource limits below and Errors.
Networking & auth
Where the server binds, whether auth is on, and which keys are accepted. The default bind is loopback, so an unconfigured server is never accidentally public.
| Env var | Default | Meaning |
|---|---|---|
TOPICS_HOST | 127.0.0.1 | Bind host (loopback by default). May also be a full host:port — in which case it carries the port and TOPICS_PORT is ignored. |
TOPICS_PORT | 4000 | Listen port. Used unless TOPICS_HOST is a full address. |
TOPICS_API_KEYS | (unset) | Comma-separated bearer keys, hashed SHA-256 at rest. Each entry is key | key:scopes | key:scopes:prefixes | key::prefixes. Unset ⇒ auth disabled (dev mode). |
TOPICS_ALLOW_INSECURE_NO_AUTH | 0 | Required to start on a non-loopback bind with no keys — otherwise the server refuses to start. Local/dev only. |
TOPICS_PROBE_AUTH | false | When true, require auth on the /v0/health, /v0/ready, and /v0/metrics probes too. By default probes skip auth. |
On a non-loopback bind with no TOPICS_API_KEYS, the server refuses to start unless
TOPICS_ALLOW_INSECURE_NO_AUTH=1 — it would otherwise be an open, unauthenticated event
store. Never set that flag on a network-exposed deployment. The key entry syntax (scopes
and the topic-name prefix allowlist) is documented in full on the
Security page.
A bare key grants full access. The scopes field draws from
read/write/delete/admin (single-letter aliases r/w/d/a, plus rw); an empty
scopes field means all scopes. The prefixes field is a topic-name prefix allowlist. A
malformed scope token makes the server refuse to start (fail-closed). See
Security for the per-route scope requirements and how the allowlist
is enforced on paths, request bodies, and list results.
Storage & tiering
Where durable state lives, when segments are sealed, and the optional cold tier.
TOPICS_DATA_DIR is the hot-tier root; the segment-sealing knobs control segment
rotation; the hot-retention knobs govern when sealed segments relocate to cold storage.
| Env var | Default | Meaning |
|---|---|---|
TOPICS_DATA_DIR | ./topics-data | Root for the WAL, segments, and snapshots (the hot tier). Replayed on start; a missing/empty dir is a fresh start. |
TOPICS_WAL_SHARDS | min(num_cpus, 8) | Number of independent WAL shards (each its own writer thread, file set, and group-commit loop). Each topic maps to one shard by a stable hash of its id, so per-topic ordering + durability still hold; recovery is shard-count-agnostic (replays all shards by topic_id), so this may change between restarts. 1 = the flat single-writer layout. |
TOPICS_COLD_DIR | (unset) | Cold-tier directory. Set ⇒ sealed segments past the hot-retention bound relocate here off the hot path. Unset ⇒ tiering disabled (everything stays hot). |
TOPICS_SEGMENT_MAX_EVENTS | 10000 | Seal the active segment after this many records. |
TOPICS_SEGMENT_MAX_BYTES | 67108864 (64 MiB) | Seal after this many .data bytes. |
TOPICS_SEGMENT_MAX_AGE_MS | 3600000 (1 h) | Seal an idle/partial segment after this age. 0 disables age-based sealing. |
TOPICS_HOT_RETAIN_SEGMENTS | 4 | Number of newest sealed segments kept hot before relocating older ones to cold. |
TOPICS_HOT_RETAIN_BYTES | 0 (off) | Optional hot sealed-byte bound. When both this and TOPICS_HOT_RETAIN_SEGMENTS are set, the stricter of the two wins. |
Tiering is opt-in: with TOPICS_COLD_DIR unset, every segment stays in the hot tier and
the retention knobs have no effect. Cold reads run on a separate I/O pool and never block
writes or live SSE delivery. See Storage & Tiering.
Resource limits
DoS-hardening caps. Each is a guardrail with a sane default; 0 means unlimited, and
crossing a cap returns 429 throttled carrying the bound in error.detail.limit.
| Env var | Default | Meaning |
|---|---|---|
TOPICS_MAX_BODY_BYTES | 67108864 (64 MiB) | Maximum request body size. |
TOPICS_MAX_TOPICS | 100000 | Maximum number of topics. |
TOPICS_MAX_ROUTERS | 10000 | Maximum number of routers. |
TOPICS_MAX_WATCH_SESSIONS | 10000 | Maximum live watch sessions. |
TOPICS_MAX_SSE_CONNECTIONS | 10000 | Maximum concurrent SSE connections, server-wide. |
TOPICS_MAX_SSE_CONNECTIONS_PER_KEY | 1000 | Maximum concurrent SSE connections per key. |
TOPICS_MAX_INFLIGHT_PER_KEY | 1000 | Maximum concurrent in-flight requests per key. |
TOPICS_MAX_TOTAL_BYTES | 0 (unlimited) | Global quota on total retained record bytes across all topics. A write past it ⇒ 429 throttled. |
An over-cap request returns 429 throttled, distinct from the elastic throttling the
scheduler applies under CPU pressure (which adds detail.retry_after_ms instead). A cap
rejection is a hard limit; elastic throttling degrades latency, never correctness. See
Errors.
Logging
| Env var | Default | Meaning |
|---|---|---|
RUST_LOG | info | Tracing filter. Standard Rust tracing syntax (e.g. RUST_LOG=topics=debug,info) — not topics-specific. |
A worked example
A production-style invocation that binds a non-loopback address, turns auth on, points at a dedicated data directory with a cold tier, and tightens a couple of limits:
TOPICS_HOST=0.0.0.0 \
TOPICS_PORT=4000 \
TOPICS_API_KEYS="prod-key:rw:tenant42:,admin-key:a" \
TOPICS_DATA_DIR=/var/lib/topics \
TOPICS_COLD_DIR=/mnt/cold/topics \
TOPICS_MAX_TOPICS=50000 \
TOPICS_MAX_TOTAL_BYTES=10737418240 \
./target/release/topicsRun this behind a TLS-terminating reverse proxy — topics speaks plain HTTP. The same variables apply to the container image; see Docker.
See also
- Security — the key entry syntax, scopes, the prefix allowlist, and the resource model.
- Storage & Tiering — the data directory, segment sealing, and hot→cold relocation.
- Running topics — start, recovery, binding, and graceful shutdown.
- Docker — passing these variables to the published image.