Configuration
Configure sepp via sepp.toml, SEPP_ environment overrides and the admin UI.
Sepp offers various ways of configuring itself to make it as easy to run as possible. This page goes over the different ways to configure sepp and how they interact with each other.
The sepp.toml file
This is the source of truth for the server configuration. Every edit you make through the admin UI or by hand is written to this file. The server hot-reloads it on every save, so you can make changes on the fly without incurring downtime. Additionally, you can override any field in the config file with an environment variable prefixed with SEPP_. See Environment variable overrides for details.
Please note that some fields are restart-only and require a full server restart to apply. These fields are clearly marked in the admin UI.
Hot reload watches the config file the server found at startup. If the server started without a config file, edits (including those made through the admin UI editor) are written to disk but only apply after a restart — run sepp config example > sepp.toml before starting the server if you want to configure it on the fly.
Config sections overview
A sepp.toml is split into a handful of tables. Each maps to a part of this documentation; the configuration reference has the exhaustive key tables with every type and default.
| Section | What it configures |
|---|---|
[server] | Bind address, data directory, gRPC TLS, and strict-queue mode. |
[auth] | gRPC API keys. See Authentication & TLS. |
[limits] | Global request and per-job limits. |
[storage] | Durability, the background sweeper and fjall tuning. |
[logging] / [tracing] / [metrics] | The three observability stacks. See Observability. |
[admin] | The admin UI listener, its TLS and its keys. |
[[queues]] | Per-queue declarations and limit overrides. See Queues. |
Environment variable overrides
Every scalar field can be set from the environment instead of the file. Prefix SEPP_, uppercase the names and join the section and field with a double underscore:
SEPP_SERVER__LISTEN_ADDR=0.0.0.0:6000
SEPP_STORAGE__PERSIST_MODE=buffer
SEPP_METRICS__PROMETHEUS_LISTEN_ADDR=127.0.0.1:9464Environment variables take precedence over the file, which takes precedence over the built-in defaults. This is the usual way to inject secrets and per-environment addresses without committing them to sepp.toml. Structured values like [[queues]] and [admin] keys are lists of tables, so keep those in the file.
A field pinned by an env var cannot be changed by editing the file: the env value always wins and the admin UI shows such fields as locked.
Hot reloading
The server watches the config file and reloads it shortly after each save (events are debounced over 500 ms). A reload re-reads the file with environment overrides reapplied, then validates the result:
- If the new config is invalid, the server logs an error and keeps running the previous configuration. A broken edit never takes the server down.
- If it is valid, hot-reloadable changes apply atomically: the API-key set and the queue registry (which carries the effective per-queue limits) switch over without dropping connections.
Some fields are read once at startup and only change on a restart. When a reload sees one of these change, it logs a warning naming the fields and leaves the running values in place.
Restart-only fields
[server]:listen_addr,db_path,tls_cert_path,tls_key_path[limits]:max_message_bytes(the gRPC decode ceiling)[storage]: every field, includingpersist_mode, the sweeper and all fjall tuning[logging],[tracing],[metrics]: each entire section[admin]:enabled,listen_addr,tls_cert_path,tls_key_path[[queues]]:dedup_window_msoverrides (dedup deadlines are pinned when a job is inserted)
Everything else reloads live, including auth.api_keys, server.strict_queues, admin.keys, the rest of [limits] and adding, removing or retuning a [[queues]] entry.
Editing from the admin UI
The admin UI writes the same sepp.toml through the same hot-reload path, so edits made in the browser and by hand are equivalent. The editor preserves your comments and formatting, flags restart-only fields and locks any field an SEPP_ variable already pins. Concurrent edits are guarded by an ETag, so a stale tab cannot clobber a newer save. The [admin] keys themselves cannot be edited through the UI; change those in the file. See the admin UI guide for the full editor.
Limits & quotas
[limits] sets the global ceilings every request is checked against. A [[queues]] entry can override most of them for one queue; the effective limit is the queue's value if set, otherwise the global. Exceeding a limit produces a deterministic rejection rather than an error. See the rejection catalogue for which limit maps to which reason.
The few that matter most in production:
max_message_bytescaps the whole gRPC request and is the hard decode ceiling (default 16 MiB, restart-only).max_payload_bytescaps a single job's payload (default 1 MiB, hot-reloadable, overridable per queue) and must not exceedmax_message_bytes.max_queue_depthbounds how many jobs a queue may hold. Omit it for unlimited (the default), set a number to cap or set0to reject every enqueue. A cap is the main backstop against an unbounded producer filling the disk.default_max_attempts(default 3) is the retry budget before a job is dead-lettered and may not exceedmax_attempts_ceiling(default 100).default_priorityruns from0to9.
Invalid combinations (a payload cap above the message cap, default attempts above the ceiling, a duplicate or malformed queue name) are rejected when the config loads. On reload the same check runs and a failure leaves the previous config in place.
Durability & persist modes
storage.persist_mode sets the durability of each write: whether, and how, the write-ahead log is fsynced to disk after every commit. It is restart-only.
| Mode | Behaviour | Choose when |
|---|---|---|
sync_all (default) | fsync data and metadata on every commit | You want the strongest durability. |
sync_data | flush data with fdatasync, skip the extra metadata sync | You want to shave commit latency while still persisting each write to disk. |
buffer | no fsync; let the OS flush on its own schedule | You want maximum throughput and can tolerate losing recently acked writes. |
Buffer mode is not crash-safe
In buffer mode an acknowledged enqueue lives only in the OS page cache, not on disk. An OS crash or power loss can lose any writes the kernel has not yet flushed. The server logs a warning at startup whenever this mode is active.
Two more knobs shape write behaviour, both restart-only:
- The sweeper runs the periodic maintenance pass that promotes due scheduled jobs and reclaims expired leases.
sweep_limit(default 10000) caps how many jobs it touches per cycle. There is alsosweep_interval_ms(default 1000) that caps the maximum time between two sweep cycles but this is only included as a safety feature against clock jumps. Under normal operation the sweeper cycle is scheduled to run exactly when the next job is due. command_queue_capacity(default 4096) bounds how many writes can be queued to the single committer thread. If it fills under a burst, new writes get a transient, retryable rejection rather than growing memory without limit.
Storage tuning
The [storage] block also exposes the underlying fjall engine's knobs. All are optional, fall back to fjall's own defaults and are restart-only. Leave them unset unless you are sizing for a specific workload; see Resource sizing for guidance.
cache_size_bytessizes the block cache. For read-heavy workloads, roughly 20 to 25% of available RAM is a reasonable starting point.max_journaling_size_bytescaps the journal (WAL) size (minimum 64 MiB).max_cached_filescaps the open file-descriptor cache (minimum 10). Raise it for databases with many segments but keep it under your process FD limit.worker_threadssets the flush and compaction thread count (default is the smaller of the CPU count and 4).
The configuration reference lists the exact defaults.
Generating an example config
sepp config example prints a fully commented configuration to stdout. Redirect it to a file to start from a complete template:
sepp config example > sepp.tomlThe printed config mirrors the built-in defaults, so an empty sepp.toml and the generated one behave identically; the generated file just documents every key inline. The server loads ./sepp.toml automatically or pass --config <path> (or set SEPP_CONFIG) to point elsewhere.